1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
20 LanguageSettingsContent, PrettierSettings,
21 },
22 BracketPairConfig,
23 Capability::ReadWrite,
24 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
25 Override, Point,
26};
27use language_settings::{Formatter, FormatterList, IndentGuideSettings};
28use multi_buffer::{IndentGuide, PathKey};
29use parking_lot::Mutex;
30use pretty_assertions::{assert_eq, assert_ne};
31use project::project_settings::{LspSettings, ProjectSettings};
32use project::FakeFs;
33use serde_json::{self, json};
34use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
35use std::{
36 iter,
37 sync::atomic::{self, AtomicUsize},
38};
39use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
40use text::ToPoint as _;
41use unindent::Unindent;
42use util::{
43 assert_set_eq, path,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45 uri,
46};
47use workspace::{
48 item::{FollowEvent, FollowableItem, Item, ItemHandle},
49 NavigationEntry, ViewId,
50};
51
52#[gpui::test]
53fn test_edit_events(cx: &mut TestAppContext) {
54 init_test(cx, |_| {});
55
56 let buffer = cx.new(|cx| {
57 let mut buffer = language::Buffer::local("123456", cx);
58 buffer.set_group_interval(Duration::from_secs(1));
59 buffer
60 });
61
62 let events = Rc::new(RefCell::new(Vec::new()));
63 let editor1 = cx.add_window({
64 let events = events.clone();
65 |window, cx| {
66 let entity = cx.entity().clone();
67 cx.subscribe_in(
68 &entity,
69 window,
70 move |_, _, event: &EditorEvent, _, _| match event {
71 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
72 EditorEvent::BufferEdited => {
73 events.borrow_mut().push(("editor1", "buffer edited"))
74 }
75 _ => {}
76 },
77 )
78 .detach();
79 Editor::for_buffer(buffer.clone(), None, window, cx)
80 }
81 });
82
83 let editor2 = cx.add_window({
84 let events = events.clone();
85 |window, cx| {
86 cx.subscribe_in(
87 &cx.entity().clone(),
88 window,
89 move |_, _, event: &EditorEvent, _, _| match event {
90 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
91 EditorEvent::BufferEdited => {
92 events.borrow_mut().push(("editor2", "buffer edited"))
93 }
94 _ => {}
95 },
96 )
97 .detach();
98 Editor::for_buffer(buffer.clone(), None, window, cx)
99 }
100 });
101
102 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
103
104 // Mutating editor 1 will emit an `Edited` event only for that editor.
105 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor1", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Mutating editor 2 will emit an `Edited` event only for that editor.
116 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor2", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Undoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Redoing on editor 1 will emit an `Edited` event only for that editor.
138 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor1", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Undoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // Redoing on editor 2 will emit an `Edited` event only for that editor.
160 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
161 assert_eq!(
162 mem::take(&mut *events.borrow_mut()),
163 [
164 ("editor2", "edited"),
165 ("editor1", "buffer edited"),
166 ("editor2", "buffer edited"),
167 ]
168 );
169
170 // No event is emitted when the mutation is a no-op.
171 _ = editor2.update(cx, |editor, window, cx| {
172 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
173
174 editor.backspace(&Backspace, window, cx);
175 });
176 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
177}
178
179#[gpui::test]
180fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
181 init_test(cx, |_| {});
182
183 let mut now = Instant::now();
184 let group_interval = Duration::from_millis(1);
185 let buffer = cx.new(|cx| {
186 let mut buf = language::Buffer::local("123456", cx);
187 buf.set_group_interval(group_interval);
188 buf
189 });
190 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
191 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
192
193 _ = editor.update(cx, |editor, window, cx| {
194 editor.start_transaction_at(now, window, cx);
195 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
196
197 editor.insert("cd", window, cx);
198 editor.end_transaction_at(now, cx);
199 assert_eq!(editor.text(cx), "12cd56");
200 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
201
202 editor.start_transaction_at(now, window, cx);
203 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
204 editor.insert("e", window, cx);
205 editor.end_transaction_at(now, cx);
206 assert_eq!(editor.text(cx), "12cde6");
207 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
208
209 now += group_interval + Duration::from_millis(1);
210 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
211
212 // Simulate an edit in another editor
213 buffer.update(cx, |buffer, cx| {
214 buffer.start_transaction_at(now, cx);
215 buffer.edit([(0..1, "a")], None, cx);
216 buffer.edit([(1..1, "b")], None, cx);
217 buffer.end_transaction_at(now, cx);
218 });
219
220 assert_eq!(editor.text(cx), "ab2cde6");
221 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
222
223 // Last transaction happened past the group interval in a different editor.
224 // Undo it individually and don't restore selections.
225 editor.undo(&Undo, window, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
228
229 // First two transactions happened within the group interval in this editor.
230 // Undo them together and restore selections.
231 editor.undo(&Undo, window, cx);
232 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
233 assert_eq!(editor.text(cx), "123456");
234 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
235
236 // Redo the first two transactions together.
237 editor.redo(&Redo, window, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
240
241 // Redo the last transaction on its own.
242 editor.redo(&Redo, window, cx);
243 assert_eq!(editor.text(cx), "ab2cde6");
244 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
245
246 // Test empty transactions.
247 editor.start_transaction_at(now, window, cx);
248 editor.end_transaction_at(now, cx);
249 editor.undo(&Undo, window, cx);
250 assert_eq!(editor.text(cx), "12cde6");
251 });
252}
253
254#[gpui::test]
255fn test_ime_composition(cx: &mut TestAppContext) {
256 init_test(cx, |_| {});
257
258 let buffer = cx.new(|cx| {
259 let mut buffer = language::Buffer::local("abcde", cx);
260 // Ensure automatic grouping doesn't occur.
261 buffer.set_group_interval(Duration::ZERO);
262 buffer
263 });
264
265 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
266 cx.add_window(|window, cx| {
267 let mut editor = build_editor(buffer.clone(), window, cx);
268
269 // Start a new IME composition.
270 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
272 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
273 assert_eq!(editor.text(cx), "äbcde");
274 assert_eq!(
275 editor.marked_text_ranges(cx),
276 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
277 );
278
279 // Finalize IME composition.
280 editor.replace_text_in_range(None, "ā", window, cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // IME composition edits are grouped and are undone/redone at once.
285 editor.undo(&Default::default(), window, cx);
286 assert_eq!(editor.text(cx), "abcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288 editor.redo(&Default::default(), window, cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition.
293 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
294 assert_eq!(
295 editor.marked_text_ranges(cx),
296 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
297 );
298
299 // Undoing during an IME composition cancels it.
300 editor.undo(&Default::default(), window, cx);
301 assert_eq!(editor.text(cx), "ābcde");
302 assert_eq!(editor.marked_text_ranges(cx), None);
303
304 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
305 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
306 assert_eq!(editor.text(cx), "ābcdè");
307 assert_eq!(
308 editor.marked_text_ranges(cx),
309 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
310 );
311
312 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
313 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
314 assert_eq!(editor.text(cx), "ābcdę");
315 assert_eq!(editor.marked_text_ranges(cx), None);
316
317 // Start a new IME composition with multiple cursors.
318 editor.change_selections(None, window, cx, |s| {
319 s.select_ranges([
320 OffsetUtf16(1)..OffsetUtf16(1),
321 OffsetUtf16(3)..OffsetUtf16(3),
322 OffsetUtf16(5)..OffsetUtf16(5),
323 ])
324 });
325 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
326 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(0)..OffsetUtf16(3),
331 OffsetUtf16(4)..OffsetUtf16(7),
332 OffsetUtf16(8)..OffsetUtf16(11)
333 ])
334 );
335
336 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
337 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
338 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
339 assert_eq!(
340 editor.marked_text_ranges(cx),
341 Some(vec![
342 OffsetUtf16(1)..OffsetUtf16(2),
343 OffsetUtf16(5)..OffsetUtf16(6),
344 OffsetUtf16(9)..OffsetUtf16(10)
345 ])
346 );
347
348 // Finalize IME composition with multiple cursors.
349 editor.replace_text_in_range(Some(9..10), "2", window, cx);
350 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
351 assert_eq!(editor.marked_text_ranges(cx), None);
352
353 editor
354 });
355}
356
357#[gpui::test]
358fn test_selection_with_mouse(cx: &mut TestAppContext) {
359 init_test(cx, |_| {});
360
361 let editor = cx.add_window(|window, cx| {
362 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
363 build_editor(buffer, window, cx)
364 });
365
366 _ = editor.update(cx, |editor, window, cx| {
367 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
368 });
369 assert_eq!(
370 editor
371 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
374 );
375
376 _ = editor.update(cx, |editor, window, cx| {
377 editor.update_selection(
378 DisplayPoint::new(DisplayRow(3), 3),
379 0,
380 gpui::Point::<f32>::default(),
381 window,
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
391 );
392
393 _ = editor.update(cx, |editor, window, cx| {
394 editor.update_selection(
395 DisplayPoint::new(DisplayRow(1), 1),
396 0,
397 gpui::Point::<f32>::default(),
398 window,
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |editor, window, cx| {
411 editor.end_selection(window, cx);
412 editor.update_selection(
413 DisplayPoint::new(DisplayRow(3), 3),
414 0,
415 gpui::Point::<f32>::default(),
416 window,
417 cx,
418 );
419 });
420
421 assert_eq!(
422 editor
423 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
424 .unwrap(),
425 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
426 );
427
428 _ = editor.update(cx, |editor, window, cx| {
429 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
430 editor.update_selection(
431 DisplayPoint::new(DisplayRow(0), 0),
432 0,
433 gpui::Point::<f32>::default(),
434 window,
435 cx,
436 );
437 });
438
439 assert_eq!(
440 editor
441 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
442 .unwrap(),
443 [
444 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
445 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
446 ]
447 );
448
449 _ = editor.update(cx, |editor, window, cx| {
450 editor.end_selection(window, cx);
451 });
452
453 assert_eq!(
454 editor
455 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
456 .unwrap(),
457 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
458 );
459}
460
461#[gpui::test]
462fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
463 init_test(cx, |_| {});
464
465 let editor = cx.add_window(|window, cx| {
466 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
467 build_editor(buffer, window, cx)
468 });
469
470 _ = editor.update(cx, |editor, window, cx| {
471 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
472 });
473
474 _ = editor.update(cx, |editor, window, cx| {
475 editor.end_selection(window, cx);
476 });
477
478 _ = editor.update(cx, |editor, window, cx| {
479 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
480 });
481
482 _ = editor.update(cx, |editor, window, cx| {
483 editor.end_selection(window, cx);
484 });
485
486 assert_eq!(
487 editor
488 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
489 .unwrap(),
490 [
491 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
492 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
493 ]
494 );
495
496 _ = editor.update(cx, |editor, window, cx| {
497 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
498 });
499
500 _ = editor.update(cx, |editor, window, cx| {
501 editor.end_selection(window, cx);
502 });
503
504 assert_eq!(
505 editor
506 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
507 .unwrap(),
508 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
509 );
510}
511
512#[gpui::test]
513fn test_canceling_pending_selection(cx: &mut TestAppContext) {
514 init_test(cx, |_| {});
515
516 let editor = cx.add_window(|window, cx| {
517 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
518 build_editor(buffer, window, cx)
519 });
520
521 _ = editor.update(cx, |editor, window, cx| {
522 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
523 assert_eq!(
524 editor.selections.display_ranges(cx),
525 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
526 );
527 });
528
529 _ = editor.update(cx, |editor, window, cx| {
530 editor.update_selection(
531 DisplayPoint::new(DisplayRow(3), 3),
532 0,
533 gpui::Point::<f32>::default(),
534 window,
535 cx,
536 );
537 assert_eq!(
538 editor.selections.display_ranges(cx),
539 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
540 );
541 });
542
543 _ = editor.update(cx, |editor, window, cx| {
544 editor.cancel(&Cancel, window, cx);
545 editor.update_selection(
546 DisplayPoint::new(DisplayRow(1), 1),
547 0,
548 gpui::Point::<f32>::default(),
549 window,
550 cx,
551 );
552 assert_eq!(
553 editor.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
555 );
556 });
557}
558
559#[gpui::test]
560fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
561 init_test(cx, |_| {});
562
563 let editor = cx.add_window(|window, cx| {
564 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
565 build_editor(buffer, window, cx)
566 });
567
568 _ = editor.update(cx, |editor, window, cx| {
569 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
570 assert_eq!(
571 editor.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
573 );
574
575 editor.move_down(&Default::default(), window, cx);
576 assert_eq!(
577 editor.selections.display_ranges(cx),
578 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
579 );
580
581 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
582 assert_eq!(
583 editor.selections.display_ranges(cx),
584 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
585 );
586
587 editor.move_up(&Default::default(), window, cx);
588 assert_eq!(
589 editor.selections.display_ranges(cx),
590 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
591 );
592 });
593}
594
595#[gpui::test]
596fn test_clone(cx: &mut TestAppContext) {
597 init_test(cx, |_| {});
598
599 let (text, selection_ranges) = marked_text_ranges(
600 indoc! {"
601 one
602 two
603 threeˇ
604 four
605 fiveˇ
606 "},
607 true,
608 );
609
610 let editor = cx.add_window(|window, cx| {
611 let buffer = MultiBuffer::build_simple(&text, cx);
612 build_editor(buffer, window, cx)
613 });
614
615 _ = editor.update(cx, |editor, window, cx| {
616 editor.change_selections(None, window, cx, |s| {
617 s.select_ranges(selection_ranges.clone())
618 });
619 editor.fold_creases(
620 vec![
621 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
622 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
623 ],
624 true,
625 window,
626 cx,
627 );
628 });
629
630 let cloned_editor = editor
631 .update(cx, |editor, _, cx| {
632 cx.open_window(Default::default(), |window, cx| {
633 cx.new(|cx| editor.clone(window, cx))
634 })
635 })
636 .unwrap()
637 .unwrap();
638
639 let snapshot = editor
640 .update(cx, |e, window, cx| e.snapshot(window, cx))
641 .unwrap();
642 let cloned_snapshot = cloned_editor
643 .update(cx, |e, window, cx| e.snapshot(window, cx))
644 .unwrap();
645
646 assert_eq!(
647 cloned_editor
648 .update(cx, |e, _, cx| e.display_text(cx))
649 .unwrap(),
650 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
651 );
652 assert_eq!(
653 cloned_snapshot
654 .folds_in_range(0..text.len())
655 .collect::<Vec<_>>(),
656 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
657 );
658 assert_set_eq!(
659 cloned_editor
660 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
661 .unwrap(),
662 editor
663 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
664 .unwrap()
665 );
666 assert_set_eq!(
667 cloned_editor
668 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
669 .unwrap(),
670 editor
671 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
672 .unwrap()
673 );
674}
675
676#[gpui::test]
677async fn test_navigation_history(cx: &mut TestAppContext) {
678 init_test(cx, |_| {});
679
680 use workspace::item::Item;
681
682 let fs = FakeFs::new(cx.executor());
683 let project = Project::test(fs, [], cx).await;
684 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
685 let pane = workspace
686 .update(cx, |workspace, _, _| workspace.active_pane().clone())
687 .unwrap();
688
689 _ = workspace.update(cx, |_v, window, cx| {
690 cx.new(|cx| {
691 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
692 let mut editor = build_editor(buffer.clone(), window, cx);
693 let handle = cx.entity();
694 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
695
696 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
697 editor.nav_history.as_mut().unwrap().pop_backward(cx)
698 }
699
700 // Move the cursor a small distance.
701 // Nothing is added to the navigation history.
702 editor.change_selections(None, window, cx, |s| {
703 s.select_display_ranges([
704 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
705 ])
706 });
707 editor.change_selections(None, window, cx, |s| {
708 s.select_display_ranges([
709 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
710 ])
711 });
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance.
715 // The history can jump back to the previous position.
716 editor.change_selections(None, window, cx, |s| {
717 s.select_display_ranges([
718 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
719 ])
720 });
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), window, cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Move the cursor a small distance via the mouse.
731 // Nothing is added to the navigation history.
732 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
733 editor.end_selection(window, cx);
734 assert_eq!(
735 editor.selections.display_ranges(cx),
736 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
737 );
738 assert!(pop_history(&mut editor, cx).is_none());
739
740 // Move the cursor a large distance via the mouse.
741 // The history can jump back to the previous position.
742 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
743 editor.end_selection(window, cx);
744 assert_eq!(
745 editor.selections.display_ranges(cx),
746 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
747 );
748 let nav_entry = pop_history(&mut editor, cx).unwrap();
749 editor.navigate(nav_entry.data.unwrap(), window, cx);
750 assert_eq!(nav_entry.item.id(), cx.entity_id());
751 assert_eq!(
752 editor.selections.display_ranges(cx),
753 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
754 );
755 assert!(pop_history(&mut editor, cx).is_none());
756
757 // Set scroll position to check later
758 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
759 let original_scroll_position = editor.scroll_manager.anchor();
760
761 // Jump to the end of the document and adjust scroll
762 editor.move_to_end(&MoveToEnd, window, cx);
763 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
764 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
765
766 let nav_entry = pop_history(&mut editor, cx).unwrap();
767 editor.navigate(nav_entry.data.unwrap(), window, cx);
768 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
769
770 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
771 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
772 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
773 let invalid_point = Point::new(9999, 0);
774 editor.navigate(
775 Box::new(NavigationData {
776 cursor_anchor: invalid_anchor,
777 cursor_position: invalid_point,
778 scroll_anchor: ScrollAnchor {
779 anchor: invalid_anchor,
780 offset: Default::default(),
781 },
782 scroll_top_row: invalid_point.row,
783 }),
784 window,
785 cx,
786 );
787 assert_eq!(
788 editor.selections.display_ranges(cx),
789 &[editor.max_point(cx)..editor.max_point(cx)]
790 );
791 assert_eq!(
792 editor.scroll_position(cx),
793 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
794 );
795
796 editor
797 })
798 });
799}
800
801#[gpui::test]
802fn test_cancel(cx: &mut TestAppContext) {
803 init_test(cx, |_| {});
804
805 let editor = cx.add_window(|window, cx| {
806 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
807 build_editor(buffer, window, cx)
808 });
809
810 _ = editor.update(cx, |editor, window, cx| {
811 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
812 editor.update_selection(
813 DisplayPoint::new(DisplayRow(1), 1),
814 0,
815 gpui::Point::<f32>::default(),
816 window,
817 cx,
818 );
819 editor.end_selection(window, cx);
820
821 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
822 editor.update_selection(
823 DisplayPoint::new(DisplayRow(0), 3),
824 0,
825 gpui::Point::<f32>::default(),
826 window,
827 cx,
828 );
829 editor.end_selection(window, cx);
830 assert_eq!(
831 editor.selections.display_ranges(cx),
832 [
833 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
834 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
835 ]
836 );
837 });
838
839 _ = editor.update(cx, |editor, window, cx| {
840 editor.cancel(&Cancel, window, cx);
841 assert_eq!(
842 editor.selections.display_ranges(cx),
843 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
844 );
845 });
846
847 _ = editor.update(cx, |editor, window, cx| {
848 editor.cancel(&Cancel, window, cx);
849 assert_eq!(
850 editor.selections.display_ranges(cx),
851 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
852 );
853 });
854}
855
856#[gpui::test]
857fn test_fold_action(cx: &mut TestAppContext) {
858 init_test(cx, |_| {});
859
860 let editor = cx.add_window(|window, cx| {
861 let buffer = MultiBuffer::build_simple(
862 &"
863 impl Foo {
864 // Hello!
865
866 fn a() {
867 1
868 }
869
870 fn b() {
871 2
872 }
873
874 fn c() {
875 3
876 }
877 }
878 "
879 .unindent(),
880 cx,
881 );
882 build_editor(buffer.clone(), window, cx)
883 });
884
885 _ = editor.update(cx, |editor, window, cx| {
886 editor.change_selections(None, window, cx, |s| {
887 s.select_display_ranges([
888 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
889 ]);
890 });
891 editor.fold(&Fold, window, cx);
892 assert_eq!(
893 editor.display_text(cx),
894 "
895 impl Foo {
896 // Hello!
897
898 fn a() {
899 1
900 }
901
902 fn b() {⋯
903 }
904
905 fn c() {⋯
906 }
907 }
908 "
909 .unindent(),
910 );
911
912 editor.fold(&Fold, window, cx);
913 assert_eq!(
914 editor.display_text(cx),
915 "
916 impl Foo {⋯
917 }
918 "
919 .unindent(),
920 );
921
922 editor.unfold_lines(&UnfoldLines, window, cx);
923 assert_eq!(
924 editor.display_text(cx),
925 "
926 impl Foo {
927 // Hello!
928
929 fn a() {
930 1
931 }
932
933 fn b() {⋯
934 }
935
936 fn c() {⋯
937 }
938 }
939 "
940 .unindent(),
941 );
942
943 editor.unfold_lines(&UnfoldLines, window, cx);
944 assert_eq!(
945 editor.display_text(cx),
946 editor.buffer.read(cx).read(cx).text()
947 );
948 });
949}
950
951#[gpui::test]
952fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
953 init_test(cx, |_| {});
954
955 let editor = cx.add_window(|window, cx| {
956 let buffer = MultiBuffer::build_simple(
957 &"
958 class Foo:
959 # Hello!
960
961 def a():
962 print(1)
963
964 def b():
965 print(2)
966
967 def c():
968 print(3)
969 "
970 .unindent(),
971 cx,
972 );
973 build_editor(buffer.clone(), window, cx)
974 });
975
976 _ = editor.update(cx, |editor, window, cx| {
977 editor.change_selections(None, window, cx, |s| {
978 s.select_display_ranges([
979 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
980 ]);
981 });
982 editor.fold(&Fold, window, cx);
983 assert_eq!(
984 editor.display_text(cx),
985 "
986 class Foo:
987 # Hello!
988
989 def a():
990 print(1)
991
992 def b():⋯
993
994 def c():⋯
995 "
996 .unindent(),
997 );
998
999 editor.fold(&Fold, window, cx);
1000 assert_eq!(
1001 editor.display_text(cx),
1002 "
1003 class Foo:⋯
1004 "
1005 .unindent(),
1006 );
1007
1008 editor.unfold_lines(&UnfoldLines, window, cx);
1009 assert_eq!(
1010 editor.display_text(cx),
1011 "
1012 class Foo:
1013 # Hello!
1014
1015 def a():
1016 print(1)
1017
1018 def b():⋯
1019
1020 def c():⋯
1021 "
1022 .unindent(),
1023 );
1024
1025 editor.unfold_lines(&UnfoldLines, window, cx);
1026 assert_eq!(
1027 editor.display_text(cx),
1028 editor.buffer.read(cx).read(cx).text()
1029 );
1030 });
1031}
1032
1033#[gpui::test]
1034fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1035 init_test(cx, |_| {});
1036
1037 let editor = cx.add_window(|window, cx| {
1038 let buffer = MultiBuffer::build_simple(
1039 &"
1040 class Foo:
1041 # Hello!
1042
1043 def a():
1044 print(1)
1045
1046 def b():
1047 print(2)
1048
1049
1050 def c():
1051 print(3)
1052
1053
1054 "
1055 .unindent(),
1056 cx,
1057 );
1058 build_editor(buffer.clone(), window, cx)
1059 });
1060
1061 _ = editor.update(cx, |editor, window, cx| {
1062 editor.change_selections(None, window, cx, |s| {
1063 s.select_display_ranges([
1064 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1065 ]);
1066 });
1067 editor.fold(&Fold, window, cx);
1068 assert_eq!(
1069 editor.display_text(cx),
1070 "
1071 class Foo:
1072 # Hello!
1073
1074 def a():
1075 print(1)
1076
1077 def b():⋯
1078
1079
1080 def c():⋯
1081
1082
1083 "
1084 .unindent(),
1085 );
1086
1087 editor.fold(&Fold, window, cx);
1088 assert_eq!(
1089 editor.display_text(cx),
1090 "
1091 class Foo:⋯
1092
1093
1094 "
1095 .unindent(),
1096 );
1097
1098 editor.unfold_lines(&UnfoldLines, window, cx);
1099 assert_eq!(
1100 editor.display_text(cx),
1101 "
1102 class Foo:
1103 # Hello!
1104
1105 def a():
1106 print(1)
1107
1108 def b():⋯
1109
1110
1111 def c():⋯
1112
1113
1114 "
1115 .unindent(),
1116 );
1117
1118 editor.unfold_lines(&UnfoldLines, window, cx);
1119 assert_eq!(
1120 editor.display_text(cx),
1121 editor.buffer.read(cx).read(cx).text()
1122 );
1123 });
1124}
1125
1126#[gpui::test]
1127fn test_fold_at_level(cx: &mut TestAppContext) {
1128 init_test(cx, |_| {});
1129
1130 let editor = cx.add_window(|window, cx| {
1131 let buffer = MultiBuffer::build_simple(
1132 &"
1133 class Foo:
1134 # Hello!
1135
1136 def a():
1137 print(1)
1138
1139 def b():
1140 print(2)
1141
1142
1143 class Bar:
1144 # World!
1145
1146 def a():
1147 print(1)
1148
1149 def b():
1150 print(2)
1151
1152
1153 "
1154 .unindent(),
1155 cx,
1156 );
1157 build_editor(buffer.clone(), window, cx)
1158 });
1159
1160 _ = editor.update(cx, |editor, window, cx| {
1161 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1162 assert_eq!(
1163 editor.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():⋯
1169
1170 def b():⋯
1171
1172
1173 class Bar:
1174 # World!
1175
1176 def a():⋯
1177
1178 def b():⋯
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1186 assert_eq!(
1187 editor.display_text(cx),
1188 "
1189 class Foo:⋯
1190
1191
1192 class Bar:⋯
1193
1194
1195 "
1196 .unindent(),
1197 );
1198
1199 editor.unfold_all(&UnfoldAll, window, cx);
1200 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1201 assert_eq!(
1202 editor.display_text(cx),
1203 "
1204 class Foo:
1205 # Hello!
1206
1207 def a():
1208 print(1)
1209
1210 def b():
1211 print(2)
1212
1213
1214 class Bar:
1215 # World!
1216
1217 def a():
1218 print(1)
1219
1220 def b():
1221 print(2)
1222
1223
1224 "
1225 .unindent(),
1226 );
1227
1228 assert_eq!(
1229 editor.display_text(cx),
1230 editor.buffer.read(cx).read(cx).text()
1231 );
1232 });
1233}
1234
1235#[gpui::test]
1236fn test_move_cursor(cx: &mut TestAppContext) {
1237 init_test(cx, |_| {});
1238
1239 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1240 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1241
1242 buffer.update(cx, |buffer, cx| {
1243 buffer.edit(
1244 vec![
1245 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1246 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1247 ],
1248 None,
1249 cx,
1250 );
1251 });
1252 _ = editor.update(cx, |editor, window, cx| {
1253 assert_eq!(
1254 editor.selections.display_ranges(cx),
1255 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1256 );
1257
1258 editor.move_down(&MoveDown, window, cx);
1259 assert_eq!(
1260 editor.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1262 );
1263
1264 editor.move_right(&MoveRight, window, cx);
1265 assert_eq!(
1266 editor.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1268 );
1269
1270 editor.move_left(&MoveLeft, window, cx);
1271 assert_eq!(
1272 editor.selections.display_ranges(cx),
1273 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1274 );
1275
1276 editor.move_up(&MoveUp, window, cx);
1277 assert_eq!(
1278 editor.selections.display_ranges(cx),
1279 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1280 );
1281
1282 editor.move_to_end(&MoveToEnd, window, cx);
1283 assert_eq!(
1284 editor.selections.display_ranges(cx),
1285 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1286 );
1287
1288 editor.move_to_beginning(&MoveToBeginning, window, cx);
1289 assert_eq!(
1290 editor.selections.display_ranges(cx),
1291 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1292 );
1293
1294 editor.change_selections(None, window, cx, |s| {
1295 s.select_display_ranges([
1296 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1297 ]);
1298 });
1299 editor.select_to_beginning(&SelectToBeginning, window, cx);
1300 assert_eq!(
1301 editor.selections.display_ranges(cx),
1302 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1303 );
1304
1305 editor.select_to_end(&SelectToEnd, window, cx);
1306 assert_eq!(
1307 editor.selections.display_ranges(cx),
1308 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1309 );
1310 });
1311}
1312
1313// TODO: Re-enable this test
1314#[cfg(target_os = "macos")]
1315#[gpui::test]
1316fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1317 init_test(cx, |_| {});
1318
1319 let editor = cx.add_window(|window, cx| {
1320 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1321 build_editor(buffer.clone(), window, cx)
1322 });
1323
1324 assert_eq!('🟥'.len_utf8(), 4);
1325 assert_eq!('α'.len_utf8(), 2);
1326
1327 _ = editor.update(cx, |editor, window, cx| {
1328 editor.fold_creases(
1329 vec![
1330 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1332 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1333 ],
1334 true,
1335 window,
1336 cx,
1337 );
1338 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1339
1340 editor.move_right(&MoveRight, window, cx);
1341 assert_eq!(
1342 editor.selections.display_ranges(cx),
1343 &[empty_range(0, "🟥".len())]
1344 );
1345 editor.move_right(&MoveRight, window, cx);
1346 assert_eq!(
1347 editor.selections.display_ranges(cx),
1348 &[empty_range(0, "🟥🟧".len())]
1349 );
1350 editor.move_right(&MoveRight, window, cx);
1351 assert_eq!(
1352 editor.selections.display_ranges(cx),
1353 &[empty_range(0, "🟥🟧⋯".len())]
1354 );
1355
1356 editor.move_down(&MoveDown, window, cx);
1357 assert_eq!(
1358 editor.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 editor.move_left(&MoveLeft, window, cx);
1362 assert_eq!(
1363 editor.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯".len())]
1365 );
1366 editor.move_left(&MoveLeft, window, cx);
1367 assert_eq!(
1368 editor.selections.display_ranges(cx),
1369 &[empty_range(1, "ab".len())]
1370 );
1371 editor.move_left(&MoveLeft, window, cx);
1372 assert_eq!(
1373 editor.selections.display_ranges(cx),
1374 &[empty_range(1, "a".len())]
1375 );
1376
1377 editor.move_down(&MoveDown, window, cx);
1378 assert_eq!(
1379 editor.selections.display_ranges(cx),
1380 &[empty_range(2, "α".len())]
1381 );
1382 editor.move_right(&MoveRight, window, cx);
1383 assert_eq!(
1384 editor.selections.display_ranges(cx),
1385 &[empty_range(2, "αβ".len())]
1386 );
1387 editor.move_right(&MoveRight, window, cx);
1388 assert_eq!(
1389 editor.selections.display_ranges(cx),
1390 &[empty_range(2, "αβ⋯".len())]
1391 );
1392 editor.move_right(&MoveRight, window, cx);
1393 assert_eq!(
1394 editor.selections.display_ranges(cx),
1395 &[empty_range(2, "αβ⋯ε".len())]
1396 );
1397
1398 editor.move_up(&MoveUp, window, cx);
1399 assert_eq!(
1400 editor.selections.display_ranges(cx),
1401 &[empty_range(1, "ab⋯e".len())]
1402 );
1403 editor.move_down(&MoveDown, window, cx);
1404 assert_eq!(
1405 editor.selections.display_ranges(cx),
1406 &[empty_range(2, "αβ⋯ε".len())]
1407 );
1408 editor.move_up(&MoveUp, window, cx);
1409 assert_eq!(
1410 editor.selections.display_ranges(cx),
1411 &[empty_range(1, "ab⋯e".len())]
1412 );
1413
1414 editor.move_up(&MoveUp, window, cx);
1415 assert_eq!(
1416 editor.selections.display_ranges(cx),
1417 &[empty_range(0, "🟥🟧".len())]
1418 );
1419 editor.move_left(&MoveLeft, window, cx);
1420 assert_eq!(
1421 editor.selections.display_ranges(cx),
1422 &[empty_range(0, "🟥".len())]
1423 );
1424 editor.move_left(&MoveLeft, window, cx);
1425 assert_eq!(
1426 editor.selections.display_ranges(cx),
1427 &[empty_range(0, "".len())]
1428 );
1429 });
1430}
1431
1432#[gpui::test]
1433fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1434 init_test(cx, |_| {});
1435
1436 let editor = cx.add_window(|window, cx| {
1437 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1438 build_editor(buffer.clone(), window, cx)
1439 });
1440 _ = editor.update(cx, |editor, window, cx| {
1441 editor.change_selections(None, window, cx, |s| {
1442 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1443 });
1444
1445 // moving above start of document should move selection to start of document,
1446 // but the next move down should still be at the original goal_x
1447 editor.move_up(&MoveUp, window, cx);
1448 assert_eq!(
1449 editor.selections.display_ranges(cx),
1450 &[empty_range(0, "".len())]
1451 );
1452
1453 editor.move_down(&MoveDown, window, cx);
1454 assert_eq!(
1455 editor.selections.display_ranges(cx),
1456 &[empty_range(1, "abcd".len())]
1457 );
1458
1459 editor.move_down(&MoveDown, window, cx);
1460 assert_eq!(
1461 editor.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464
1465 editor.move_down(&MoveDown, window, cx);
1466 assert_eq!(
1467 editor.selections.display_ranges(cx),
1468 &[empty_range(3, "abcd".len())]
1469 );
1470
1471 editor.move_down(&MoveDown, window, cx);
1472 assert_eq!(
1473 editor.selections.display_ranges(cx),
1474 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1475 );
1476
1477 // moving past end of document should not change goal_x
1478 editor.move_down(&MoveDown, window, cx);
1479 assert_eq!(
1480 editor.selections.display_ranges(cx),
1481 &[empty_range(5, "".len())]
1482 );
1483
1484 editor.move_down(&MoveDown, window, cx);
1485 assert_eq!(
1486 editor.selections.display_ranges(cx),
1487 &[empty_range(5, "".len())]
1488 );
1489
1490 editor.move_up(&MoveUp, window, cx);
1491 assert_eq!(
1492 editor.selections.display_ranges(cx),
1493 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1494 );
1495
1496 editor.move_up(&MoveUp, window, cx);
1497 assert_eq!(
1498 editor.selections.display_ranges(cx),
1499 &[empty_range(3, "abcd".len())]
1500 );
1501
1502 editor.move_up(&MoveUp, window, cx);
1503 assert_eq!(
1504 editor.selections.display_ranges(cx),
1505 &[empty_range(2, "αβγ".len())]
1506 );
1507 });
1508}
1509
1510#[gpui::test]
1511fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1512 init_test(cx, |_| {});
1513 let move_to_beg = MoveToBeginningOfLine {
1514 stop_at_soft_wraps: true,
1515 stop_at_indent: true,
1516 };
1517
1518 let delete_to_beg = DeleteToBeginningOfLine {
1519 stop_at_indent: false,
1520 };
1521
1522 let move_to_end = MoveToEndOfLine {
1523 stop_at_soft_wraps: true,
1524 };
1525
1526 let editor = cx.add_window(|window, cx| {
1527 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1528 build_editor(buffer, window, cx)
1529 });
1530 _ = editor.update(cx, |editor, window, cx| {
1531 editor.change_selections(None, window, cx, |s| {
1532 s.select_display_ranges([
1533 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1534 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1535 ]);
1536 });
1537 });
1538
1539 _ = editor.update(cx, |editor, window, cx| {
1540 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1541 assert_eq!(
1542 editor.selections.display_ranges(cx),
1543 &[
1544 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1545 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1546 ]
1547 );
1548 });
1549
1550 _ = editor.update(cx, |editor, window, cx| {
1551 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1552 assert_eq!(
1553 editor.selections.display_ranges(cx),
1554 &[
1555 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1556 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1557 ]
1558 );
1559 });
1560
1561 _ = editor.update(cx, |editor, window, cx| {
1562 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1563 assert_eq!(
1564 editor.selections.display_ranges(cx),
1565 &[
1566 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1567 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1568 ]
1569 );
1570 });
1571
1572 _ = editor.update(cx, |editor, window, cx| {
1573 editor.move_to_end_of_line(&move_to_end, window, cx);
1574 assert_eq!(
1575 editor.selections.display_ranges(cx),
1576 &[
1577 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1578 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1579 ]
1580 );
1581 });
1582
1583 // Moving to the end of line again is a no-op.
1584 _ = editor.update(cx, |editor, window, cx| {
1585 editor.move_to_end_of_line(&move_to_end, window, cx);
1586 assert_eq!(
1587 editor.selections.display_ranges(cx),
1588 &[
1589 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1590 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1591 ]
1592 );
1593 });
1594
1595 _ = editor.update(cx, |editor, window, cx| {
1596 editor.move_left(&MoveLeft, window, cx);
1597 editor.select_to_beginning_of_line(
1598 &SelectToBeginningOfLine {
1599 stop_at_soft_wraps: true,
1600 stop_at_indent: true,
1601 },
1602 window,
1603 cx,
1604 );
1605 assert_eq!(
1606 editor.selections.display_ranges(cx),
1607 &[
1608 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1609 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1610 ]
1611 );
1612 });
1613
1614 _ = editor.update(cx, |editor, window, cx| {
1615 editor.select_to_beginning_of_line(
1616 &SelectToBeginningOfLine {
1617 stop_at_soft_wraps: true,
1618 stop_at_indent: true,
1619 },
1620 window,
1621 cx,
1622 );
1623 assert_eq!(
1624 editor.selections.display_ranges(cx),
1625 &[
1626 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1627 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1628 ]
1629 );
1630 });
1631
1632 _ = editor.update(cx, |editor, window, cx| {
1633 editor.select_to_beginning_of_line(
1634 &SelectToBeginningOfLine {
1635 stop_at_soft_wraps: true,
1636 stop_at_indent: true,
1637 },
1638 window,
1639 cx,
1640 );
1641 assert_eq!(
1642 editor.selections.display_ranges(cx),
1643 &[
1644 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1645 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1646 ]
1647 );
1648 });
1649
1650 _ = editor.update(cx, |editor, window, cx| {
1651 editor.select_to_end_of_line(
1652 &SelectToEndOfLine {
1653 stop_at_soft_wraps: true,
1654 },
1655 window,
1656 cx,
1657 );
1658 assert_eq!(
1659 editor.selections.display_ranges(cx),
1660 &[
1661 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1662 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1663 ]
1664 );
1665 });
1666
1667 _ = editor.update(cx, |editor, window, cx| {
1668 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1669 assert_eq!(editor.display_text(cx), "ab\n de");
1670 assert_eq!(
1671 editor.selections.display_ranges(cx),
1672 &[
1673 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1674 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1675 ]
1676 );
1677 });
1678
1679 _ = editor.update(cx, |editor, window, cx| {
1680 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1681 assert_eq!(editor.display_text(cx), "\n");
1682 assert_eq!(
1683 editor.selections.display_ranges(cx),
1684 &[
1685 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1686 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1687 ]
1688 );
1689 });
1690}
1691
1692#[gpui::test]
1693fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1694 init_test(cx, |_| {});
1695 let move_to_beg = MoveToBeginningOfLine {
1696 stop_at_soft_wraps: false,
1697 stop_at_indent: false,
1698 };
1699
1700 let move_to_end = MoveToEndOfLine {
1701 stop_at_soft_wraps: false,
1702 };
1703
1704 let editor = cx.add_window(|window, cx| {
1705 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1706 build_editor(buffer, window, cx)
1707 });
1708
1709 _ = editor.update(cx, |editor, window, cx| {
1710 editor.set_wrap_width(Some(140.0.into()), cx);
1711
1712 // We expect the following lines after wrapping
1713 // ```
1714 // thequickbrownfox
1715 // jumpedoverthelazydo
1716 // gs
1717 // ```
1718 // The final `gs` was soft-wrapped onto a new line.
1719 assert_eq!(
1720 "thequickbrownfox\njumpedoverthelaz\nydogs",
1721 editor.display_text(cx),
1722 );
1723
1724 // First, let's assert behavior on the first line, that was not soft-wrapped.
1725 // Start the cursor at the `k` on the first line
1726 editor.change_selections(None, window, cx, |s| {
1727 s.select_display_ranges([
1728 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1729 ]);
1730 });
1731
1732 // Moving to the beginning of the line should put us at the beginning of the line.
1733 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1734 assert_eq!(
1735 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1736 editor.selections.display_ranges(cx)
1737 );
1738
1739 // Moving to the end of the line should put us at the end of the line.
1740 editor.move_to_end_of_line(&move_to_end, window, cx);
1741 assert_eq!(
1742 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1743 editor.selections.display_ranges(cx)
1744 );
1745
1746 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1747 // Start the cursor at the last line (`y` that was wrapped to a new line)
1748 editor.change_selections(None, window, cx, |s| {
1749 s.select_display_ranges([
1750 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1751 ]);
1752 });
1753
1754 // Moving to the beginning of the line should put us at the start of the second line of
1755 // display text, i.e., the `j`.
1756 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1757 assert_eq!(
1758 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1759 editor.selections.display_ranges(cx)
1760 );
1761
1762 // Moving to the beginning of the line again should be a no-op.
1763 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1764 assert_eq!(
1765 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1766 editor.selections.display_ranges(cx)
1767 );
1768
1769 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1770 // next display line.
1771 editor.move_to_end_of_line(&move_to_end, window, cx);
1772 assert_eq!(
1773 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1774 editor.selections.display_ranges(cx)
1775 );
1776
1777 // Moving to the end of the line again should be a no-op.
1778 editor.move_to_end_of_line(&move_to_end, window, cx);
1779 assert_eq!(
1780 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1781 editor.selections.display_ranges(cx)
1782 );
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let move_to_beg = MoveToBeginningOfLine {
1791 stop_at_soft_wraps: true,
1792 stop_at_indent: true,
1793 };
1794
1795 let select_to_beg = SelectToBeginningOfLine {
1796 stop_at_soft_wraps: true,
1797 stop_at_indent: true,
1798 };
1799
1800 let delete_to_beg = DeleteToBeginningOfLine {
1801 stop_at_indent: true,
1802 };
1803
1804 let move_to_end = MoveToEndOfLine {
1805 stop_at_soft_wraps: false,
1806 };
1807
1808 let editor = cx.add_window(|window, cx| {
1809 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1810 build_editor(buffer, window, cx)
1811 });
1812
1813 _ = editor.update(cx, |editor, window, cx| {
1814 editor.change_selections(None, window, cx, |s| {
1815 s.select_display_ranges([
1816 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1817 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1818 ]);
1819 });
1820
1821 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1822 // and the second cursor at the first non-whitespace character in the line.
1823 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1824 assert_eq!(
1825 editor.selections.display_ranges(cx),
1826 &[
1827 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1828 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1829 ]
1830 );
1831
1832 // Moving to the beginning of the line again should be a no-op for the first cursor,
1833 // and should move the second cursor to the beginning of the line.
1834 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1835 assert_eq!(
1836 editor.selections.display_ranges(cx),
1837 &[
1838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1839 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1840 ]
1841 );
1842
1843 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1844 // and should move the second cursor back to the first non-whitespace character in the line.
1845 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1846 assert_eq!(
1847 editor.selections.display_ranges(cx),
1848 &[
1849 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1851 ]
1852 );
1853
1854 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1855 // and to the first non-whitespace character in the line for the second cursor.
1856 editor.move_to_end_of_line(&move_to_end, window, cx);
1857 editor.move_left(&MoveLeft, window, cx);
1858 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1859 assert_eq!(
1860 editor.selections.display_ranges(cx),
1861 &[
1862 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1863 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1864 ]
1865 );
1866
1867 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1868 // and should select to the beginning of the line for the second cursor.
1869 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1870 assert_eq!(
1871 editor.selections.display_ranges(cx),
1872 &[
1873 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1874 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1875 ]
1876 );
1877
1878 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1879 // and should delete to the first non-whitespace character in the line for the second cursor.
1880 editor.move_to_end_of_line(&move_to_end, window, cx);
1881 editor.move_left(&MoveLeft, window, cx);
1882 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1883 assert_eq!(editor.text(cx), "c\n f");
1884 });
1885}
1886
1887#[gpui::test]
1888fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1889 init_test(cx, |_| {});
1890
1891 let editor = cx.add_window(|window, cx| {
1892 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1893 build_editor(buffer, window, cx)
1894 });
1895 _ = editor.update(cx, |editor, window, cx| {
1896 editor.change_selections(None, window, cx, |s| {
1897 s.select_display_ranges([
1898 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1899 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1900 ])
1901 });
1902
1903 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1904 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1905
1906 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1907 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1908
1909 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1910 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1911
1912 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1913 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1914
1915 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1916 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1917
1918 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1919 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1920
1921 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1922 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1923
1924 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1925 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1926
1927 editor.move_right(&MoveRight, window, cx);
1928 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1929 assert_selection_ranges(
1930 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1931 editor,
1932 cx,
1933 );
1934
1935 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1936 assert_selection_ranges(
1937 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1938 editor,
1939 cx,
1940 );
1941
1942 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1943 assert_selection_ranges(
1944 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1945 editor,
1946 cx,
1947 );
1948 });
1949}
1950
1951#[gpui::test]
1952fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1953 init_test(cx, |_| {});
1954
1955 let editor = cx.add_window(|window, cx| {
1956 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1957 build_editor(buffer, window, cx)
1958 });
1959
1960 _ = editor.update(cx, |editor, window, cx| {
1961 editor.set_wrap_width(Some(140.0.into()), cx);
1962 assert_eq!(
1963 editor.display_text(cx),
1964 "use one::{\n two::three::\n four::five\n};"
1965 );
1966
1967 editor.change_selections(None, window, cx, |s| {
1968 s.select_display_ranges([
1969 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1970 ]);
1971 });
1972
1973 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1974 assert_eq!(
1975 editor.selections.display_ranges(cx),
1976 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1977 );
1978
1979 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1980 assert_eq!(
1981 editor.selections.display_ranges(cx),
1982 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1983 );
1984
1985 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1986 assert_eq!(
1987 editor.selections.display_ranges(cx),
1988 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1989 );
1990
1991 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1992 assert_eq!(
1993 editor.selections.display_ranges(cx),
1994 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1995 );
1996
1997 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1998 assert_eq!(
1999 editor.selections.display_ranges(cx),
2000 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2001 );
2002
2003 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2004 assert_eq!(
2005 editor.selections.display_ranges(cx),
2006 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2007 );
2008 });
2009}
2010
2011#[gpui::test]
2012async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2013 init_test(cx, |_| {});
2014 let mut cx = EditorTestContext::new(cx).await;
2015
2016 let line_height = cx.editor(|editor, window, _| {
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(window.rem_size())
2022 });
2023 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2024
2025 cx.set_state(
2026 &r#"ˇone
2027 two
2028
2029 three
2030 fourˇ
2031 five
2032
2033 six"#
2034 .unindent(),
2035 );
2036
2037 cx.update_editor(|editor, window, cx| {
2038 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2039 });
2040 cx.assert_editor_state(
2041 &r#"one
2042 two
2043 ˇ
2044 three
2045 four
2046 five
2047 ˇ
2048 six"#
2049 .unindent(),
2050 );
2051
2052 cx.update_editor(|editor, window, cx| {
2053 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2054 });
2055 cx.assert_editor_state(
2056 &r#"one
2057 two
2058
2059 three
2060 four
2061 five
2062 ˇ
2063 sixˇ"#
2064 .unindent(),
2065 );
2066
2067 cx.update_editor(|editor, window, cx| {
2068 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2069 });
2070 cx.assert_editor_state(
2071 &r#"one
2072 two
2073
2074 three
2075 four
2076 five
2077
2078 sixˇ"#
2079 .unindent(),
2080 );
2081
2082 cx.update_editor(|editor, window, cx| {
2083 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2084 });
2085 cx.assert_editor_state(
2086 &r#"one
2087 two
2088
2089 three
2090 four
2091 five
2092 ˇ
2093 six"#
2094 .unindent(),
2095 );
2096
2097 cx.update_editor(|editor, window, cx| {
2098 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2099 });
2100 cx.assert_editor_state(
2101 &r#"one
2102 two
2103 ˇ
2104 three
2105 four
2106 five
2107
2108 six"#
2109 .unindent(),
2110 );
2111
2112 cx.update_editor(|editor, window, cx| {
2113 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2114 });
2115 cx.assert_editor_state(
2116 &r#"ˇone
2117 two
2118
2119 three
2120 four
2121 five
2122
2123 six"#
2124 .unindent(),
2125 );
2126}
2127
2128#[gpui::test]
2129async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2130 init_test(cx, |_| {});
2131 let mut cx = EditorTestContext::new(cx).await;
2132 let line_height = cx.editor(|editor, window, _| {
2133 editor
2134 .style()
2135 .unwrap()
2136 .text
2137 .line_height_in_pixels(window.rem_size())
2138 });
2139 let window = cx.window;
2140 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2141
2142 cx.set_state(
2143 r#"ˇone
2144 two
2145 three
2146 four
2147 five
2148 six
2149 seven
2150 eight
2151 nine
2152 ten
2153 "#,
2154 );
2155
2156 cx.update_editor(|editor, window, cx| {
2157 assert_eq!(
2158 editor.snapshot(window, cx).scroll_position(),
2159 gpui::Point::new(0., 0.)
2160 );
2161 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2162 assert_eq!(
2163 editor.snapshot(window, cx).scroll_position(),
2164 gpui::Point::new(0., 3.)
2165 );
2166 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2167 assert_eq!(
2168 editor.snapshot(window, cx).scroll_position(),
2169 gpui::Point::new(0., 6.)
2170 );
2171 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2172 assert_eq!(
2173 editor.snapshot(window, cx).scroll_position(),
2174 gpui::Point::new(0., 3.)
2175 );
2176
2177 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2178 assert_eq!(
2179 editor.snapshot(window, cx).scroll_position(),
2180 gpui::Point::new(0., 1.)
2181 );
2182 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2183 assert_eq!(
2184 editor.snapshot(window, cx).scroll_position(),
2185 gpui::Point::new(0., 3.)
2186 );
2187 });
2188}
2189
2190#[gpui::test]
2191async fn test_autoscroll(cx: &mut TestAppContext) {
2192 init_test(cx, |_| {});
2193 let mut cx = EditorTestContext::new(cx).await;
2194
2195 let line_height = cx.update_editor(|editor, window, cx| {
2196 editor.set_vertical_scroll_margin(2, cx);
2197 editor
2198 .style()
2199 .unwrap()
2200 .text
2201 .line_height_in_pixels(window.rem_size())
2202 });
2203 let window = cx.window;
2204 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2205
2206 cx.set_state(
2207 r#"ˇone
2208 two
2209 three
2210 four
2211 five
2212 six
2213 seven
2214 eight
2215 nine
2216 ten
2217 "#,
2218 );
2219 cx.update_editor(|editor, window, cx| {
2220 assert_eq!(
2221 editor.snapshot(window, cx).scroll_position(),
2222 gpui::Point::new(0., 0.0)
2223 );
2224 });
2225
2226 // Add a cursor below the visible area. Since both cursors cannot fit
2227 // on screen, the editor autoscrolls to reveal the newest cursor, and
2228 // allows the vertical scroll margin below that cursor.
2229 cx.update_editor(|editor, window, cx| {
2230 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2231 selections.select_ranges([
2232 Point::new(0, 0)..Point::new(0, 0),
2233 Point::new(6, 0)..Point::new(6, 0),
2234 ]);
2235 })
2236 });
2237 cx.update_editor(|editor, window, cx| {
2238 assert_eq!(
2239 editor.snapshot(window, cx).scroll_position(),
2240 gpui::Point::new(0., 3.0)
2241 );
2242 });
2243
2244 // Move down. The editor cursor scrolls down to track the newest cursor.
2245 cx.update_editor(|editor, window, cx| {
2246 editor.move_down(&Default::default(), window, cx);
2247 });
2248 cx.update_editor(|editor, window, cx| {
2249 assert_eq!(
2250 editor.snapshot(window, cx).scroll_position(),
2251 gpui::Point::new(0., 4.0)
2252 );
2253 });
2254
2255 // Add a cursor above the visible area. Since both cursors fit on screen,
2256 // the editor scrolls to show both.
2257 cx.update_editor(|editor, window, cx| {
2258 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2259 selections.select_ranges([
2260 Point::new(1, 0)..Point::new(1, 0),
2261 Point::new(6, 0)..Point::new(6, 0),
2262 ]);
2263 })
2264 });
2265 cx.update_editor(|editor, window, cx| {
2266 assert_eq!(
2267 editor.snapshot(window, cx).scroll_position(),
2268 gpui::Point::new(0., 1.0)
2269 );
2270 });
2271}
2272
2273#[gpui::test]
2274async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2275 init_test(cx, |_| {});
2276 let mut cx = EditorTestContext::new(cx).await;
2277
2278 let line_height = cx.editor(|editor, window, _cx| {
2279 editor
2280 .style()
2281 .unwrap()
2282 .text
2283 .line_height_in_pixels(window.rem_size())
2284 });
2285 let window = cx.window;
2286 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2287 cx.set_state(
2288 &r#"
2289 ˇone
2290 two
2291 threeˇ
2292 four
2293 five
2294 six
2295 seven
2296 eight
2297 nine
2298 ten
2299 "#
2300 .unindent(),
2301 );
2302
2303 cx.update_editor(|editor, window, cx| {
2304 editor.move_page_down(&MovePageDown::default(), window, cx)
2305 });
2306 cx.assert_editor_state(
2307 &r#"
2308 one
2309 two
2310 three
2311 ˇfour
2312 five
2313 sixˇ
2314 seven
2315 eight
2316 nine
2317 ten
2318 "#
2319 .unindent(),
2320 );
2321
2322 cx.update_editor(|editor, window, cx| {
2323 editor.move_page_down(&MovePageDown::default(), window, cx)
2324 });
2325 cx.assert_editor_state(
2326 &r#"
2327 one
2328 two
2329 three
2330 four
2331 five
2332 six
2333 ˇseven
2334 eight
2335 nineˇ
2336 ten
2337 "#
2338 .unindent(),
2339 );
2340
2341 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2342 cx.assert_editor_state(
2343 &r#"
2344 one
2345 two
2346 three
2347 ˇfour
2348 five
2349 sixˇ
2350 seven
2351 eight
2352 nine
2353 ten
2354 "#
2355 .unindent(),
2356 );
2357
2358 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2359 cx.assert_editor_state(
2360 &r#"
2361 ˇone
2362 two
2363 threeˇ
2364 four
2365 five
2366 six
2367 seven
2368 eight
2369 nine
2370 ten
2371 "#
2372 .unindent(),
2373 );
2374
2375 // Test select collapsing
2376 cx.update_editor(|editor, window, cx| {
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 editor.move_page_down(&MovePageDown::default(), window, cx);
2380 });
2381 cx.assert_editor_state(
2382 &r#"
2383 one
2384 two
2385 three
2386 four
2387 five
2388 six
2389 seven
2390 eight
2391 nine
2392 ˇten
2393 ˇ"#
2394 .unindent(),
2395 );
2396}
2397
2398#[gpui::test]
2399async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2400 init_test(cx, |_| {});
2401 let mut cx = EditorTestContext::new(cx).await;
2402 cx.set_state("one «two threeˇ» four");
2403 cx.update_editor(|editor, window, cx| {
2404 editor.delete_to_beginning_of_line(
2405 &DeleteToBeginningOfLine {
2406 stop_at_indent: false,
2407 },
2408 window,
2409 cx,
2410 );
2411 assert_eq!(editor.text(cx), " four");
2412 });
2413}
2414
2415#[gpui::test]
2416fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2417 init_test(cx, |_| {});
2418
2419 let editor = cx.add_window(|window, cx| {
2420 let buffer = MultiBuffer::build_simple("one two three four", cx);
2421 build_editor(buffer.clone(), window, cx)
2422 });
2423
2424 _ = editor.update(cx, |editor, window, cx| {
2425 editor.change_selections(None, window, cx, |s| {
2426 s.select_display_ranges([
2427 // an empty selection - the preceding word fragment is deleted
2428 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2429 // characters selected - they are deleted
2430 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2431 ])
2432 });
2433 editor.delete_to_previous_word_start(
2434 &DeleteToPreviousWordStart {
2435 ignore_newlines: false,
2436 },
2437 window,
2438 cx,
2439 );
2440 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2441 });
2442
2443 _ = editor.update(cx, |editor, window, cx| {
2444 editor.change_selections(None, window, cx, |s| {
2445 s.select_display_ranges([
2446 // an empty selection - the following word fragment is deleted
2447 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2448 // characters selected - they are deleted
2449 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2450 ])
2451 });
2452 editor.delete_to_next_word_end(
2453 &DeleteToNextWordEnd {
2454 ignore_newlines: false,
2455 },
2456 window,
2457 cx,
2458 );
2459 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2460 });
2461}
2462
2463#[gpui::test]
2464fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2465 init_test(cx, |_| {});
2466
2467 let editor = cx.add_window(|window, cx| {
2468 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2469 build_editor(buffer.clone(), window, cx)
2470 });
2471 let del_to_prev_word_start = DeleteToPreviousWordStart {
2472 ignore_newlines: false,
2473 };
2474 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2475 ignore_newlines: true,
2476 };
2477
2478 _ = editor.update(cx, |editor, window, cx| {
2479 editor.change_selections(None, window, cx, |s| {
2480 s.select_display_ranges([
2481 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2482 ])
2483 });
2484 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2485 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2486 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2487 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2488 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2489 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2490 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2491 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2492 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2493 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2494 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2495 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2496 });
2497}
2498
2499#[gpui::test]
2500fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2501 init_test(cx, |_| {});
2502
2503 let editor = cx.add_window(|window, cx| {
2504 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2505 build_editor(buffer.clone(), window, cx)
2506 });
2507 let del_to_next_word_end = DeleteToNextWordEnd {
2508 ignore_newlines: false,
2509 };
2510 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2511 ignore_newlines: true,
2512 };
2513
2514 _ = editor.update(cx, |editor, window, cx| {
2515 editor.change_selections(None, window, cx, |s| {
2516 s.select_display_ranges([
2517 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2518 ])
2519 });
2520 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2521 assert_eq!(
2522 editor.buffer.read(cx).read(cx).text(),
2523 "one\n two\nthree\n four"
2524 );
2525 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2526 assert_eq!(
2527 editor.buffer.read(cx).read(cx).text(),
2528 "\n two\nthree\n four"
2529 );
2530 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2531 assert_eq!(
2532 editor.buffer.read(cx).read(cx).text(),
2533 "two\nthree\n four"
2534 );
2535 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2536 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2537 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2538 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2539 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2540 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2541 });
2542}
2543
2544#[gpui::test]
2545fn test_newline(cx: &mut TestAppContext) {
2546 init_test(cx, |_| {});
2547
2548 let editor = cx.add_window(|window, cx| {
2549 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2550 build_editor(buffer.clone(), window, cx)
2551 });
2552
2553 _ = editor.update(cx, |editor, window, cx| {
2554 editor.change_selections(None, window, cx, |s| {
2555 s.select_display_ranges([
2556 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2557 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2558 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2559 ])
2560 });
2561
2562 editor.newline(&Newline, window, cx);
2563 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2564 });
2565}
2566
2567#[gpui::test]
2568fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|window, cx| {
2572 let buffer = MultiBuffer::build_simple(
2573 "
2574 a
2575 b(
2576 X
2577 )
2578 c(
2579 X
2580 )
2581 "
2582 .unindent()
2583 .as_str(),
2584 cx,
2585 );
2586 let mut editor = build_editor(buffer.clone(), window, cx);
2587 editor.change_selections(None, window, cx, |s| {
2588 s.select_ranges([
2589 Point::new(2, 4)..Point::new(2, 5),
2590 Point::new(5, 4)..Point::new(5, 5),
2591 ])
2592 });
2593 editor
2594 });
2595
2596 _ = editor.update(cx, |editor, window, cx| {
2597 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2598 editor.buffer.update(cx, |buffer, cx| {
2599 buffer.edit(
2600 [
2601 (Point::new(1, 2)..Point::new(3, 0), ""),
2602 (Point::new(4, 2)..Point::new(6, 0), ""),
2603 ],
2604 None,
2605 cx,
2606 );
2607 assert_eq!(
2608 buffer.read(cx).text(),
2609 "
2610 a
2611 b()
2612 c()
2613 "
2614 .unindent()
2615 );
2616 });
2617 assert_eq!(
2618 editor.selections.ranges(cx),
2619 &[
2620 Point::new(1, 2)..Point::new(1, 2),
2621 Point::new(2, 2)..Point::new(2, 2),
2622 ],
2623 );
2624
2625 editor.newline(&Newline, window, cx);
2626 assert_eq!(
2627 editor.text(cx),
2628 "
2629 a
2630 b(
2631 )
2632 c(
2633 )
2634 "
2635 .unindent()
2636 );
2637
2638 // The selections are moved after the inserted newlines
2639 assert_eq!(
2640 editor.selections.ranges(cx),
2641 &[
2642 Point::new(2, 0)..Point::new(2, 0),
2643 Point::new(4, 0)..Point::new(4, 0),
2644 ],
2645 );
2646 });
2647}
2648
2649#[gpui::test]
2650async fn test_newline_above(cx: &mut TestAppContext) {
2651 init_test(cx, |settings| {
2652 settings.defaults.tab_size = NonZeroU32::new(4)
2653 });
2654
2655 let language = Arc::new(
2656 Language::new(
2657 LanguageConfig::default(),
2658 Some(tree_sitter_rust::LANGUAGE.into()),
2659 )
2660 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2661 .unwrap(),
2662 );
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2666 cx.set_state(indoc! {"
2667 const a: ˇA = (
2668 (ˇ
2669 «const_functionˇ»(ˇ),
2670 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2671 )ˇ
2672 ˇ);ˇ
2673 "});
2674
2675 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2676 cx.assert_editor_state(indoc! {"
2677 ˇ
2678 const a: A = (
2679 ˇ
2680 (
2681 ˇ
2682 ˇ
2683 const_function(),
2684 ˇ
2685 ˇ
2686 ˇ
2687 ˇ
2688 something_else,
2689 ˇ
2690 )
2691 ˇ
2692 ˇ
2693 );
2694 "});
2695}
2696
2697#[gpui::test]
2698async fn test_newline_below(cx: &mut TestAppContext) {
2699 init_test(cx, |settings| {
2700 settings.defaults.tab_size = NonZeroU32::new(4)
2701 });
2702
2703 let language = Arc::new(
2704 Language::new(
2705 LanguageConfig::default(),
2706 Some(tree_sitter_rust::LANGUAGE.into()),
2707 )
2708 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2709 .unwrap(),
2710 );
2711
2712 let mut cx = EditorTestContext::new(cx).await;
2713 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2714 cx.set_state(indoc! {"
2715 const a: ˇA = (
2716 (ˇ
2717 «const_functionˇ»(ˇ),
2718 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2719 )ˇ
2720 ˇ);ˇ
2721 "});
2722
2723 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2724 cx.assert_editor_state(indoc! {"
2725 const a: A = (
2726 ˇ
2727 (
2728 ˇ
2729 const_function(),
2730 ˇ
2731 ˇ
2732 something_else,
2733 ˇ
2734 ˇ
2735 ˇ
2736 ˇ
2737 )
2738 ˇ
2739 );
2740 ˇ
2741 ˇ
2742 "});
2743}
2744
2745#[gpui::test]
2746async fn test_newline_comments(cx: &mut TestAppContext) {
2747 init_test(cx, |settings| {
2748 settings.defaults.tab_size = NonZeroU32::new(4)
2749 });
2750
2751 let language = Arc::new(Language::new(
2752 LanguageConfig {
2753 line_comments: vec!["//".into()],
2754 ..LanguageConfig::default()
2755 },
2756 None,
2757 ));
2758 {
2759 let mut cx = EditorTestContext::new(cx).await;
2760 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2761 cx.set_state(indoc! {"
2762 // Fooˇ
2763 "});
2764
2765 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2766 cx.assert_editor_state(indoc! {"
2767 // Foo
2768 //ˇ
2769 "});
2770 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2771 cx.set_state(indoc! {"
2772 ˇ// Foo
2773 "});
2774 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2775 cx.assert_editor_state(indoc! {"
2776
2777 ˇ// Foo
2778 "});
2779 }
2780 // Ensure that comment continuations can be disabled.
2781 update_test_language_settings(cx, |settings| {
2782 settings.defaults.extend_comment_on_newline = Some(false);
2783 });
2784 let mut cx = EditorTestContext::new(cx).await;
2785 cx.set_state(indoc! {"
2786 // Fooˇ
2787 "});
2788 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2789 cx.assert_editor_state(indoc! {"
2790 // Foo
2791 ˇ
2792 "});
2793}
2794
2795#[gpui::test]
2796fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2797 init_test(cx, |_| {});
2798
2799 let editor = cx.add_window(|window, cx| {
2800 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2801 let mut editor = build_editor(buffer.clone(), window, cx);
2802 editor.change_selections(None, window, cx, |s| {
2803 s.select_ranges([3..4, 11..12, 19..20])
2804 });
2805 editor
2806 });
2807
2808 _ = editor.update(cx, |editor, window, cx| {
2809 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2810 editor.buffer.update(cx, |buffer, cx| {
2811 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2812 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2813 });
2814 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2815
2816 editor.insert("Z", window, cx);
2817 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2818
2819 // The selections are moved after the inserted characters
2820 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2821 });
2822}
2823
2824#[gpui::test]
2825async fn test_tab(cx: &mut TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.tab_size = NonZeroU32::new(3)
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831 cx.set_state(indoc! {"
2832 ˇabˇc
2833 ˇ🏀ˇ🏀ˇefg
2834 dˇ
2835 "});
2836 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2837 cx.assert_editor_state(indoc! {"
2838 ˇab ˇc
2839 ˇ🏀 ˇ🏀 ˇefg
2840 d ˇ
2841 "});
2842
2843 cx.set_state(indoc! {"
2844 a
2845 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2846 "});
2847 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2848 cx.assert_editor_state(indoc! {"
2849 a
2850 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2851 "});
2852}
2853
2854#[gpui::test]
2855async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2856 init_test(cx, |_| {});
2857
2858 let mut cx = EditorTestContext::new(cx).await;
2859 let language = Arc::new(
2860 Language::new(
2861 LanguageConfig::default(),
2862 Some(tree_sitter_rust::LANGUAGE.into()),
2863 )
2864 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2865 .unwrap(),
2866 );
2867 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2868
2869 // cursors that are already at the suggested indent level insert
2870 // a soft tab. cursors that are to the left of the suggested indent
2871 // auto-indent their line.
2872 cx.set_state(indoc! {"
2873 ˇ
2874 const a: B = (
2875 c(
2876 d(
2877 ˇ
2878 )
2879 ˇ
2880 ˇ )
2881 );
2882 "});
2883 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2884 cx.assert_editor_state(indoc! {"
2885 ˇ
2886 const a: B = (
2887 c(
2888 d(
2889 ˇ
2890 )
2891 ˇ
2892 ˇ)
2893 );
2894 "});
2895
2896 // handle auto-indent when there are multiple cursors on the same line
2897 cx.set_state(indoc! {"
2898 const a: B = (
2899 c(
2900 ˇ ˇ
2901 ˇ )
2902 );
2903 "});
2904 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2905 cx.assert_editor_state(indoc! {"
2906 const a: B = (
2907 c(
2908 ˇ
2909 ˇ)
2910 );
2911 "});
2912}
2913
2914#[gpui::test]
2915async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2916 init_test(cx, |settings| {
2917 settings.defaults.tab_size = NonZeroU32::new(4)
2918 });
2919
2920 let language = Arc::new(
2921 Language::new(
2922 LanguageConfig::default(),
2923 Some(tree_sitter_rust::LANGUAGE.into()),
2924 )
2925 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2926 .unwrap(),
2927 );
2928
2929 let mut cx = EditorTestContext::new(cx).await;
2930 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2931 cx.set_state(indoc! {"
2932 fn a() {
2933 if b {
2934 \t ˇc
2935 }
2936 }
2937 "});
2938
2939 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2940 cx.assert_editor_state(indoc! {"
2941 fn a() {
2942 if b {
2943 ˇc
2944 }
2945 }
2946 "});
2947}
2948
2949#[gpui::test]
2950async fn test_indent_outdent(cx: &mut TestAppContext) {
2951 init_test(cx, |settings| {
2952 settings.defaults.tab_size = NonZeroU32::new(4);
2953 });
2954
2955 let mut cx = EditorTestContext::new(cx).await;
2956
2957 cx.set_state(indoc! {"
2958 «oneˇ» «twoˇ»
2959 three
2960 four
2961 "});
2962 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2963 cx.assert_editor_state(indoc! {"
2964 «oneˇ» «twoˇ»
2965 three
2966 four
2967 "});
2968
2969 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2970 cx.assert_editor_state(indoc! {"
2971 «oneˇ» «twoˇ»
2972 three
2973 four
2974 "});
2975
2976 // select across line ending
2977 cx.set_state(indoc! {"
2978 one two
2979 t«hree
2980 ˇ» four
2981 "});
2982 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2983 cx.assert_editor_state(indoc! {"
2984 one two
2985 t«hree
2986 ˇ» four
2987 "});
2988
2989 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2990 cx.assert_editor_state(indoc! {"
2991 one two
2992 t«hree
2993 ˇ» four
2994 "});
2995
2996 // Ensure that indenting/outdenting works when the cursor is at column 0.
2997 cx.set_state(indoc! {"
2998 one two
2999 ˇthree
3000 four
3001 "});
3002 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3003 cx.assert_editor_state(indoc! {"
3004 one two
3005 ˇthree
3006 four
3007 "});
3008
3009 cx.set_state(indoc! {"
3010 one two
3011 ˇ three
3012 four
3013 "});
3014 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3015 cx.assert_editor_state(indoc! {"
3016 one two
3017 ˇthree
3018 four
3019 "});
3020}
3021
3022#[gpui::test]
3023async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3024 init_test(cx, |settings| {
3025 settings.defaults.hard_tabs = Some(true);
3026 });
3027
3028 let mut cx = EditorTestContext::new(cx).await;
3029
3030 // select two ranges on one line
3031 cx.set_state(indoc! {"
3032 «oneˇ» «twoˇ»
3033 three
3034 four
3035 "});
3036 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3037 cx.assert_editor_state(indoc! {"
3038 \t«oneˇ» «twoˇ»
3039 three
3040 four
3041 "});
3042 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3043 cx.assert_editor_state(indoc! {"
3044 \t\t«oneˇ» «twoˇ»
3045 three
3046 four
3047 "});
3048 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3049 cx.assert_editor_state(indoc! {"
3050 \t«oneˇ» «twoˇ»
3051 three
3052 four
3053 "});
3054 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3055 cx.assert_editor_state(indoc! {"
3056 «oneˇ» «twoˇ»
3057 three
3058 four
3059 "});
3060
3061 // select across a line ending
3062 cx.set_state(indoc! {"
3063 one two
3064 t«hree
3065 ˇ»four
3066 "});
3067 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3068 cx.assert_editor_state(indoc! {"
3069 one two
3070 \tt«hree
3071 ˇ»four
3072 "});
3073 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3074 cx.assert_editor_state(indoc! {"
3075 one two
3076 \t\tt«hree
3077 ˇ»four
3078 "});
3079 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3080 cx.assert_editor_state(indoc! {"
3081 one two
3082 \tt«hree
3083 ˇ»four
3084 "});
3085 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3086 cx.assert_editor_state(indoc! {"
3087 one two
3088 t«hree
3089 ˇ»four
3090 "});
3091
3092 // Ensure that indenting/outdenting works when the cursor is at column 0.
3093 cx.set_state(indoc! {"
3094 one two
3095 ˇthree
3096 four
3097 "});
3098 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3099 cx.assert_editor_state(indoc! {"
3100 one two
3101 ˇthree
3102 four
3103 "});
3104 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3105 cx.assert_editor_state(indoc! {"
3106 one two
3107 \tˇthree
3108 four
3109 "});
3110 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3111 cx.assert_editor_state(indoc! {"
3112 one two
3113 ˇthree
3114 four
3115 "});
3116}
3117
3118#[gpui::test]
3119fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3120 init_test(cx, |settings| {
3121 settings.languages.extend([
3122 (
3123 "TOML".into(),
3124 LanguageSettingsContent {
3125 tab_size: NonZeroU32::new(2),
3126 ..Default::default()
3127 },
3128 ),
3129 (
3130 "Rust".into(),
3131 LanguageSettingsContent {
3132 tab_size: NonZeroU32::new(4),
3133 ..Default::default()
3134 },
3135 ),
3136 ]);
3137 });
3138
3139 let toml_language = Arc::new(Language::new(
3140 LanguageConfig {
3141 name: "TOML".into(),
3142 ..Default::default()
3143 },
3144 None,
3145 ));
3146 let rust_language = Arc::new(Language::new(
3147 LanguageConfig {
3148 name: "Rust".into(),
3149 ..Default::default()
3150 },
3151 None,
3152 ));
3153
3154 let toml_buffer =
3155 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3156 let rust_buffer =
3157 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3158 let multibuffer = cx.new(|cx| {
3159 let mut multibuffer = MultiBuffer::new(ReadWrite);
3160 multibuffer.push_excerpts(
3161 toml_buffer.clone(),
3162 [ExcerptRange {
3163 context: Point::new(0, 0)..Point::new(2, 0),
3164 primary: None,
3165 }],
3166 cx,
3167 );
3168 multibuffer.push_excerpts(
3169 rust_buffer.clone(),
3170 [ExcerptRange {
3171 context: Point::new(0, 0)..Point::new(1, 0),
3172 primary: None,
3173 }],
3174 cx,
3175 );
3176 multibuffer
3177 });
3178
3179 cx.add_window(|window, cx| {
3180 let mut editor = build_editor(multibuffer, window, cx);
3181
3182 assert_eq!(
3183 editor.text(cx),
3184 indoc! {"
3185 a = 1
3186 b = 2
3187
3188 const c: usize = 3;
3189 "}
3190 );
3191
3192 select_ranges(
3193 &mut editor,
3194 indoc! {"
3195 «aˇ» = 1
3196 b = 2
3197
3198 «const c:ˇ» usize = 3;
3199 "},
3200 window,
3201 cx,
3202 );
3203
3204 editor.tab(&Tab, window, cx);
3205 assert_text_with_selections(
3206 &mut editor,
3207 indoc! {"
3208 «aˇ» = 1
3209 b = 2
3210
3211 «const c:ˇ» usize = 3;
3212 "},
3213 cx,
3214 );
3215 editor.backtab(&Backtab, window, cx);
3216 assert_text_with_selections(
3217 &mut editor,
3218 indoc! {"
3219 «aˇ» = 1
3220 b = 2
3221
3222 «const c:ˇ» usize = 3;
3223 "},
3224 cx,
3225 );
3226
3227 editor
3228 });
3229}
3230
3231#[gpui::test]
3232async fn test_backspace(cx: &mut TestAppContext) {
3233 init_test(cx, |_| {});
3234
3235 let mut cx = EditorTestContext::new(cx).await;
3236
3237 // Basic backspace
3238 cx.set_state(indoc! {"
3239 onˇe two three
3240 fou«rˇ» five six
3241 seven «ˇeight nine
3242 »ten
3243 "});
3244 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3245 cx.assert_editor_state(indoc! {"
3246 oˇe two three
3247 fouˇ five six
3248 seven ˇten
3249 "});
3250
3251 // Test backspace inside and around indents
3252 cx.set_state(indoc! {"
3253 zero
3254 ˇone
3255 ˇtwo
3256 ˇ ˇ ˇ three
3257 ˇ ˇ four
3258 "});
3259 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3260 cx.assert_editor_state(indoc! {"
3261 zero
3262 ˇone
3263 ˇtwo
3264 ˇ threeˇ four
3265 "});
3266
3267 // Test backspace with line_mode set to true
3268 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3269 cx.set_state(indoc! {"
3270 The ˇquick ˇbrown
3271 fox jumps over
3272 the lazy dog
3273 ˇThe qu«ick bˇ»rown"});
3274 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3275 cx.assert_editor_state(indoc! {"
3276 ˇfox jumps over
3277 the lazy dogˇ"});
3278}
3279
3280#[gpui::test]
3281async fn test_delete(cx: &mut TestAppContext) {
3282 init_test(cx, |_| {});
3283
3284 let mut cx = EditorTestContext::new(cx).await;
3285 cx.set_state(indoc! {"
3286 onˇe two three
3287 fou«rˇ» five six
3288 seven «ˇeight nine
3289 »ten
3290 "});
3291 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3292 cx.assert_editor_state(indoc! {"
3293 onˇ two three
3294 fouˇ five six
3295 seven ˇten
3296 "});
3297
3298 // Test backspace with line_mode set to true
3299 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3300 cx.set_state(indoc! {"
3301 The ˇquick ˇbrown
3302 fox «ˇjum»ps over
3303 the lazy dog
3304 ˇThe qu«ick bˇ»rown"});
3305 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3306 cx.assert_editor_state("ˇthe lazy dogˇ");
3307}
3308
3309#[gpui::test]
3310fn test_delete_line(cx: &mut TestAppContext) {
3311 init_test(cx, |_| {});
3312
3313 let editor = cx.add_window(|window, cx| {
3314 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3315 build_editor(buffer, window, cx)
3316 });
3317 _ = editor.update(cx, |editor, window, cx| {
3318 editor.change_selections(None, window, cx, |s| {
3319 s.select_display_ranges([
3320 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3321 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3322 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3323 ])
3324 });
3325 editor.delete_line(&DeleteLine, window, cx);
3326 assert_eq!(editor.display_text(cx), "ghi");
3327 assert_eq!(
3328 editor.selections.display_ranges(cx),
3329 vec![
3330 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3331 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3332 ]
3333 );
3334 });
3335
3336 let editor = cx.add_window(|window, cx| {
3337 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3338 build_editor(buffer, window, cx)
3339 });
3340 _ = editor.update(cx, |editor, window, cx| {
3341 editor.change_selections(None, window, cx, |s| {
3342 s.select_display_ranges([
3343 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3344 ])
3345 });
3346 editor.delete_line(&DeleteLine, window, cx);
3347 assert_eq!(editor.display_text(cx), "ghi\n");
3348 assert_eq!(
3349 editor.selections.display_ranges(cx),
3350 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3351 );
3352 });
3353}
3354
3355#[gpui::test]
3356fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3357 init_test(cx, |_| {});
3358
3359 cx.add_window(|window, cx| {
3360 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3361 let mut editor = build_editor(buffer.clone(), window, cx);
3362 let buffer = buffer.read(cx).as_singleton().unwrap();
3363
3364 assert_eq!(
3365 editor.selections.ranges::<Point>(cx),
3366 &[Point::new(0, 0)..Point::new(0, 0)]
3367 );
3368
3369 // When on single line, replace newline at end by space
3370 editor.join_lines(&JoinLines, window, cx);
3371 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3372 assert_eq!(
3373 editor.selections.ranges::<Point>(cx),
3374 &[Point::new(0, 3)..Point::new(0, 3)]
3375 );
3376
3377 // When multiple lines are selected, remove newlines that are spanned by the selection
3378 editor.change_selections(None, window, cx, |s| {
3379 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3380 });
3381 editor.join_lines(&JoinLines, window, cx);
3382 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3383 assert_eq!(
3384 editor.selections.ranges::<Point>(cx),
3385 &[Point::new(0, 11)..Point::new(0, 11)]
3386 );
3387
3388 // Undo should be transactional
3389 editor.undo(&Undo, window, cx);
3390 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3391 assert_eq!(
3392 editor.selections.ranges::<Point>(cx),
3393 &[Point::new(0, 5)..Point::new(2, 2)]
3394 );
3395
3396 // When joining an empty line don't insert a space
3397 editor.change_selections(None, window, cx, |s| {
3398 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3399 });
3400 editor.join_lines(&JoinLines, window, cx);
3401 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3402 assert_eq!(
3403 editor.selections.ranges::<Point>(cx),
3404 [Point::new(2, 3)..Point::new(2, 3)]
3405 );
3406
3407 // We can remove trailing newlines
3408 editor.join_lines(&JoinLines, window, cx);
3409 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3410 assert_eq!(
3411 editor.selections.ranges::<Point>(cx),
3412 [Point::new(2, 3)..Point::new(2, 3)]
3413 );
3414
3415 // We don't blow up on the last line
3416 editor.join_lines(&JoinLines, window, cx);
3417 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3418 assert_eq!(
3419 editor.selections.ranges::<Point>(cx),
3420 [Point::new(2, 3)..Point::new(2, 3)]
3421 );
3422
3423 // reset to test indentation
3424 editor.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 [
3427 (Point::new(1, 0)..Point::new(1, 2), " "),
3428 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3429 ],
3430 None,
3431 cx,
3432 )
3433 });
3434
3435 // We remove any leading spaces
3436 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3437 editor.change_selections(None, window, cx, |s| {
3438 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3439 });
3440 editor.join_lines(&JoinLines, window, cx);
3441 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3442
3443 // We don't insert a space for a line containing only spaces
3444 editor.join_lines(&JoinLines, window, cx);
3445 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3446
3447 // We ignore any leading tabs
3448 editor.join_lines(&JoinLines, window, cx);
3449 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3450
3451 editor
3452 });
3453}
3454
3455#[gpui::test]
3456fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 cx.add_window(|window, cx| {
3460 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3461 let mut editor = build_editor(buffer.clone(), window, cx);
3462 let buffer = buffer.read(cx).as_singleton().unwrap();
3463
3464 editor.change_selections(None, window, cx, |s| {
3465 s.select_ranges([
3466 Point::new(0, 2)..Point::new(1, 1),
3467 Point::new(1, 2)..Point::new(1, 2),
3468 Point::new(3, 1)..Point::new(3, 2),
3469 ])
3470 });
3471
3472 editor.join_lines(&JoinLines, window, cx);
3473 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3474
3475 assert_eq!(
3476 editor.selections.ranges::<Point>(cx),
3477 [
3478 Point::new(0, 7)..Point::new(0, 7),
3479 Point::new(1, 3)..Point::new(1, 3)
3480 ]
3481 );
3482 editor
3483 });
3484}
3485
3486#[gpui::test]
3487async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3488 init_test(cx, |_| {});
3489
3490 let mut cx = EditorTestContext::new(cx).await;
3491
3492 let diff_base = r#"
3493 Line 0
3494 Line 1
3495 Line 2
3496 Line 3
3497 "#
3498 .unindent();
3499
3500 cx.set_state(
3501 &r#"
3502 ˇLine 0
3503 Line 1
3504 Line 2
3505 Line 3
3506 "#
3507 .unindent(),
3508 );
3509
3510 cx.set_head_text(&diff_base);
3511 executor.run_until_parked();
3512
3513 // Join lines
3514 cx.update_editor(|editor, window, cx| {
3515 editor.join_lines(&JoinLines, window, cx);
3516 });
3517 executor.run_until_parked();
3518
3519 cx.assert_editor_state(
3520 &r#"
3521 Line 0ˇ Line 1
3522 Line 2
3523 Line 3
3524 "#
3525 .unindent(),
3526 );
3527 // Join again
3528 cx.update_editor(|editor, window, cx| {
3529 editor.join_lines(&JoinLines, window, cx);
3530 });
3531 executor.run_until_parked();
3532
3533 cx.assert_editor_state(
3534 &r#"
3535 Line 0 Line 1ˇ Line 2
3536 Line 3
3537 "#
3538 .unindent(),
3539 );
3540}
3541
3542#[gpui::test]
3543async fn test_custom_newlines_cause_no_false_positive_diffs(
3544 executor: BackgroundExecutor,
3545 cx: &mut TestAppContext,
3546) {
3547 init_test(cx, |_| {});
3548 let mut cx = EditorTestContext::new(cx).await;
3549 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3550 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3551 executor.run_until_parked();
3552
3553 cx.update_editor(|editor, window, cx| {
3554 let snapshot = editor.snapshot(window, cx);
3555 assert_eq!(
3556 snapshot
3557 .buffer_snapshot
3558 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3559 .collect::<Vec<_>>(),
3560 Vec::new(),
3561 "Should not have any diffs for files with custom newlines"
3562 );
3563 });
3564}
3565
3566#[gpui::test]
3567async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let mut cx = EditorTestContext::new(cx).await;
3571
3572 // Test sort_lines_case_insensitive()
3573 cx.set_state(indoc! {"
3574 «z
3575 y
3576 x
3577 Z
3578 Y
3579 Xˇ»
3580 "});
3581 cx.update_editor(|e, window, cx| {
3582 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3583 });
3584 cx.assert_editor_state(indoc! {"
3585 «x
3586 X
3587 y
3588 Y
3589 z
3590 Zˇ»
3591 "});
3592
3593 // Test reverse_lines()
3594 cx.set_state(indoc! {"
3595 «5
3596 4
3597 3
3598 2
3599 1ˇ»
3600 "});
3601 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3602 cx.assert_editor_state(indoc! {"
3603 «1
3604 2
3605 3
3606 4
3607 5ˇ»
3608 "});
3609
3610 // Skip testing shuffle_line()
3611
3612 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3613 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3614
3615 // Don't manipulate when cursor is on single line, but expand the selection
3616 cx.set_state(indoc! {"
3617 ddˇdd
3618 ccc
3619 bb
3620 a
3621 "});
3622 cx.update_editor(|e, window, cx| {
3623 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3624 });
3625 cx.assert_editor_state(indoc! {"
3626 «ddddˇ»
3627 ccc
3628 bb
3629 a
3630 "});
3631
3632 // Basic manipulate case
3633 // Start selection moves to column 0
3634 // End of selection shrinks to fit shorter line
3635 cx.set_state(indoc! {"
3636 dd«d
3637 ccc
3638 bb
3639 aaaaaˇ»
3640 "});
3641 cx.update_editor(|e, window, cx| {
3642 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3643 });
3644 cx.assert_editor_state(indoc! {"
3645 «aaaaa
3646 bb
3647 ccc
3648 dddˇ»
3649 "});
3650
3651 // Manipulate case with newlines
3652 cx.set_state(indoc! {"
3653 dd«d
3654 ccc
3655
3656 bb
3657 aaaaa
3658
3659 ˇ»
3660 "});
3661 cx.update_editor(|e, window, cx| {
3662 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3663 });
3664 cx.assert_editor_state(indoc! {"
3665 «
3666
3667 aaaaa
3668 bb
3669 ccc
3670 dddˇ»
3671
3672 "});
3673
3674 // Adding new line
3675 cx.set_state(indoc! {"
3676 aa«a
3677 bbˇ»b
3678 "});
3679 cx.update_editor(|e, window, cx| {
3680 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3681 });
3682 cx.assert_editor_state(indoc! {"
3683 «aaa
3684 bbb
3685 added_lineˇ»
3686 "});
3687
3688 // Removing line
3689 cx.set_state(indoc! {"
3690 aa«a
3691 bbbˇ»
3692 "});
3693 cx.update_editor(|e, window, cx| {
3694 e.manipulate_lines(window, cx, |lines| {
3695 lines.pop();
3696 })
3697 });
3698 cx.assert_editor_state(indoc! {"
3699 «aaaˇ»
3700 "});
3701
3702 // Removing all lines
3703 cx.set_state(indoc! {"
3704 aa«a
3705 bbbˇ»
3706 "});
3707 cx.update_editor(|e, window, cx| {
3708 e.manipulate_lines(window, cx, |lines| {
3709 lines.drain(..);
3710 })
3711 });
3712 cx.assert_editor_state(indoc! {"
3713 ˇ
3714 "});
3715}
3716
3717#[gpui::test]
3718async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3719 init_test(cx, |_| {});
3720
3721 let mut cx = EditorTestContext::new(cx).await;
3722
3723 // Consider continuous selection as single selection
3724 cx.set_state(indoc! {"
3725 Aaa«aa
3726 cˇ»c«c
3727 bb
3728 aaaˇ»aa
3729 "});
3730 cx.update_editor(|e, window, cx| {
3731 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3732 });
3733 cx.assert_editor_state(indoc! {"
3734 «Aaaaa
3735 ccc
3736 bb
3737 aaaaaˇ»
3738 "});
3739
3740 cx.set_state(indoc! {"
3741 Aaa«aa
3742 cˇ»c«c
3743 bb
3744 aaaˇ»aa
3745 "});
3746 cx.update_editor(|e, window, cx| {
3747 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3748 });
3749 cx.assert_editor_state(indoc! {"
3750 «Aaaaa
3751 ccc
3752 bbˇ»
3753 "});
3754
3755 // Consider non continuous selection as distinct dedup operations
3756 cx.set_state(indoc! {"
3757 «aaaaa
3758 bb
3759 aaaaa
3760 aaaaaˇ»
3761
3762 aaa«aaˇ»
3763 "});
3764 cx.update_editor(|e, window, cx| {
3765 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3766 });
3767 cx.assert_editor_state(indoc! {"
3768 «aaaaa
3769 bbˇ»
3770
3771 «aaaaaˇ»
3772 "});
3773}
3774
3775#[gpui::test]
3776async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3777 init_test(cx, |_| {});
3778
3779 let mut cx = EditorTestContext::new(cx).await;
3780
3781 cx.set_state(indoc! {"
3782 «Aaa
3783 aAa
3784 Aaaˇ»
3785 "});
3786 cx.update_editor(|e, window, cx| {
3787 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3788 });
3789 cx.assert_editor_state(indoc! {"
3790 «Aaa
3791 aAaˇ»
3792 "});
3793
3794 cx.set_state(indoc! {"
3795 «Aaa
3796 aAa
3797 aaAˇ»
3798 "});
3799 cx.update_editor(|e, window, cx| {
3800 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3801 });
3802 cx.assert_editor_state(indoc! {"
3803 «Aaaˇ»
3804 "});
3805}
3806
3807#[gpui::test]
3808async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3809 init_test(cx, |_| {});
3810
3811 let mut cx = EditorTestContext::new(cx).await;
3812
3813 // Manipulate with multiple selections on a single line
3814 cx.set_state(indoc! {"
3815 dd«dd
3816 cˇ»c«c
3817 bb
3818 aaaˇ»aa
3819 "});
3820 cx.update_editor(|e, window, cx| {
3821 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3822 });
3823 cx.assert_editor_state(indoc! {"
3824 «aaaaa
3825 bb
3826 ccc
3827 ddddˇ»
3828 "});
3829
3830 // Manipulate with multiple disjoin selections
3831 cx.set_state(indoc! {"
3832 5«
3833 4
3834 3
3835 2
3836 1ˇ»
3837
3838 dd«dd
3839 ccc
3840 bb
3841 aaaˇ»aa
3842 "});
3843 cx.update_editor(|e, window, cx| {
3844 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3845 });
3846 cx.assert_editor_state(indoc! {"
3847 «1
3848 2
3849 3
3850 4
3851 5ˇ»
3852
3853 «aaaaa
3854 bb
3855 ccc
3856 ddddˇ»
3857 "});
3858
3859 // Adding lines on each selection
3860 cx.set_state(indoc! {"
3861 2«
3862 1ˇ»
3863
3864 bb«bb
3865 aaaˇ»aa
3866 "});
3867 cx.update_editor(|e, window, cx| {
3868 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3869 });
3870 cx.assert_editor_state(indoc! {"
3871 «2
3872 1
3873 added lineˇ»
3874
3875 «bbbb
3876 aaaaa
3877 added lineˇ»
3878 "});
3879
3880 // Removing lines on each selection
3881 cx.set_state(indoc! {"
3882 2«
3883 1ˇ»
3884
3885 bb«bb
3886 aaaˇ»aa
3887 "});
3888 cx.update_editor(|e, window, cx| {
3889 e.manipulate_lines(window, cx, |lines| {
3890 lines.pop();
3891 })
3892 });
3893 cx.assert_editor_state(indoc! {"
3894 «2ˇ»
3895
3896 «bbbbˇ»
3897 "});
3898}
3899
3900#[gpui::test]
3901async fn test_manipulate_text(cx: &mut TestAppContext) {
3902 init_test(cx, |_| {});
3903
3904 let mut cx = EditorTestContext::new(cx).await;
3905
3906 // Test convert_to_upper_case()
3907 cx.set_state(indoc! {"
3908 «hello worldˇ»
3909 "});
3910 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3911 cx.assert_editor_state(indoc! {"
3912 «HELLO WORLDˇ»
3913 "});
3914
3915 // Test convert_to_lower_case()
3916 cx.set_state(indoc! {"
3917 «HELLO WORLDˇ»
3918 "});
3919 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3920 cx.assert_editor_state(indoc! {"
3921 «hello worldˇ»
3922 "});
3923
3924 // Test multiple line, single selection case
3925 cx.set_state(indoc! {"
3926 «The quick brown
3927 fox jumps over
3928 the lazy dogˇ»
3929 "});
3930 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3931 cx.assert_editor_state(indoc! {"
3932 «The Quick Brown
3933 Fox Jumps Over
3934 The Lazy Dogˇ»
3935 "});
3936
3937 // Test multiple line, single selection case
3938 cx.set_state(indoc! {"
3939 «The quick brown
3940 fox jumps over
3941 the lazy dogˇ»
3942 "});
3943 cx.update_editor(|e, window, cx| {
3944 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3945 });
3946 cx.assert_editor_state(indoc! {"
3947 «TheQuickBrown
3948 FoxJumpsOver
3949 TheLazyDogˇ»
3950 "});
3951
3952 // From here on out, test more complex cases of manipulate_text()
3953
3954 // Test no selection case - should affect words cursors are in
3955 // Cursor at beginning, middle, and end of word
3956 cx.set_state(indoc! {"
3957 ˇhello big beauˇtiful worldˇ
3958 "});
3959 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3960 cx.assert_editor_state(indoc! {"
3961 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3962 "});
3963
3964 // Test multiple selections on a single line and across multiple lines
3965 cx.set_state(indoc! {"
3966 «Theˇ» quick «brown
3967 foxˇ» jumps «overˇ»
3968 the «lazyˇ» dog
3969 "});
3970 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3971 cx.assert_editor_state(indoc! {"
3972 «THEˇ» quick «BROWN
3973 FOXˇ» jumps «OVERˇ»
3974 the «LAZYˇ» dog
3975 "});
3976
3977 // Test case where text length grows
3978 cx.set_state(indoc! {"
3979 «tschüߡ»
3980 "});
3981 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3982 cx.assert_editor_state(indoc! {"
3983 «TSCHÜSSˇ»
3984 "});
3985
3986 // Test to make sure we don't crash when text shrinks
3987 cx.set_state(indoc! {"
3988 aaa_bbbˇ
3989 "});
3990 cx.update_editor(|e, window, cx| {
3991 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3992 });
3993 cx.assert_editor_state(indoc! {"
3994 «aaaBbbˇ»
3995 "});
3996
3997 // Test to make sure we all aware of the fact that each word can grow and shrink
3998 // Final selections should be aware of this fact
3999 cx.set_state(indoc! {"
4000 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4001 "});
4002 cx.update_editor(|e, window, cx| {
4003 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4004 });
4005 cx.assert_editor_state(indoc! {"
4006 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4007 "});
4008
4009 cx.set_state(indoc! {"
4010 «hElLo, WoRld!ˇ»
4011 "});
4012 cx.update_editor(|e, window, cx| {
4013 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4014 });
4015 cx.assert_editor_state(indoc! {"
4016 «HeLlO, wOrLD!ˇ»
4017 "});
4018}
4019
4020#[gpui::test]
4021fn test_duplicate_line(cx: &mut TestAppContext) {
4022 init_test(cx, |_| {});
4023
4024 let editor = cx.add_window(|window, cx| {
4025 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4026 build_editor(buffer, window, cx)
4027 });
4028 _ = editor.update(cx, |editor, window, cx| {
4029 editor.change_selections(None, window, cx, |s| {
4030 s.select_display_ranges([
4031 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4032 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4033 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4034 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4035 ])
4036 });
4037 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4038 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4039 assert_eq!(
4040 editor.selections.display_ranges(cx),
4041 vec![
4042 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4043 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4044 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4045 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4046 ]
4047 );
4048 });
4049
4050 let editor = cx.add_window(|window, cx| {
4051 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4052 build_editor(buffer, window, cx)
4053 });
4054 _ = editor.update(cx, |editor, window, cx| {
4055 editor.change_selections(None, window, cx, |s| {
4056 s.select_display_ranges([
4057 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4058 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4059 ])
4060 });
4061 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4062 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4063 assert_eq!(
4064 editor.selections.display_ranges(cx),
4065 vec![
4066 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4067 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4068 ]
4069 );
4070 });
4071
4072 // With `move_upwards` the selections stay in place, except for
4073 // the lines inserted above them
4074 let editor = cx.add_window(|window, cx| {
4075 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4076 build_editor(buffer, window, cx)
4077 });
4078 _ = editor.update(cx, |editor, window, cx| {
4079 editor.change_selections(None, window, cx, |s| {
4080 s.select_display_ranges([
4081 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4082 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4083 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4084 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4085 ])
4086 });
4087 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4088 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4089 assert_eq!(
4090 editor.selections.display_ranges(cx),
4091 vec![
4092 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4093 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4094 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4095 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4096 ]
4097 );
4098 });
4099
4100 let editor = cx.add_window(|window, cx| {
4101 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4102 build_editor(buffer, window, cx)
4103 });
4104 _ = editor.update(cx, |editor, window, cx| {
4105 editor.change_selections(None, window, cx, |s| {
4106 s.select_display_ranges([
4107 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4108 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4109 ])
4110 });
4111 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4112 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4113 assert_eq!(
4114 editor.selections.display_ranges(cx),
4115 vec![
4116 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4117 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4118 ]
4119 );
4120 });
4121
4122 let editor = cx.add_window(|window, cx| {
4123 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4124 build_editor(buffer, window, cx)
4125 });
4126 _ = editor.update(cx, |editor, window, cx| {
4127 editor.change_selections(None, window, cx, |s| {
4128 s.select_display_ranges([
4129 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4130 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4131 ])
4132 });
4133 editor.duplicate_selection(&DuplicateSelection, window, cx);
4134 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4135 assert_eq!(
4136 editor.selections.display_ranges(cx),
4137 vec![
4138 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4139 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4140 ]
4141 );
4142 });
4143}
4144
4145#[gpui::test]
4146fn test_move_line_up_down(cx: &mut TestAppContext) {
4147 init_test(cx, |_| {});
4148
4149 let editor = cx.add_window(|window, cx| {
4150 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4151 build_editor(buffer, window, cx)
4152 });
4153 _ = editor.update(cx, |editor, window, cx| {
4154 editor.fold_creases(
4155 vec![
4156 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4158 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4159 ],
4160 true,
4161 window,
4162 cx,
4163 );
4164 editor.change_selections(None, window, cx, |s| {
4165 s.select_display_ranges([
4166 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4167 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4168 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4169 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4170 ])
4171 });
4172 assert_eq!(
4173 editor.display_text(cx),
4174 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4175 );
4176
4177 editor.move_line_up(&MoveLineUp, window, cx);
4178 assert_eq!(
4179 editor.display_text(cx),
4180 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4181 );
4182 assert_eq!(
4183 editor.selections.display_ranges(cx),
4184 vec![
4185 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4186 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4187 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4188 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4189 ]
4190 );
4191 });
4192
4193 _ = editor.update(cx, |editor, window, cx| {
4194 editor.move_line_down(&MoveLineDown, window, cx);
4195 assert_eq!(
4196 editor.display_text(cx),
4197 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4198 );
4199 assert_eq!(
4200 editor.selections.display_ranges(cx),
4201 vec![
4202 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4203 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4204 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4206 ]
4207 );
4208 });
4209
4210 _ = editor.update(cx, |editor, window, cx| {
4211 editor.move_line_down(&MoveLineDown, window, cx);
4212 assert_eq!(
4213 editor.display_text(cx),
4214 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4215 );
4216 assert_eq!(
4217 editor.selections.display_ranges(cx),
4218 vec![
4219 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4220 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4221 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4222 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4223 ]
4224 );
4225 });
4226
4227 _ = editor.update(cx, |editor, window, cx| {
4228 editor.move_line_up(&MoveLineUp, window, cx);
4229 assert_eq!(
4230 editor.display_text(cx),
4231 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4232 );
4233 assert_eq!(
4234 editor.selections.display_ranges(cx),
4235 vec![
4236 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4237 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4238 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4239 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4240 ]
4241 );
4242 });
4243}
4244
4245#[gpui::test]
4246fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4247 init_test(cx, |_| {});
4248
4249 let editor = cx.add_window(|window, cx| {
4250 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4251 build_editor(buffer, window, cx)
4252 });
4253 _ = editor.update(cx, |editor, window, cx| {
4254 let snapshot = editor.buffer.read(cx).snapshot(cx);
4255 editor.insert_blocks(
4256 [BlockProperties {
4257 style: BlockStyle::Fixed,
4258 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4259 height: 1,
4260 render: Arc::new(|_| div().into_any()),
4261 priority: 0,
4262 }],
4263 Some(Autoscroll::fit()),
4264 cx,
4265 );
4266 editor.change_selections(None, window, cx, |s| {
4267 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4268 });
4269 editor.move_line_down(&MoveLineDown, window, cx);
4270 });
4271}
4272
4273#[gpui::test]
4274async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4275 init_test(cx, |_| {});
4276
4277 let mut cx = EditorTestContext::new(cx).await;
4278 cx.set_state(
4279 &"
4280 ˇzero
4281 one
4282 two
4283 three
4284 four
4285 five
4286 "
4287 .unindent(),
4288 );
4289
4290 // Create a four-line block that replaces three lines of text.
4291 cx.update_editor(|editor, window, cx| {
4292 let snapshot = editor.snapshot(window, cx);
4293 let snapshot = &snapshot.buffer_snapshot;
4294 let placement = BlockPlacement::Replace(
4295 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4296 );
4297 editor.insert_blocks(
4298 [BlockProperties {
4299 placement,
4300 height: 4,
4301 style: BlockStyle::Sticky,
4302 render: Arc::new(|_| gpui::div().into_any_element()),
4303 priority: 0,
4304 }],
4305 None,
4306 cx,
4307 );
4308 });
4309
4310 // Move down so that the cursor touches the block.
4311 cx.update_editor(|editor, window, cx| {
4312 editor.move_down(&Default::default(), window, cx);
4313 });
4314 cx.assert_editor_state(
4315 &"
4316 zero
4317 «one
4318 two
4319 threeˇ»
4320 four
4321 five
4322 "
4323 .unindent(),
4324 );
4325
4326 // Move down past the block.
4327 cx.update_editor(|editor, window, cx| {
4328 editor.move_down(&Default::default(), window, cx);
4329 });
4330 cx.assert_editor_state(
4331 &"
4332 zero
4333 one
4334 two
4335 three
4336 ˇfour
4337 five
4338 "
4339 .unindent(),
4340 );
4341}
4342
4343#[gpui::test]
4344fn test_transpose(cx: &mut TestAppContext) {
4345 init_test(cx, |_| {});
4346
4347 _ = cx.add_window(|window, cx| {
4348 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4349 editor.set_style(EditorStyle::default(), window, cx);
4350 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4351 editor.transpose(&Default::default(), window, cx);
4352 assert_eq!(editor.text(cx), "bac");
4353 assert_eq!(editor.selections.ranges(cx), [2..2]);
4354
4355 editor.transpose(&Default::default(), window, cx);
4356 assert_eq!(editor.text(cx), "bca");
4357 assert_eq!(editor.selections.ranges(cx), [3..3]);
4358
4359 editor.transpose(&Default::default(), window, cx);
4360 assert_eq!(editor.text(cx), "bac");
4361 assert_eq!(editor.selections.ranges(cx), [3..3]);
4362
4363 editor
4364 });
4365
4366 _ = cx.add_window(|window, cx| {
4367 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4368 editor.set_style(EditorStyle::default(), window, cx);
4369 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4370 editor.transpose(&Default::default(), window, cx);
4371 assert_eq!(editor.text(cx), "acb\nde");
4372 assert_eq!(editor.selections.ranges(cx), [3..3]);
4373
4374 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4375 editor.transpose(&Default::default(), window, cx);
4376 assert_eq!(editor.text(cx), "acbd\ne");
4377 assert_eq!(editor.selections.ranges(cx), [5..5]);
4378
4379 editor.transpose(&Default::default(), window, cx);
4380 assert_eq!(editor.text(cx), "acbde\n");
4381 assert_eq!(editor.selections.ranges(cx), [6..6]);
4382
4383 editor.transpose(&Default::default(), window, cx);
4384 assert_eq!(editor.text(cx), "acbd\ne");
4385 assert_eq!(editor.selections.ranges(cx), [6..6]);
4386
4387 editor
4388 });
4389
4390 _ = cx.add_window(|window, cx| {
4391 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4392 editor.set_style(EditorStyle::default(), window, cx);
4393 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4394 editor.transpose(&Default::default(), window, cx);
4395 assert_eq!(editor.text(cx), "bacd\ne");
4396 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4397
4398 editor.transpose(&Default::default(), window, cx);
4399 assert_eq!(editor.text(cx), "bcade\n");
4400 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4401
4402 editor.transpose(&Default::default(), window, cx);
4403 assert_eq!(editor.text(cx), "bcda\ne");
4404 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4405
4406 editor.transpose(&Default::default(), window, cx);
4407 assert_eq!(editor.text(cx), "bcade\n");
4408 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4409
4410 editor.transpose(&Default::default(), window, cx);
4411 assert_eq!(editor.text(cx), "bcaed\n");
4412 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4413
4414 editor
4415 });
4416
4417 _ = cx.add_window(|window, cx| {
4418 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4419 editor.set_style(EditorStyle::default(), window, cx);
4420 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4421 editor.transpose(&Default::default(), window, cx);
4422 assert_eq!(editor.text(cx), "🏀🍐✋");
4423 assert_eq!(editor.selections.ranges(cx), [8..8]);
4424
4425 editor.transpose(&Default::default(), window, cx);
4426 assert_eq!(editor.text(cx), "🏀✋🍐");
4427 assert_eq!(editor.selections.ranges(cx), [11..11]);
4428
4429 editor.transpose(&Default::default(), window, cx);
4430 assert_eq!(editor.text(cx), "🏀🍐✋");
4431 assert_eq!(editor.selections.ranges(cx), [11..11]);
4432
4433 editor
4434 });
4435}
4436
4437#[gpui::test]
4438async fn test_rewrap(cx: &mut TestAppContext) {
4439 init_test(cx, |settings| {
4440 settings.languages.extend([
4441 (
4442 "Markdown".into(),
4443 LanguageSettingsContent {
4444 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4445 ..Default::default()
4446 },
4447 ),
4448 (
4449 "Plain Text".into(),
4450 LanguageSettingsContent {
4451 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4452 ..Default::default()
4453 },
4454 ),
4455 ])
4456 });
4457
4458 let mut cx = EditorTestContext::new(cx).await;
4459
4460 let language_with_c_comments = Arc::new(Language::new(
4461 LanguageConfig {
4462 line_comments: vec!["// ".into()],
4463 ..LanguageConfig::default()
4464 },
4465 None,
4466 ));
4467 let language_with_pound_comments = Arc::new(Language::new(
4468 LanguageConfig {
4469 line_comments: vec!["# ".into()],
4470 ..LanguageConfig::default()
4471 },
4472 None,
4473 ));
4474 let markdown_language = Arc::new(Language::new(
4475 LanguageConfig {
4476 name: "Markdown".into(),
4477 ..LanguageConfig::default()
4478 },
4479 None,
4480 ));
4481 let language_with_doc_comments = Arc::new(Language::new(
4482 LanguageConfig {
4483 line_comments: vec!["// ".into(), "/// ".into()],
4484 ..LanguageConfig::default()
4485 },
4486 Some(tree_sitter_rust::LANGUAGE.into()),
4487 ));
4488
4489 let plaintext_language = Arc::new(Language::new(
4490 LanguageConfig {
4491 name: "Plain Text".into(),
4492 ..LanguageConfig::default()
4493 },
4494 None,
4495 ));
4496
4497 assert_rewrap(
4498 indoc! {"
4499 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4500 "},
4501 indoc! {"
4502 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4503 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4504 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4505 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4506 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4507 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4508 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4509 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4510 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4511 // porttitor id. Aliquam id accumsan eros.
4512 "},
4513 language_with_c_comments.clone(),
4514 &mut cx,
4515 );
4516
4517 // Test that rewrapping works inside of a selection
4518 assert_rewrap(
4519 indoc! {"
4520 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4521 "},
4522 indoc! {"
4523 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4524 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4525 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4526 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4527 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4528 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4529 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4530 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4531 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4532 // porttitor id. Aliquam id accumsan eros.ˇ»
4533 "},
4534 language_with_c_comments.clone(),
4535 &mut cx,
4536 );
4537
4538 // Test that cursors that expand to the same region are collapsed.
4539 assert_rewrap(
4540 indoc! {"
4541 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4542 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4543 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4544 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4545 "},
4546 indoc! {"
4547 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4548 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4549 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4550 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4551 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4552 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4553 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4554 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4555 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4556 // porttitor id. Aliquam id accumsan eros.
4557 "},
4558 language_with_c_comments.clone(),
4559 &mut cx,
4560 );
4561
4562 // Test that non-contiguous selections are treated separately.
4563 assert_rewrap(
4564 indoc! {"
4565 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4566 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4567 //
4568 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4569 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4570 "},
4571 indoc! {"
4572 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4573 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4574 // auctor, eu lacinia sapien scelerisque.
4575 //
4576 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4577 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4578 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4579 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4580 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4581 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4582 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4583 "},
4584 language_with_c_comments.clone(),
4585 &mut cx,
4586 );
4587
4588 // Test that different comment prefixes are supported.
4589 assert_rewrap(
4590 indoc! {"
4591 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4592 "},
4593 indoc! {"
4594 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4595 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4596 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4597 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4598 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4599 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4600 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4601 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4602 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4603 # accumsan eros.
4604 "},
4605 language_with_pound_comments.clone(),
4606 &mut cx,
4607 );
4608
4609 // Test that rewrapping is ignored outside of comments in most languages.
4610 assert_rewrap(
4611 indoc! {"
4612 /// Adds two numbers.
4613 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4614 fn add(a: u32, b: u32) -> u32 {
4615 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4616 }
4617 "},
4618 indoc! {"
4619 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4620 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4621 fn add(a: u32, b: u32) -> u32 {
4622 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4623 }
4624 "},
4625 language_with_doc_comments.clone(),
4626 &mut cx,
4627 );
4628
4629 // Test that rewrapping works in Markdown and Plain Text languages.
4630 assert_rewrap(
4631 indoc! {"
4632 # Hello
4633
4634 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4635 "},
4636 indoc! {"
4637 # Hello
4638
4639 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4640 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4641 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4642 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4643 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4644 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4645 Integer sit amet scelerisque nisi.
4646 "},
4647 markdown_language,
4648 &mut cx,
4649 );
4650
4651 assert_rewrap(
4652 indoc! {"
4653 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4654 "},
4655 indoc! {"
4656 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4657 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4658 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4659 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4660 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4661 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4662 Integer sit amet scelerisque nisi.
4663 "},
4664 plaintext_language,
4665 &mut cx,
4666 );
4667
4668 // Test rewrapping unaligned comments in a selection.
4669 assert_rewrap(
4670 indoc! {"
4671 fn foo() {
4672 if true {
4673 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4674 // Praesent semper egestas tellus id dignissim.ˇ»
4675 do_something();
4676 } else {
4677 //
4678 }
4679 }
4680 "},
4681 indoc! {"
4682 fn foo() {
4683 if true {
4684 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4685 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4686 // egestas tellus id dignissim.ˇ»
4687 do_something();
4688 } else {
4689 //
4690 }
4691 }
4692 "},
4693 language_with_doc_comments.clone(),
4694 &mut cx,
4695 );
4696
4697 assert_rewrap(
4698 indoc! {"
4699 fn foo() {
4700 if true {
4701 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4702 // Praesent semper egestas tellus id dignissim.»
4703 do_something();
4704 } else {
4705 //
4706 }
4707
4708 }
4709 "},
4710 indoc! {"
4711 fn foo() {
4712 if true {
4713 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4714 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4715 // egestas tellus id dignissim.»
4716 do_something();
4717 } else {
4718 //
4719 }
4720
4721 }
4722 "},
4723 language_with_doc_comments.clone(),
4724 &mut cx,
4725 );
4726
4727 #[track_caller]
4728 fn assert_rewrap(
4729 unwrapped_text: &str,
4730 wrapped_text: &str,
4731 language: Arc<Language>,
4732 cx: &mut EditorTestContext,
4733 ) {
4734 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4735 cx.set_state(unwrapped_text);
4736 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4737 cx.assert_editor_state(wrapped_text);
4738 }
4739}
4740
4741#[gpui::test]
4742async fn test_hard_wrap(cx: &mut TestAppContext) {
4743 init_test(cx, |_| {});
4744 let mut cx = EditorTestContext::new(cx).await;
4745
4746 cx.update_editor(|editor, _, cx| {
4747 editor.set_hard_wrap(Some(14), cx);
4748 });
4749
4750 cx.set_state(indoc!(
4751 "
4752 one two three ˇ
4753 "
4754 ));
4755 cx.simulate_input("four");
4756 cx.run_until_parked();
4757
4758 cx.assert_editor_state(indoc!(
4759 "
4760 one two three
4761 fourˇ
4762 "
4763 ));
4764}
4765
4766#[gpui::test]
4767async fn test_clipboard(cx: &mut TestAppContext) {
4768 init_test(cx, |_| {});
4769
4770 let mut cx = EditorTestContext::new(cx).await;
4771
4772 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4773 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4774 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4775
4776 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4777 cx.set_state("two ˇfour ˇsix ˇ");
4778 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4779 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4780
4781 // Paste again but with only two cursors. Since the number of cursors doesn't
4782 // match the number of slices in the clipboard, the entire clipboard text
4783 // is pasted at each cursor.
4784 cx.set_state("ˇtwo one✅ four three six five ˇ");
4785 cx.update_editor(|e, window, cx| {
4786 e.handle_input("( ", window, cx);
4787 e.paste(&Paste, window, cx);
4788 e.handle_input(") ", window, cx);
4789 });
4790 cx.assert_editor_state(
4791 &([
4792 "( one✅ ",
4793 "three ",
4794 "five ) ˇtwo one✅ four three six five ( one✅ ",
4795 "three ",
4796 "five ) ˇ",
4797 ]
4798 .join("\n")),
4799 );
4800
4801 // Cut with three selections, one of which is full-line.
4802 cx.set_state(indoc! {"
4803 1«2ˇ»3
4804 4ˇ567
4805 «8ˇ»9"});
4806 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4807 cx.assert_editor_state(indoc! {"
4808 1ˇ3
4809 ˇ9"});
4810
4811 // Paste with three selections, noticing how the copied selection that was full-line
4812 // gets inserted before the second cursor.
4813 cx.set_state(indoc! {"
4814 1ˇ3
4815 9ˇ
4816 «oˇ»ne"});
4817 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4818 cx.assert_editor_state(indoc! {"
4819 12ˇ3
4820 4567
4821 9ˇ
4822 8ˇne"});
4823
4824 // Copy with a single cursor only, which writes the whole line into the clipboard.
4825 cx.set_state(indoc! {"
4826 The quick brown
4827 fox juˇmps over
4828 the lazy dog"});
4829 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4830 assert_eq!(
4831 cx.read_from_clipboard()
4832 .and_then(|item| item.text().as_deref().map(str::to_string)),
4833 Some("fox jumps over\n".to_string())
4834 );
4835
4836 // Paste with three selections, noticing how the copied full-line selection is inserted
4837 // before the empty selections but replaces the selection that is non-empty.
4838 cx.set_state(indoc! {"
4839 Tˇhe quick brown
4840 «foˇ»x jumps over
4841 tˇhe lazy dog"});
4842 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4843 cx.assert_editor_state(indoc! {"
4844 fox jumps over
4845 Tˇhe quick brown
4846 fox jumps over
4847 ˇx jumps over
4848 fox jumps over
4849 tˇhe lazy dog"});
4850}
4851
4852#[gpui::test]
4853async fn test_paste_multiline(cx: &mut TestAppContext) {
4854 init_test(cx, |_| {});
4855
4856 let mut cx = EditorTestContext::new(cx).await;
4857 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4858
4859 // Cut an indented block, without the leading whitespace.
4860 cx.set_state(indoc! {"
4861 const a: B = (
4862 c(),
4863 «d(
4864 e,
4865 f
4866 )ˇ»
4867 );
4868 "});
4869 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4870 cx.assert_editor_state(indoc! {"
4871 const a: B = (
4872 c(),
4873 ˇ
4874 );
4875 "});
4876
4877 // Paste it at the same position.
4878 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4879 cx.assert_editor_state(indoc! {"
4880 const a: B = (
4881 c(),
4882 d(
4883 e,
4884 f
4885 )ˇ
4886 );
4887 "});
4888
4889 // Paste it at a line with a lower indent level.
4890 cx.set_state(indoc! {"
4891 ˇ
4892 const a: B = (
4893 c(),
4894 );
4895 "});
4896 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4897 cx.assert_editor_state(indoc! {"
4898 d(
4899 e,
4900 f
4901 )ˇ
4902 const a: B = (
4903 c(),
4904 );
4905 "});
4906
4907 // Cut an indented block, with the leading whitespace.
4908 cx.set_state(indoc! {"
4909 const a: B = (
4910 c(),
4911 « d(
4912 e,
4913 f
4914 )
4915 ˇ»);
4916 "});
4917 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4918 cx.assert_editor_state(indoc! {"
4919 const a: B = (
4920 c(),
4921 ˇ);
4922 "});
4923
4924 // Paste it at the same position.
4925 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4926 cx.assert_editor_state(indoc! {"
4927 const a: B = (
4928 c(),
4929 d(
4930 e,
4931 f
4932 )
4933 ˇ);
4934 "});
4935
4936 // Paste it at a line with a higher indent level.
4937 cx.set_state(indoc! {"
4938 const a: B = (
4939 c(),
4940 d(
4941 e,
4942 fˇ
4943 )
4944 );
4945 "});
4946 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4947 cx.assert_editor_state(indoc! {"
4948 const a: B = (
4949 c(),
4950 d(
4951 e,
4952 f d(
4953 e,
4954 f
4955 )
4956 ˇ
4957 )
4958 );
4959 "});
4960
4961 // Copy an indented block, starting mid-line
4962 cx.set_state(indoc! {"
4963 const a: B = (
4964 c(),
4965 somethin«g(
4966 e,
4967 f
4968 )ˇ»
4969 );
4970 "});
4971 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4972
4973 // Paste it on a line with a lower indent level
4974 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4975 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4976 cx.assert_editor_state(indoc! {"
4977 const a: B = (
4978 c(),
4979 something(
4980 e,
4981 f
4982 )
4983 );
4984 g(
4985 e,
4986 f
4987 )ˇ"});
4988}
4989
4990#[gpui::test]
4991async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4992 init_test(cx, |_| {});
4993
4994 cx.write_to_clipboard(ClipboardItem::new_string(
4995 " d(\n e\n );\n".into(),
4996 ));
4997
4998 let mut cx = EditorTestContext::new(cx).await;
4999 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5000
5001 cx.set_state(indoc! {"
5002 fn a() {
5003 b();
5004 if c() {
5005 ˇ
5006 }
5007 }
5008 "});
5009
5010 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5011 cx.assert_editor_state(indoc! {"
5012 fn a() {
5013 b();
5014 if c() {
5015 d(
5016 e
5017 );
5018 ˇ
5019 }
5020 }
5021 "});
5022
5023 cx.set_state(indoc! {"
5024 fn a() {
5025 b();
5026 ˇ
5027 }
5028 "});
5029
5030 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5031 cx.assert_editor_state(indoc! {"
5032 fn a() {
5033 b();
5034 d(
5035 e
5036 );
5037 ˇ
5038 }
5039 "});
5040}
5041
5042#[gpui::test]
5043fn test_select_all(cx: &mut TestAppContext) {
5044 init_test(cx, |_| {});
5045
5046 let editor = cx.add_window(|window, cx| {
5047 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5048 build_editor(buffer, window, cx)
5049 });
5050 _ = editor.update(cx, |editor, window, cx| {
5051 editor.select_all(&SelectAll, window, cx);
5052 assert_eq!(
5053 editor.selections.display_ranges(cx),
5054 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5055 );
5056 });
5057}
5058
5059#[gpui::test]
5060fn test_select_line(cx: &mut TestAppContext) {
5061 init_test(cx, |_| {});
5062
5063 let editor = cx.add_window(|window, cx| {
5064 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5065 build_editor(buffer, window, cx)
5066 });
5067 _ = editor.update(cx, |editor, window, cx| {
5068 editor.change_selections(None, window, cx, |s| {
5069 s.select_display_ranges([
5070 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5071 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5072 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5073 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5074 ])
5075 });
5076 editor.select_line(&SelectLine, window, cx);
5077 assert_eq!(
5078 editor.selections.display_ranges(cx),
5079 vec![
5080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5081 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5082 ]
5083 );
5084 });
5085
5086 _ = editor.update(cx, |editor, window, cx| {
5087 editor.select_line(&SelectLine, window, cx);
5088 assert_eq!(
5089 editor.selections.display_ranges(cx),
5090 vec![
5091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5092 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5093 ]
5094 );
5095 });
5096
5097 _ = editor.update(cx, |editor, window, cx| {
5098 editor.select_line(&SelectLine, window, cx);
5099 assert_eq!(
5100 editor.selections.display_ranges(cx),
5101 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5102 );
5103 });
5104}
5105
5106#[gpui::test]
5107async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5108 init_test(cx, |_| {});
5109 let mut cx = EditorTestContext::new(cx).await;
5110
5111 #[track_caller]
5112 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5113 cx.set_state(initial_state);
5114 cx.update_editor(|e, window, cx| {
5115 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5116 });
5117 cx.assert_editor_state(expected_state);
5118 }
5119
5120 // Selection starts and ends at the middle of lines, left-to-right
5121 test(
5122 &mut cx,
5123 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5124 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5125 );
5126 // Same thing, right-to-left
5127 test(
5128 &mut cx,
5129 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5130 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5131 );
5132
5133 // Whole buffer, left-to-right, last line *doesn't* end with newline
5134 test(
5135 &mut cx,
5136 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5137 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5138 );
5139 // Same thing, right-to-left
5140 test(
5141 &mut cx,
5142 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5143 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5144 );
5145
5146 // Whole buffer, left-to-right, last line ends with newline
5147 test(
5148 &mut cx,
5149 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5150 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5151 );
5152 // Same thing, right-to-left
5153 test(
5154 &mut cx,
5155 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5156 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5157 );
5158
5159 // Starts at the end of a line, ends at the start of another
5160 test(
5161 &mut cx,
5162 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5163 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let editor = cx.add_window(|window, cx| {
5172 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5173 build_editor(buffer, window, cx)
5174 });
5175
5176 // setup
5177 _ = editor.update(cx, |editor, window, cx| {
5178 editor.fold_creases(
5179 vec![
5180 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5181 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5182 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5183 ],
5184 true,
5185 window,
5186 cx,
5187 );
5188 assert_eq!(
5189 editor.display_text(cx),
5190 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5191 );
5192 });
5193
5194 _ = editor.update(cx, |editor, window, cx| {
5195 editor.change_selections(None, window, cx, |s| {
5196 s.select_display_ranges([
5197 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5198 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5199 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5200 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5201 ])
5202 });
5203 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5204 assert_eq!(
5205 editor.display_text(cx),
5206 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5207 );
5208 });
5209 EditorTestContext::for_editor(editor, cx)
5210 .await
5211 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5212
5213 _ = editor.update(cx, |editor, window, cx| {
5214 editor.change_selections(None, window, cx, |s| {
5215 s.select_display_ranges([
5216 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5217 ])
5218 });
5219 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5220 assert_eq!(
5221 editor.display_text(cx),
5222 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5223 );
5224 assert_eq!(
5225 editor.selections.display_ranges(cx),
5226 [
5227 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5228 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5229 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5230 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5231 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5232 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5233 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5234 ]
5235 );
5236 });
5237 EditorTestContext::for_editor(editor, cx)
5238 .await
5239 .assert_editor_state(
5240 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249
5250 cx.set_state(indoc!(
5251 r#"abc
5252 defˇghi
5253
5254 jk
5255 nlmo
5256 "#
5257 ));
5258
5259 cx.update_editor(|editor, window, cx| {
5260 editor.add_selection_above(&Default::default(), window, cx);
5261 });
5262
5263 cx.assert_editor_state(indoc!(
5264 r#"abcˇ
5265 defˇghi
5266
5267 jk
5268 nlmo
5269 "#
5270 ));
5271
5272 cx.update_editor(|editor, window, cx| {
5273 editor.add_selection_above(&Default::default(), window, cx);
5274 });
5275
5276 cx.assert_editor_state(indoc!(
5277 r#"abcˇ
5278 defˇghi
5279
5280 jk
5281 nlmo
5282 "#
5283 ));
5284
5285 cx.update_editor(|editor, window, cx| {
5286 editor.add_selection_below(&Default::default(), window, cx);
5287 });
5288
5289 cx.assert_editor_state(indoc!(
5290 r#"abc
5291 defˇghi
5292
5293 jk
5294 nlmo
5295 "#
5296 ));
5297
5298 cx.update_editor(|editor, window, cx| {
5299 editor.undo_selection(&Default::default(), window, cx);
5300 });
5301
5302 cx.assert_editor_state(indoc!(
5303 r#"abcˇ
5304 defˇghi
5305
5306 jk
5307 nlmo
5308 "#
5309 ));
5310
5311 cx.update_editor(|editor, window, cx| {
5312 editor.redo_selection(&Default::default(), window, cx);
5313 });
5314
5315 cx.assert_editor_state(indoc!(
5316 r#"abc
5317 defˇghi
5318
5319 jk
5320 nlmo
5321 "#
5322 ));
5323
5324 cx.update_editor(|editor, window, cx| {
5325 editor.add_selection_below(&Default::default(), window, cx);
5326 });
5327
5328 cx.assert_editor_state(indoc!(
5329 r#"abc
5330 defˇghi
5331
5332 jk
5333 nlmˇo
5334 "#
5335 ));
5336
5337 cx.update_editor(|editor, window, cx| {
5338 editor.add_selection_below(&Default::default(), window, cx);
5339 });
5340
5341 cx.assert_editor_state(indoc!(
5342 r#"abc
5343 defˇghi
5344
5345 jk
5346 nlmˇo
5347 "#
5348 ));
5349
5350 // change selections
5351 cx.set_state(indoc!(
5352 r#"abc
5353 def«ˇg»hi
5354
5355 jk
5356 nlmo
5357 "#
5358 ));
5359
5360 cx.update_editor(|editor, window, cx| {
5361 editor.add_selection_below(&Default::default(), window, cx);
5362 });
5363
5364 cx.assert_editor_state(indoc!(
5365 r#"abc
5366 def«ˇg»hi
5367
5368 jk
5369 nlm«ˇo»
5370 "#
5371 ));
5372
5373 cx.update_editor(|editor, window, cx| {
5374 editor.add_selection_below(&Default::default(), window, cx);
5375 });
5376
5377 cx.assert_editor_state(indoc!(
5378 r#"abc
5379 def«ˇg»hi
5380
5381 jk
5382 nlm«ˇo»
5383 "#
5384 ));
5385
5386 cx.update_editor(|editor, window, cx| {
5387 editor.add_selection_above(&Default::default(), window, cx);
5388 });
5389
5390 cx.assert_editor_state(indoc!(
5391 r#"abc
5392 def«ˇg»hi
5393
5394 jk
5395 nlmo
5396 "#
5397 ));
5398
5399 cx.update_editor(|editor, window, cx| {
5400 editor.add_selection_above(&Default::default(), window, cx);
5401 });
5402
5403 cx.assert_editor_state(indoc!(
5404 r#"abc
5405 def«ˇg»hi
5406
5407 jk
5408 nlmo
5409 "#
5410 ));
5411
5412 // Change selections again
5413 cx.set_state(indoc!(
5414 r#"a«bc
5415 defgˇ»hi
5416
5417 jk
5418 nlmo
5419 "#
5420 ));
5421
5422 cx.update_editor(|editor, window, cx| {
5423 editor.add_selection_below(&Default::default(), window, cx);
5424 });
5425
5426 cx.assert_editor_state(indoc!(
5427 r#"a«bcˇ»
5428 d«efgˇ»hi
5429
5430 j«kˇ»
5431 nlmo
5432 "#
5433 ));
5434
5435 cx.update_editor(|editor, window, cx| {
5436 editor.add_selection_below(&Default::default(), window, cx);
5437 });
5438 cx.assert_editor_state(indoc!(
5439 r#"a«bcˇ»
5440 d«efgˇ»hi
5441
5442 j«kˇ»
5443 n«lmoˇ»
5444 "#
5445 ));
5446 cx.update_editor(|editor, window, cx| {
5447 editor.add_selection_above(&Default::default(), window, cx);
5448 });
5449
5450 cx.assert_editor_state(indoc!(
5451 r#"a«bcˇ»
5452 d«efgˇ»hi
5453
5454 j«kˇ»
5455 nlmo
5456 "#
5457 ));
5458
5459 // Change selections again
5460 cx.set_state(indoc!(
5461 r#"abc
5462 d«ˇefghi
5463
5464 jk
5465 nlm»o
5466 "#
5467 ));
5468
5469 cx.update_editor(|editor, window, cx| {
5470 editor.add_selection_above(&Default::default(), window, cx);
5471 });
5472
5473 cx.assert_editor_state(indoc!(
5474 r#"a«ˇbc»
5475 d«ˇef»ghi
5476
5477 j«ˇk»
5478 n«ˇlm»o
5479 "#
5480 ));
5481
5482 cx.update_editor(|editor, window, cx| {
5483 editor.add_selection_below(&Default::default(), window, cx);
5484 });
5485
5486 cx.assert_editor_state(indoc!(
5487 r#"abc
5488 d«ˇef»ghi
5489
5490 j«ˇk»
5491 n«ˇlm»o
5492 "#
5493 ));
5494}
5495
5496#[gpui::test]
5497async fn test_select_next(cx: &mut TestAppContext) {
5498 init_test(cx, |_| {});
5499
5500 let mut cx = EditorTestContext::new(cx).await;
5501 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5502
5503 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5504 .unwrap();
5505 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5506
5507 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5508 .unwrap();
5509 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5510
5511 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5512 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5513
5514 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5515 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5516
5517 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5518 .unwrap();
5519 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5520
5521 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5522 .unwrap();
5523 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5524}
5525
5526#[gpui::test]
5527async fn test_select_all_matches(cx: &mut TestAppContext) {
5528 init_test(cx, |_| {});
5529
5530 let mut cx = EditorTestContext::new(cx).await;
5531
5532 // Test caret-only selections
5533 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5534 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5535 .unwrap();
5536 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5537
5538 // Test left-to-right selections
5539 cx.set_state("abc\n«abcˇ»\nabc");
5540 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5541 .unwrap();
5542 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5543
5544 // Test right-to-left selections
5545 cx.set_state("abc\n«ˇabc»\nabc");
5546 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5547 .unwrap();
5548 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5549
5550 // Test selecting whitespace with caret selection
5551 cx.set_state("abc\nˇ abc\nabc");
5552 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5553 .unwrap();
5554 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5555
5556 // Test selecting whitespace with left-to-right selection
5557 cx.set_state("abc\n«ˇ »abc\nabc");
5558 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5559 .unwrap();
5560 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5561
5562 // Test no matches with right-to-left selection
5563 cx.set_state("abc\n« ˇ»abc\nabc");
5564 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5565 .unwrap();
5566 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5567}
5568
5569#[gpui::test]
5570async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5571 init_test(cx, |_| {});
5572
5573 let mut cx = EditorTestContext::new(cx).await;
5574 cx.set_state(
5575 r#"let foo = 2;
5576lˇet foo = 2;
5577let fooˇ = 2;
5578let foo = 2;
5579let foo = ˇ2;"#,
5580 );
5581
5582 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5583 .unwrap();
5584 cx.assert_editor_state(
5585 r#"let foo = 2;
5586«letˇ» foo = 2;
5587let «fooˇ» = 2;
5588let foo = 2;
5589let foo = «2ˇ»;"#,
5590 );
5591
5592 // noop for multiple selections with different contents
5593 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5594 .unwrap();
5595 cx.assert_editor_state(
5596 r#"let foo = 2;
5597«letˇ» foo = 2;
5598let «fooˇ» = 2;
5599let foo = 2;
5600let foo = «2ˇ»;"#,
5601 );
5602}
5603
5604#[gpui::test]
5605async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5606 init_test(cx, |_| {});
5607
5608 let mut cx =
5609 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5610
5611 cx.assert_editor_state(indoc! {"
5612 ˇbbb
5613 ccc
5614
5615 bbb
5616 ccc
5617 "});
5618 cx.dispatch_action(SelectPrevious::default());
5619 cx.assert_editor_state(indoc! {"
5620 «bbbˇ»
5621 ccc
5622
5623 bbb
5624 ccc
5625 "});
5626 cx.dispatch_action(SelectPrevious::default());
5627 cx.assert_editor_state(indoc! {"
5628 «bbbˇ»
5629 ccc
5630
5631 «bbbˇ»
5632 ccc
5633 "});
5634}
5635
5636#[gpui::test]
5637async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5638 init_test(cx, |_| {});
5639
5640 let mut cx = EditorTestContext::new(cx).await;
5641 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5642
5643 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5644 .unwrap();
5645 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5646
5647 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5648 .unwrap();
5649 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5650
5651 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5652 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5653
5654 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5655 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5656
5657 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5658 .unwrap();
5659 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5660
5661 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5662 .unwrap();
5663 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5664
5665 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5666 .unwrap();
5667 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5668}
5669
5670#[gpui::test]
5671async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5672 init_test(cx, |_| {});
5673
5674 let mut cx = EditorTestContext::new(cx).await;
5675 cx.set_state("aˇ");
5676
5677 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5678 .unwrap();
5679 cx.assert_editor_state("«aˇ»");
5680 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5681 .unwrap();
5682 cx.assert_editor_state("«aˇ»");
5683}
5684
5685#[gpui::test]
5686async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5687 init_test(cx, |_| {});
5688
5689 let mut cx = EditorTestContext::new(cx).await;
5690 cx.set_state(
5691 r#"let foo = 2;
5692lˇet foo = 2;
5693let fooˇ = 2;
5694let foo = 2;
5695let foo = ˇ2;"#,
5696 );
5697
5698 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5699 .unwrap();
5700 cx.assert_editor_state(
5701 r#"let foo = 2;
5702«letˇ» foo = 2;
5703let «fooˇ» = 2;
5704let foo = 2;
5705let foo = «2ˇ»;"#,
5706 );
5707
5708 // noop for multiple selections with different contents
5709 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5710 .unwrap();
5711 cx.assert_editor_state(
5712 r#"let foo = 2;
5713«letˇ» foo = 2;
5714let «fooˇ» = 2;
5715let foo = 2;
5716let foo = «2ˇ»;"#,
5717 );
5718}
5719
5720#[gpui::test]
5721async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5722 init_test(cx, |_| {});
5723
5724 let mut cx = EditorTestContext::new(cx).await;
5725 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5726
5727 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5728 .unwrap();
5729 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5730
5731 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5732 .unwrap();
5733 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5734
5735 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5736 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5737
5738 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5739 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5740
5741 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5742 .unwrap();
5743 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5744
5745 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5746 .unwrap();
5747 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5748}
5749
5750#[gpui::test]
5751async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5752 init_test(cx, |_| {});
5753
5754 let language = Arc::new(Language::new(
5755 LanguageConfig::default(),
5756 Some(tree_sitter_rust::LANGUAGE.into()),
5757 ));
5758
5759 let text = r#"
5760 use mod1::mod2::{mod3, mod4};
5761
5762 fn fn_1(param1: bool, param2: &str) {
5763 let var1 = "text";
5764 }
5765 "#
5766 .unindent();
5767
5768 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5769 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5770 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5771
5772 editor
5773 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5774 .await;
5775
5776 editor.update_in(cx, |editor, window, cx| {
5777 editor.change_selections(None, window, cx, |s| {
5778 s.select_display_ranges([
5779 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5780 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5781 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5782 ]);
5783 });
5784 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5785 });
5786 editor.update(cx, |editor, cx| {
5787 assert_text_with_selections(
5788 editor,
5789 indoc! {r#"
5790 use mod1::mod2::{mod3, «mod4ˇ»};
5791
5792 fn fn_1«ˇ(param1: bool, param2: &str)» {
5793 let var1 = "«textˇ»";
5794 }
5795 "#},
5796 cx,
5797 );
5798 });
5799
5800 editor.update_in(cx, |editor, window, cx| {
5801 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5802 });
5803 editor.update(cx, |editor, cx| {
5804 assert_text_with_selections(
5805 editor,
5806 indoc! {r#"
5807 use mod1::mod2::«{mod3, mod4}ˇ»;
5808
5809 «ˇfn fn_1(param1: bool, param2: &str) {
5810 let var1 = "text";
5811 }»
5812 "#},
5813 cx,
5814 );
5815 });
5816
5817 editor.update_in(cx, |editor, window, cx| {
5818 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5819 });
5820 assert_eq!(
5821 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5822 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5823 );
5824
5825 // Trying to expand the selected syntax node one more time has no effect.
5826 editor.update_in(cx, |editor, window, cx| {
5827 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5828 });
5829 assert_eq!(
5830 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5831 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5832 );
5833
5834 editor.update_in(cx, |editor, window, cx| {
5835 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5836 });
5837 editor.update(cx, |editor, cx| {
5838 assert_text_with_selections(
5839 editor,
5840 indoc! {r#"
5841 use mod1::mod2::«{mod3, mod4}ˇ»;
5842
5843 «ˇfn fn_1(param1: bool, param2: &str) {
5844 let var1 = "text";
5845 }»
5846 "#},
5847 cx,
5848 );
5849 });
5850
5851 editor.update_in(cx, |editor, window, cx| {
5852 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5853 });
5854 editor.update(cx, |editor, cx| {
5855 assert_text_with_selections(
5856 editor,
5857 indoc! {r#"
5858 use mod1::mod2::{mod3, «mod4ˇ»};
5859
5860 fn fn_1«ˇ(param1: bool, param2: &str)» {
5861 let var1 = "«textˇ»";
5862 }
5863 "#},
5864 cx,
5865 );
5866 });
5867
5868 editor.update_in(cx, |editor, window, cx| {
5869 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5870 });
5871 editor.update(cx, |editor, cx| {
5872 assert_text_with_selections(
5873 editor,
5874 indoc! {r#"
5875 use mod1::mod2::{mod3, mo«ˇ»d4};
5876
5877 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5878 let var1 = "te«ˇ»xt";
5879 }
5880 "#},
5881 cx,
5882 );
5883 });
5884
5885 // Trying to shrink the selected syntax node one more time has no effect.
5886 editor.update_in(cx, |editor, window, cx| {
5887 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5888 });
5889 editor.update_in(cx, |editor, _, cx| {
5890 assert_text_with_selections(
5891 editor,
5892 indoc! {r#"
5893 use mod1::mod2::{mod3, mo«ˇ»d4};
5894
5895 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5896 let var1 = "te«ˇ»xt";
5897 }
5898 "#},
5899 cx,
5900 );
5901 });
5902
5903 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5904 // a fold.
5905 editor.update_in(cx, |editor, window, cx| {
5906 editor.fold_creases(
5907 vec![
5908 Crease::simple(
5909 Point::new(0, 21)..Point::new(0, 24),
5910 FoldPlaceholder::test(),
5911 ),
5912 Crease::simple(
5913 Point::new(3, 20)..Point::new(3, 22),
5914 FoldPlaceholder::test(),
5915 ),
5916 ],
5917 true,
5918 window,
5919 cx,
5920 );
5921 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5922 });
5923 editor.update(cx, |editor, cx| {
5924 assert_text_with_selections(
5925 editor,
5926 indoc! {r#"
5927 use mod1::mod2::«{mod3, mod4}ˇ»;
5928
5929 fn fn_1«ˇ(param1: bool, param2: &str)» {
5930 «let var1 = "text";ˇ»
5931 }
5932 "#},
5933 cx,
5934 );
5935 });
5936}
5937
5938#[gpui::test]
5939async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5940 init_test(cx, |_| {});
5941
5942 let base_text = r#"
5943 impl A {
5944 // this is an uncommitted comment
5945
5946 fn b() {
5947 c();
5948 }
5949
5950 // this is another uncommitted comment
5951
5952 fn d() {
5953 // e
5954 // f
5955 }
5956 }
5957
5958 fn g() {
5959 // h
5960 }
5961 "#
5962 .unindent();
5963
5964 let text = r#"
5965 ˇimpl A {
5966
5967 fn b() {
5968 c();
5969 }
5970
5971 fn d() {
5972 // e
5973 // f
5974 }
5975 }
5976
5977 fn g() {
5978 // h
5979 }
5980 "#
5981 .unindent();
5982
5983 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5984 cx.set_state(&text);
5985 cx.set_head_text(&base_text);
5986 cx.update_editor(|editor, window, cx| {
5987 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5988 });
5989
5990 cx.assert_state_with_diff(
5991 "
5992 ˇimpl A {
5993 - // this is an uncommitted comment
5994
5995 fn b() {
5996 c();
5997 }
5998
5999 - // this is another uncommitted comment
6000 -
6001 fn d() {
6002 // e
6003 // f
6004 }
6005 }
6006
6007 fn g() {
6008 // h
6009 }
6010 "
6011 .unindent(),
6012 );
6013
6014 let expected_display_text = "
6015 impl A {
6016 // this is an uncommitted comment
6017
6018 fn b() {
6019 ⋯
6020 }
6021
6022 // this is another uncommitted comment
6023
6024 fn d() {
6025 ⋯
6026 }
6027 }
6028
6029 fn g() {
6030 ⋯
6031 }
6032 "
6033 .unindent();
6034
6035 cx.update_editor(|editor, window, cx| {
6036 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6037 assert_eq!(editor.display_text(cx), expected_display_text);
6038 });
6039}
6040
6041#[gpui::test]
6042async fn test_autoindent(cx: &mut TestAppContext) {
6043 init_test(cx, |_| {});
6044
6045 let language = Arc::new(
6046 Language::new(
6047 LanguageConfig {
6048 brackets: BracketPairConfig {
6049 pairs: vec![
6050 BracketPair {
6051 start: "{".to_string(),
6052 end: "}".to_string(),
6053 close: false,
6054 surround: false,
6055 newline: true,
6056 },
6057 BracketPair {
6058 start: "(".to_string(),
6059 end: ")".to_string(),
6060 close: false,
6061 surround: false,
6062 newline: true,
6063 },
6064 ],
6065 ..Default::default()
6066 },
6067 ..Default::default()
6068 },
6069 Some(tree_sitter_rust::LANGUAGE.into()),
6070 )
6071 .with_indents_query(
6072 r#"
6073 (_ "(" ")" @end) @indent
6074 (_ "{" "}" @end) @indent
6075 "#,
6076 )
6077 .unwrap(),
6078 );
6079
6080 let text = "fn a() {}";
6081
6082 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6083 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6084 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6085 editor
6086 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6087 .await;
6088
6089 editor.update_in(cx, |editor, window, cx| {
6090 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6091 editor.newline(&Newline, window, cx);
6092 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6093 assert_eq!(
6094 editor.selections.ranges(cx),
6095 &[
6096 Point::new(1, 4)..Point::new(1, 4),
6097 Point::new(3, 4)..Point::new(3, 4),
6098 Point::new(5, 0)..Point::new(5, 0)
6099 ]
6100 );
6101 });
6102}
6103
6104#[gpui::test]
6105async fn test_autoindent_selections(cx: &mut TestAppContext) {
6106 init_test(cx, |_| {});
6107
6108 {
6109 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6110 cx.set_state(indoc! {"
6111 impl A {
6112
6113 fn b() {}
6114
6115 «fn c() {
6116
6117 }ˇ»
6118 }
6119 "});
6120
6121 cx.update_editor(|editor, window, cx| {
6122 editor.autoindent(&Default::default(), window, cx);
6123 });
6124
6125 cx.assert_editor_state(indoc! {"
6126 impl A {
6127
6128 fn b() {}
6129
6130 «fn c() {
6131
6132 }ˇ»
6133 }
6134 "});
6135 }
6136
6137 {
6138 let mut cx = EditorTestContext::new_multibuffer(
6139 cx,
6140 [indoc! { "
6141 impl A {
6142 «
6143 // a
6144 fn b(){}
6145 »
6146 «
6147 }
6148 fn c(){}
6149 »
6150 "}],
6151 );
6152
6153 let buffer = cx.update_editor(|editor, _, cx| {
6154 let buffer = editor.buffer().update(cx, |buffer, _| {
6155 buffer.all_buffers().iter().next().unwrap().clone()
6156 });
6157 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6158 buffer
6159 });
6160
6161 cx.run_until_parked();
6162 cx.update_editor(|editor, window, cx| {
6163 editor.select_all(&Default::default(), window, cx);
6164 editor.autoindent(&Default::default(), window, cx)
6165 });
6166 cx.run_until_parked();
6167
6168 cx.update(|_, cx| {
6169 pretty_assertions::assert_eq!(
6170 buffer.read(cx).text(),
6171 indoc! { "
6172 impl A {
6173
6174 // a
6175 fn b(){}
6176
6177
6178 }
6179 fn c(){}
6180
6181 " }
6182 )
6183 });
6184 }
6185}
6186
6187#[gpui::test]
6188async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6189 init_test(cx, |_| {});
6190
6191 let mut cx = EditorTestContext::new(cx).await;
6192
6193 let language = Arc::new(Language::new(
6194 LanguageConfig {
6195 brackets: BracketPairConfig {
6196 pairs: vec![
6197 BracketPair {
6198 start: "{".to_string(),
6199 end: "}".to_string(),
6200 close: true,
6201 surround: true,
6202 newline: true,
6203 },
6204 BracketPair {
6205 start: "(".to_string(),
6206 end: ")".to_string(),
6207 close: true,
6208 surround: true,
6209 newline: true,
6210 },
6211 BracketPair {
6212 start: "/*".to_string(),
6213 end: " */".to_string(),
6214 close: true,
6215 surround: true,
6216 newline: true,
6217 },
6218 BracketPair {
6219 start: "[".to_string(),
6220 end: "]".to_string(),
6221 close: false,
6222 surround: false,
6223 newline: true,
6224 },
6225 BracketPair {
6226 start: "\"".to_string(),
6227 end: "\"".to_string(),
6228 close: true,
6229 surround: true,
6230 newline: false,
6231 },
6232 BracketPair {
6233 start: "<".to_string(),
6234 end: ">".to_string(),
6235 close: false,
6236 surround: true,
6237 newline: true,
6238 },
6239 ],
6240 ..Default::default()
6241 },
6242 autoclose_before: "})]".to_string(),
6243 ..Default::default()
6244 },
6245 Some(tree_sitter_rust::LANGUAGE.into()),
6246 ));
6247
6248 cx.language_registry().add(language.clone());
6249 cx.update_buffer(|buffer, cx| {
6250 buffer.set_language(Some(language), cx);
6251 });
6252
6253 cx.set_state(
6254 &r#"
6255 🏀ˇ
6256 εˇ
6257 ❤️ˇ
6258 "#
6259 .unindent(),
6260 );
6261
6262 // autoclose multiple nested brackets at multiple cursors
6263 cx.update_editor(|editor, window, cx| {
6264 editor.handle_input("{", window, cx);
6265 editor.handle_input("{", window, cx);
6266 editor.handle_input("{", window, cx);
6267 });
6268 cx.assert_editor_state(
6269 &"
6270 🏀{{{ˇ}}}
6271 ε{{{ˇ}}}
6272 ❤️{{{ˇ}}}
6273 "
6274 .unindent(),
6275 );
6276
6277 // insert a different closing bracket
6278 cx.update_editor(|editor, window, cx| {
6279 editor.handle_input(")", window, cx);
6280 });
6281 cx.assert_editor_state(
6282 &"
6283 🏀{{{)ˇ}}}
6284 ε{{{)ˇ}}}
6285 ❤️{{{)ˇ}}}
6286 "
6287 .unindent(),
6288 );
6289
6290 // skip over the auto-closed brackets when typing a closing bracket
6291 cx.update_editor(|editor, window, cx| {
6292 editor.move_right(&MoveRight, window, cx);
6293 editor.handle_input("}", window, cx);
6294 editor.handle_input("}", window, cx);
6295 editor.handle_input("}", window, cx);
6296 });
6297 cx.assert_editor_state(
6298 &"
6299 🏀{{{)}}}}ˇ
6300 ε{{{)}}}}ˇ
6301 ❤️{{{)}}}}ˇ
6302 "
6303 .unindent(),
6304 );
6305
6306 // autoclose multi-character pairs
6307 cx.set_state(
6308 &"
6309 ˇ
6310 ˇ
6311 "
6312 .unindent(),
6313 );
6314 cx.update_editor(|editor, window, cx| {
6315 editor.handle_input("/", window, cx);
6316 editor.handle_input("*", window, cx);
6317 });
6318 cx.assert_editor_state(
6319 &"
6320 /*ˇ */
6321 /*ˇ */
6322 "
6323 .unindent(),
6324 );
6325
6326 // one cursor autocloses a multi-character pair, one cursor
6327 // does not autoclose.
6328 cx.set_state(
6329 &"
6330 /ˇ
6331 ˇ
6332 "
6333 .unindent(),
6334 );
6335 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6336 cx.assert_editor_state(
6337 &"
6338 /*ˇ */
6339 *ˇ
6340 "
6341 .unindent(),
6342 );
6343
6344 // Don't autoclose if the next character isn't whitespace and isn't
6345 // listed in the language's "autoclose_before" section.
6346 cx.set_state("ˇa b");
6347 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6348 cx.assert_editor_state("{ˇa b");
6349
6350 // Don't autoclose if `close` is false for the bracket pair
6351 cx.set_state("ˇ");
6352 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6353 cx.assert_editor_state("[ˇ");
6354
6355 // Surround with brackets if text is selected
6356 cx.set_state("«aˇ» b");
6357 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6358 cx.assert_editor_state("{«aˇ»} b");
6359
6360 // Autclose pair where the start and end characters are the same
6361 cx.set_state("aˇ");
6362 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6363 cx.assert_editor_state("a\"ˇ\"");
6364 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6365 cx.assert_editor_state("a\"\"ˇ");
6366
6367 // Don't autoclose pair if autoclose is disabled
6368 cx.set_state("ˇ");
6369 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6370 cx.assert_editor_state("<ˇ");
6371
6372 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6373 cx.set_state("«aˇ» b");
6374 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6375 cx.assert_editor_state("<«aˇ»> b");
6376}
6377
6378#[gpui::test]
6379async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6380 init_test(cx, |settings| {
6381 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6382 });
6383
6384 let mut cx = EditorTestContext::new(cx).await;
6385
6386 let language = Arc::new(Language::new(
6387 LanguageConfig {
6388 brackets: BracketPairConfig {
6389 pairs: vec![
6390 BracketPair {
6391 start: "{".to_string(),
6392 end: "}".to_string(),
6393 close: true,
6394 surround: true,
6395 newline: true,
6396 },
6397 BracketPair {
6398 start: "(".to_string(),
6399 end: ")".to_string(),
6400 close: true,
6401 surround: true,
6402 newline: true,
6403 },
6404 BracketPair {
6405 start: "[".to_string(),
6406 end: "]".to_string(),
6407 close: false,
6408 surround: false,
6409 newline: true,
6410 },
6411 ],
6412 ..Default::default()
6413 },
6414 autoclose_before: "})]".to_string(),
6415 ..Default::default()
6416 },
6417 Some(tree_sitter_rust::LANGUAGE.into()),
6418 ));
6419
6420 cx.language_registry().add(language.clone());
6421 cx.update_buffer(|buffer, cx| {
6422 buffer.set_language(Some(language), cx);
6423 });
6424
6425 cx.set_state(
6426 &"
6427 ˇ
6428 ˇ
6429 ˇ
6430 "
6431 .unindent(),
6432 );
6433
6434 // ensure only matching closing brackets are skipped over
6435 cx.update_editor(|editor, window, cx| {
6436 editor.handle_input("}", window, cx);
6437 editor.move_left(&MoveLeft, window, cx);
6438 editor.handle_input(")", window, cx);
6439 editor.move_left(&MoveLeft, window, cx);
6440 });
6441 cx.assert_editor_state(
6442 &"
6443 ˇ)}
6444 ˇ)}
6445 ˇ)}
6446 "
6447 .unindent(),
6448 );
6449
6450 // skip-over closing brackets at multiple cursors
6451 cx.update_editor(|editor, window, cx| {
6452 editor.handle_input(")", window, cx);
6453 editor.handle_input("}", window, cx);
6454 });
6455 cx.assert_editor_state(
6456 &"
6457 )}ˇ
6458 )}ˇ
6459 )}ˇ
6460 "
6461 .unindent(),
6462 );
6463
6464 // ignore non-close brackets
6465 cx.update_editor(|editor, window, cx| {
6466 editor.handle_input("]", window, cx);
6467 editor.move_left(&MoveLeft, window, cx);
6468 editor.handle_input("]", window, cx);
6469 });
6470 cx.assert_editor_state(
6471 &"
6472 )}]ˇ]
6473 )}]ˇ]
6474 )}]ˇ]
6475 "
6476 .unindent(),
6477 );
6478}
6479
6480#[gpui::test]
6481async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6482 init_test(cx, |_| {});
6483
6484 let mut cx = EditorTestContext::new(cx).await;
6485
6486 let html_language = Arc::new(
6487 Language::new(
6488 LanguageConfig {
6489 name: "HTML".into(),
6490 brackets: BracketPairConfig {
6491 pairs: vec![
6492 BracketPair {
6493 start: "<".into(),
6494 end: ">".into(),
6495 close: true,
6496 ..Default::default()
6497 },
6498 BracketPair {
6499 start: "{".into(),
6500 end: "}".into(),
6501 close: true,
6502 ..Default::default()
6503 },
6504 BracketPair {
6505 start: "(".into(),
6506 end: ")".into(),
6507 close: true,
6508 ..Default::default()
6509 },
6510 ],
6511 ..Default::default()
6512 },
6513 autoclose_before: "})]>".into(),
6514 ..Default::default()
6515 },
6516 Some(tree_sitter_html::LANGUAGE.into()),
6517 )
6518 .with_injection_query(
6519 r#"
6520 (script_element
6521 (raw_text) @injection.content
6522 (#set! injection.language "javascript"))
6523 "#,
6524 )
6525 .unwrap(),
6526 );
6527
6528 let javascript_language = Arc::new(Language::new(
6529 LanguageConfig {
6530 name: "JavaScript".into(),
6531 brackets: BracketPairConfig {
6532 pairs: vec![
6533 BracketPair {
6534 start: "/*".into(),
6535 end: " */".into(),
6536 close: true,
6537 ..Default::default()
6538 },
6539 BracketPair {
6540 start: "{".into(),
6541 end: "}".into(),
6542 close: true,
6543 ..Default::default()
6544 },
6545 BracketPair {
6546 start: "(".into(),
6547 end: ")".into(),
6548 close: true,
6549 ..Default::default()
6550 },
6551 ],
6552 ..Default::default()
6553 },
6554 autoclose_before: "})]>".into(),
6555 ..Default::default()
6556 },
6557 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6558 ));
6559
6560 cx.language_registry().add(html_language.clone());
6561 cx.language_registry().add(javascript_language.clone());
6562
6563 cx.update_buffer(|buffer, cx| {
6564 buffer.set_language(Some(html_language), cx);
6565 });
6566
6567 cx.set_state(
6568 &r#"
6569 <body>ˇ
6570 <script>
6571 var x = 1;ˇ
6572 </script>
6573 </body>ˇ
6574 "#
6575 .unindent(),
6576 );
6577
6578 // Precondition: different languages are active at different locations.
6579 cx.update_editor(|editor, window, cx| {
6580 let snapshot = editor.snapshot(window, cx);
6581 let cursors = editor.selections.ranges::<usize>(cx);
6582 let languages = cursors
6583 .iter()
6584 .map(|c| snapshot.language_at(c.start).unwrap().name())
6585 .collect::<Vec<_>>();
6586 assert_eq!(
6587 languages,
6588 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6589 );
6590 });
6591
6592 // Angle brackets autoclose in HTML, but not JavaScript.
6593 cx.update_editor(|editor, window, cx| {
6594 editor.handle_input("<", window, cx);
6595 editor.handle_input("a", window, cx);
6596 });
6597 cx.assert_editor_state(
6598 &r#"
6599 <body><aˇ>
6600 <script>
6601 var x = 1;<aˇ
6602 </script>
6603 </body><aˇ>
6604 "#
6605 .unindent(),
6606 );
6607
6608 // Curly braces and parens autoclose in both HTML and JavaScript.
6609 cx.update_editor(|editor, window, cx| {
6610 editor.handle_input(" b=", window, cx);
6611 editor.handle_input("{", window, cx);
6612 editor.handle_input("c", window, cx);
6613 editor.handle_input("(", window, cx);
6614 });
6615 cx.assert_editor_state(
6616 &r#"
6617 <body><a b={c(ˇ)}>
6618 <script>
6619 var x = 1;<a b={c(ˇ)}
6620 </script>
6621 </body><a b={c(ˇ)}>
6622 "#
6623 .unindent(),
6624 );
6625
6626 // Brackets that were already autoclosed are skipped.
6627 cx.update_editor(|editor, window, cx| {
6628 editor.handle_input(")", window, cx);
6629 editor.handle_input("d", window, cx);
6630 editor.handle_input("}", window, cx);
6631 });
6632 cx.assert_editor_state(
6633 &r#"
6634 <body><a b={c()d}ˇ>
6635 <script>
6636 var x = 1;<a b={c()d}ˇ
6637 </script>
6638 </body><a b={c()d}ˇ>
6639 "#
6640 .unindent(),
6641 );
6642 cx.update_editor(|editor, window, cx| {
6643 editor.handle_input(">", window, cx);
6644 });
6645 cx.assert_editor_state(
6646 &r#"
6647 <body><a b={c()d}>ˇ
6648 <script>
6649 var x = 1;<a b={c()d}>ˇ
6650 </script>
6651 </body><a b={c()d}>ˇ
6652 "#
6653 .unindent(),
6654 );
6655
6656 // Reset
6657 cx.set_state(
6658 &r#"
6659 <body>ˇ
6660 <script>
6661 var x = 1;ˇ
6662 </script>
6663 </body>ˇ
6664 "#
6665 .unindent(),
6666 );
6667
6668 cx.update_editor(|editor, window, cx| {
6669 editor.handle_input("<", window, cx);
6670 });
6671 cx.assert_editor_state(
6672 &r#"
6673 <body><ˇ>
6674 <script>
6675 var x = 1;<ˇ
6676 </script>
6677 </body><ˇ>
6678 "#
6679 .unindent(),
6680 );
6681
6682 // When backspacing, the closing angle brackets are removed.
6683 cx.update_editor(|editor, window, cx| {
6684 editor.backspace(&Backspace, window, cx);
6685 });
6686 cx.assert_editor_state(
6687 &r#"
6688 <body>ˇ
6689 <script>
6690 var x = 1;ˇ
6691 </script>
6692 </body>ˇ
6693 "#
6694 .unindent(),
6695 );
6696
6697 // Block comments autoclose in JavaScript, but not HTML.
6698 cx.update_editor(|editor, window, cx| {
6699 editor.handle_input("/", window, cx);
6700 editor.handle_input("*", window, cx);
6701 });
6702 cx.assert_editor_state(
6703 &r#"
6704 <body>/*ˇ
6705 <script>
6706 var x = 1;/*ˇ */
6707 </script>
6708 </body>/*ˇ
6709 "#
6710 .unindent(),
6711 );
6712}
6713
6714#[gpui::test]
6715async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6716 init_test(cx, |_| {});
6717
6718 let mut cx = EditorTestContext::new(cx).await;
6719
6720 let rust_language = Arc::new(
6721 Language::new(
6722 LanguageConfig {
6723 name: "Rust".into(),
6724 brackets: serde_json::from_value(json!([
6725 { "start": "{", "end": "}", "close": true, "newline": true },
6726 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6727 ]))
6728 .unwrap(),
6729 autoclose_before: "})]>".into(),
6730 ..Default::default()
6731 },
6732 Some(tree_sitter_rust::LANGUAGE.into()),
6733 )
6734 .with_override_query("(string_literal) @string")
6735 .unwrap(),
6736 );
6737
6738 cx.language_registry().add(rust_language.clone());
6739 cx.update_buffer(|buffer, cx| {
6740 buffer.set_language(Some(rust_language), cx);
6741 });
6742
6743 cx.set_state(
6744 &r#"
6745 let x = ˇ
6746 "#
6747 .unindent(),
6748 );
6749
6750 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6751 cx.update_editor(|editor, window, cx| {
6752 editor.handle_input("\"", window, cx);
6753 });
6754 cx.assert_editor_state(
6755 &r#"
6756 let x = "ˇ"
6757 "#
6758 .unindent(),
6759 );
6760
6761 // Inserting another quotation mark. The cursor moves across the existing
6762 // automatically-inserted quotation mark.
6763 cx.update_editor(|editor, window, cx| {
6764 editor.handle_input("\"", window, cx);
6765 });
6766 cx.assert_editor_state(
6767 &r#"
6768 let x = ""ˇ
6769 "#
6770 .unindent(),
6771 );
6772
6773 // Reset
6774 cx.set_state(
6775 &r#"
6776 let x = ˇ
6777 "#
6778 .unindent(),
6779 );
6780
6781 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6782 cx.update_editor(|editor, window, cx| {
6783 editor.handle_input("\"", window, cx);
6784 editor.handle_input(" ", window, cx);
6785 editor.move_left(&Default::default(), window, cx);
6786 editor.handle_input("\\", window, cx);
6787 editor.handle_input("\"", window, cx);
6788 });
6789 cx.assert_editor_state(
6790 &r#"
6791 let x = "\"ˇ "
6792 "#
6793 .unindent(),
6794 );
6795
6796 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6797 // mark. Nothing is inserted.
6798 cx.update_editor(|editor, window, cx| {
6799 editor.move_right(&Default::default(), window, cx);
6800 editor.handle_input("\"", window, cx);
6801 });
6802 cx.assert_editor_state(
6803 &r#"
6804 let x = "\" "ˇ
6805 "#
6806 .unindent(),
6807 );
6808}
6809
6810#[gpui::test]
6811async fn test_surround_with_pair(cx: &mut TestAppContext) {
6812 init_test(cx, |_| {});
6813
6814 let language = Arc::new(Language::new(
6815 LanguageConfig {
6816 brackets: BracketPairConfig {
6817 pairs: vec![
6818 BracketPair {
6819 start: "{".to_string(),
6820 end: "}".to_string(),
6821 close: true,
6822 surround: true,
6823 newline: true,
6824 },
6825 BracketPair {
6826 start: "/* ".to_string(),
6827 end: "*/".to_string(),
6828 close: true,
6829 surround: true,
6830 ..Default::default()
6831 },
6832 ],
6833 ..Default::default()
6834 },
6835 ..Default::default()
6836 },
6837 Some(tree_sitter_rust::LANGUAGE.into()),
6838 ));
6839
6840 let text = r#"
6841 a
6842 b
6843 c
6844 "#
6845 .unindent();
6846
6847 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6848 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6849 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6850 editor
6851 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6852 .await;
6853
6854 editor.update_in(cx, |editor, window, cx| {
6855 editor.change_selections(None, window, cx, |s| {
6856 s.select_display_ranges([
6857 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6859 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6860 ])
6861 });
6862
6863 editor.handle_input("{", window, cx);
6864 editor.handle_input("{", window, cx);
6865 editor.handle_input("{", window, cx);
6866 assert_eq!(
6867 editor.text(cx),
6868 "
6869 {{{a}}}
6870 {{{b}}}
6871 {{{c}}}
6872 "
6873 .unindent()
6874 );
6875 assert_eq!(
6876 editor.selections.display_ranges(cx),
6877 [
6878 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6879 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6880 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6881 ]
6882 );
6883
6884 editor.undo(&Undo, window, cx);
6885 editor.undo(&Undo, window, cx);
6886 editor.undo(&Undo, window, cx);
6887 assert_eq!(
6888 editor.text(cx),
6889 "
6890 a
6891 b
6892 c
6893 "
6894 .unindent()
6895 );
6896 assert_eq!(
6897 editor.selections.display_ranges(cx),
6898 [
6899 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6900 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6901 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6902 ]
6903 );
6904
6905 // Ensure inserting the first character of a multi-byte bracket pair
6906 // doesn't surround the selections with the bracket.
6907 editor.handle_input("/", window, cx);
6908 assert_eq!(
6909 editor.text(cx),
6910 "
6911 /
6912 /
6913 /
6914 "
6915 .unindent()
6916 );
6917 assert_eq!(
6918 editor.selections.display_ranges(cx),
6919 [
6920 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6922 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6923 ]
6924 );
6925
6926 editor.undo(&Undo, window, cx);
6927 assert_eq!(
6928 editor.text(cx),
6929 "
6930 a
6931 b
6932 c
6933 "
6934 .unindent()
6935 );
6936 assert_eq!(
6937 editor.selections.display_ranges(cx),
6938 [
6939 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6940 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6941 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6942 ]
6943 );
6944
6945 // Ensure inserting the last character of a multi-byte bracket pair
6946 // doesn't surround the selections with the bracket.
6947 editor.handle_input("*", window, cx);
6948 assert_eq!(
6949 editor.text(cx),
6950 "
6951 *
6952 *
6953 *
6954 "
6955 .unindent()
6956 );
6957 assert_eq!(
6958 editor.selections.display_ranges(cx),
6959 [
6960 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6961 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6962 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6963 ]
6964 );
6965 });
6966}
6967
6968#[gpui::test]
6969async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6970 init_test(cx, |_| {});
6971
6972 let language = Arc::new(Language::new(
6973 LanguageConfig {
6974 brackets: BracketPairConfig {
6975 pairs: vec![BracketPair {
6976 start: "{".to_string(),
6977 end: "}".to_string(),
6978 close: true,
6979 surround: true,
6980 newline: true,
6981 }],
6982 ..Default::default()
6983 },
6984 autoclose_before: "}".to_string(),
6985 ..Default::default()
6986 },
6987 Some(tree_sitter_rust::LANGUAGE.into()),
6988 ));
6989
6990 let text = r#"
6991 a
6992 b
6993 c
6994 "#
6995 .unindent();
6996
6997 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6998 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6999 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7000 editor
7001 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7002 .await;
7003
7004 editor.update_in(cx, |editor, window, cx| {
7005 editor.change_selections(None, window, cx, |s| {
7006 s.select_ranges([
7007 Point::new(0, 1)..Point::new(0, 1),
7008 Point::new(1, 1)..Point::new(1, 1),
7009 Point::new(2, 1)..Point::new(2, 1),
7010 ])
7011 });
7012
7013 editor.handle_input("{", window, cx);
7014 editor.handle_input("{", window, cx);
7015 editor.handle_input("_", window, cx);
7016 assert_eq!(
7017 editor.text(cx),
7018 "
7019 a{{_}}
7020 b{{_}}
7021 c{{_}}
7022 "
7023 .unindent()
7024 );
7025 assert_eq!(
7026 editor.selections.ranges::<Point>(cx),
7027 [
7028 Point::new(0, 4)..Point::new(0, 4),
7029 Point::new(1, 4)..Point::new(1, 4),
7030 Point::new(2, 4)..Point::new(2, 4)
7031 ]
7032 );
7033
7034 editor.backspace(&Default::default(), window, cx);
7035 editor.backspace(&Default::default(), window, cx);
7036 assert_eq!(
7037 editor.text(cx),
7038 "
7039 a{}
7040 b{}
7041 c{}
7042 "
7043 .unindent()
7044 );
7045 assert_eq!(
7046 editor.selections.ranges::<Point>(cx),
7047 [
7048 Point::new(0, 2)..Point::new(0, 2),
7049 Point::new(1, 2)..Point::new(1, 2),
7050 Point::new(2, 2)..Point::new(2, 2)
7051 ]
7052 );
7053
7054 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7055 assert_eq!(
7056 editor.text(cx),
7057 "
7058 a
7059 b
7060 c
7061 "
7062 .unindent()
7063 );
7064 assert_eq!(
7065 editor.selections.ranges::<Point>(cx),
7066 [
7067 Point::new(0, 1)..Point::new(0, 1),
7068 Point::new(1, 1)..Point::new(1, 1),
7069 Point::new(2, 1)..Point::new(2, 1)
7070 ]
7071 );
7072 });
7073}
7074
7075#[gpui::test]
7076async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7077 init_test(cx, |settings| {
7078 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7079 });
7080
7081 let mut cx = EditorTestContext::new(cx).await;
7082
7083 let language = Arc::new(Language::new(
7084 LanguageConfig {
7085 brackets: BracketPairConfig {
7086 pairs: vec![
7087 BracketPair {
7088 start: "{".to_string(),
7089 end: "}".to_string(),
7090 close: true,
7091 surround: true,
7092 newline: true,
7093 },
7094 BracketPair {
7095 start: "(".to_string(),
7096 end: ")".to_string(),
7097 close: true,
7098 surround: true,
7099 newline: true,
7100 },
7101 BracketPair {
7102 start: "[".to_string(),
7103 end: "]".to_string(),
7104 close: false,
7105 surround: true,
7106 newline: true,
7107 },
7108 ],
7109 ..Default::default()
7110 },
7111 autoclose_before: "})]".to_string(),
7112 ..Default::default()
7113 },
7114 Some(tree_sitter_rust::LANGUAGE.into()),
7115 ));
7116
7117 cx.language_registry().add(language.clone());
7118 cx.update_buffer(|buffer, cx| {
7119 buffer.set_language(Some(language), cx);
7120 });
7121
7122 cx.set_state(
7123 &"
7124 {(ˇ)}
7125 [[ˇ]]
7126 {(ˇ)}
7127 "
7128 .unindent(),
7129 );
7130
7131 cx.update_editor(|editor, window, cx| {
7132 editor.backspace(&Default::default(), window, cx);
7133 editor.backspace(&Default::default(), window, cx);
7134 });
7135
7136 cx.assert_editor_state(
7137 &"
7138 ˇ
7139 ˇ]]
7140 ˇ
7141 "
7142 .unindent(),
7143 );
7144
7145 cx.update_editor(|editor, window, cx| {
7146 editor.handle_input("{", window, cx);
7147 editor.handle_input("{", window, cx);
7148 editor.move_right(&MoveRight, window, cx);
7149 editor.move_right(&MoveRight, window, cx);
7150 editor.move_left(&MoveLeft, window, cx);
7151 editor.move_left(&MoveLeft, window, cx);
7152 editor.backspace(&Default::default(), window, cx);
7153 });
7154
7155 cx.assert_editor_state(
7156 &"
7157 {ˇ}
7158 {ˇ}]]
7159 {ˇ}
7160 "
7161 .unindent(),
7162 );
7163
7164 cx.update_editor(|editor, window, cx| {
7165 editor.backspace(&Default::default(), window, cx);
7166 });
7167
7168 cx.assert_editor_state(
7169 &"
7170 ˇ
7171 ˇ]]
7172 ˇ
7173 "
7174 .unindent(),
7175 );
7176}
7177
7178#[gpui::test]
7179async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7180 init_test(cx, |_| {});
7181
7182 let language = Arc::new(Language::new(
7183 LanguageConfig::default(),
7184 Some(tree_sitter_rust::LANGUAGE.into()),
7185 ));
7186
7187 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7189 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7190 editor
7191 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7192 .await;
7193
7194 editor.update_in(cx, |editor, window, cx| {
7195 editor.set_auto_replace_emoji_shortcode(true);
7196
7197 editor.handle_input("Hello ", window, cx);
7198 editor.handle_input(":wave", window, cx);
7199 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7200
7201 editor.handle_input(":", window, cx);
7202 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7203
7204 editor.handle_input(" :smile", window, cx);
7205 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7206
7207 editor.handle_input(":", window, cx);
7208 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7209
7210 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7211 editor.handle_input(":wave", window, cx);
7212 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7213
7214 editor.handle_input(":", window, cx);
7215 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7216
7217 editor.handle_input(":1", window, cx);
7218 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7219
7220 editor.handle_input(":", window, cx);
7221 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7222
7223 // Ensure shortcode does not get replaced when it is part of a word
7224 editor.handle_input(" Test:wave", window, cx);
7225 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7226
7227 editor.handle_input(":", window, cx);
7228 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7229
7230 editor.set_auto_replace_emoji_shortcode(false);
7231
7232 // Ensure shortcode does not get replaced when auto replace is off
7233 editor.handle_input(" :wave", window, cx);
7234 assert_eq!(
7235 editor.text(cx),
7236 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7237 );
7238
7239 editor.handle_input(":", window, cx);
7240 assert_eq!(
7241 editor.text(cx),
7242 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7243 );
7244 });
7245}
7246
7247#[gpui::test]
7248async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7249 init_test(cx, |_| {});
7250
7251 let (text, insertion_ranges) = marked_text_ranges(
7252 indoc! {"
7253 ˇ
7254 "},
7255 false,
7256 );
7257
7258 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7259 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7260
7261 _ = editor.update_in(cx, |editor, window, cx| {
7262 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7263
7264 editor
7265 .insert_snippet(&insertion_ranges, snippet, window, cx)
7266 .unwrap();
7267
7268 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7269 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7270 assert_eq!(editor.text(cx), expected_text);
7271 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7272 }
7273
7274 assert(
7275 editor,
7276 cx,
7277 indoc! {"
7278 type «» =•
7279 "},
7280 );
7281
7282 assert!(editor.context_menu_visible(), "There should be a matches");
7283 });
7284}
7285
7286#[gpui::test]
7287async fn test_snippets(cx: &mut TestAppContext) {
7288 init_test(cx, |_| {});
7289
7290 let (text, insertion_ranges) = marked_text_ranges(
7291 indoc! {"
7292 a.ˇ b
7293 a.ˇ b
7294 a.ˇ b
7295 "},
7296 false,
7297 );
7298
7299 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7300 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7301
7302 editor.update_in(cx, |editor, window, cx| {
7303 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7304
7305 editor
7306 .insert_snippet(&insertion_ranges, snippet, window, cx)
7307 .unwrap();
7308
7309 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7310 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7311 assert_eq!(editor.text(cx), expected_text);
7312 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7313 }
7314
7315 assert(
7316 editor,
7317 cx,
7318 indoc! {"
7319 a.f(«one», two, «three») b
7320 a.f(«one», two, «three») b
7321 a.f(«one», two, «three») b
7322 "},
7323 );
7324
7325 // Can't move earlier than the first tab stop
7326 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7327 assert(
7328 editor,
7329 cx,
7330 indoc! {"
7331 a.f(«one», two, «three») b
7332 a.f(«one», two, «three») b
7333 a.f(«one», two, «three») b
7334 "},
7335 );
7336
7337 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7338 assert(
7339 editor,
7340 cx,
7341 indoc! {"
7342 a.f(one, «two», three) b
7343 a.f(one, «two», three) b
7344 a.f(one, «two», three) b
7345 "},
7346 );
7347
7348 editor.move_to_prev_snippet_tabstop(window, cx);
7349 assert(
7350 editor,
7351 cx,
7352 indoc! {"
7353 a.f(«one», two, «three») b
7354 a.f(«one», two, «three») b
7355 a.f(«one», two, «three») b
7356 "},
7357 );
7358
7359 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7360 assert(
7361 editor,
7362 cx,
7363 indoc! {"
7364 a.f(one, «two», three) b
7365 a.f(one, «two», three) b
7366 a.f(one, «two», three) b
7367 "},
7368 );
7369 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7370 assert(
7371 editor,
7372 cx,
7373 indoc! {"
7374 a.f(one, two, three)ˇ b
7375 a.f(one, two, three)ˇ b
7376 a.f(one, two, three)ˇ b
7377 "},
7378 );
7379
7380 // As soon as the last tab stop is reached, snippet state is gone
7381 editor.move_to_prev_snippet_tabstop(window, cx);
7382 assert(
7383 editor,
7384 cx,
7385 indoc! {"
7386 a.f(one, two, three)ˇ b
7387 a.f(one, two, three)ˇ b
7388 a.f(one, two, three)ˇ b
7389 "},
7390 );
7391 });
7392}
7393
7394#[gpui::test]
7395async fn test_document_format_during_save(cx: &mut TestAppContext) {
7396 init_test(cx, |_| {});
7397
7398 let fs = FakeFs::new(cx.executor());
7399 fs.insert_file(path!("/file.rs"), Default::default()).await;
7400
7401 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7402
7403 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7404 language_registry.add(rust_lang());
7405 let mut fake_servers = language_registry.register_fake_lsp(
7406 "Rust",
7407 FakeLspAdapter {
7408 capabilities: lsp::ServerCapabilities {
7409 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7410 ..Default::default()
7411 },
7412 ..Default::default()
7413 },
7414 );
7415
7416 let buffer = project
7417 .update(cx, |project, cx| {
7418 project.open_local_buffer(path!("/file.rs"), cx)
7419 })
7420 .await
7421 .unwrap();
7422
7423 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7424 let (editor, cx) = cx.add_window_view(|window, cx| {
7425 build_editor_with_project(project.clone(), buffer, window, cx)
7426 });
7427 editor.update_in(cx, |editor, window, cx| {
7428 editor.set_text("one\ntwo\nthree\n", window, cx)
7429 });
7430 assert!(cx.read(|cx| editor.is_dirty(cx)));
7431
7432 cx.executor().start_waiting();
7433 let fake_server = fake_servers.next().await.unwrap();
7434
7435 let save = editor
7436 .update_in(cx, |editor, window, cx| {
7437 editor.save(true, project.clone(), window, cx)
7438 })
7439 .unwrap();
7440 fake_server
7441 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7442 assert_eq!(
7443 params.text_document.uri,
7444 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7445 );
7446 assert_eq!(params.options.tab_size, 4);
7447 Ok(Some(vec![lsp::TextEdit::new(
7448 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7449 ", ".to_string(),
7450 )]))
7451 })
7452 .next()
7453 .await;
7454 cx.executor().start_waiting();
7455 save.await;
7456
7457 assert_eq!(
7458 editor.update(cx, |editor, cx| editor.text(cx)),
7459 "one, two\nthree\n"
7460 );
7461 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7462
7463 editor.update_in(cx, |editor, window, cx| {
7464 editor.set_text("one\ntwo\nthree\n", window, cx)
7465 });
7466 assert!(cx.read(|cx| editor.is_dirty(cx)));
7467
7468 // Ensure we can still save even if formatting hangs.
7469 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7470 assert_eq!(
7471 params.text_document.uri,
7472 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7473 );
7474 futures::future::pending::<()>().await;
7475 unreachable!()
7476 });
7477 let save = editor
7478 .update_in(cx, |editor, window, cx| {
7479 editor.save(true, project.clone(), window, cx)
7480 })
7481 .unwrap();
7482 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7483 cx.executor().start_waiting();
7484 save.await;
7485 assert_eq!(
7486 editor.update(cx, |editor, cx| editor.text(cx)),
7487 "one\ntwo\nthree\n"
7488 );
7489 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7490
7491 // For non-dirty buffer, no formatting request should be sent
7492 let save = editor
7493 .update_in(cx, |editor, window, cx| {
7494 editor.save(true, project.clone(), window, cx)
7495 })
7496 .unwrap();
7497 let _pending_format_request = fake_server
7498 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7499 panic!("Should not be invoked on non-dirty buffer");
7500 })
7501 .next();
7502 cx.executor().start_waiting();
7503 save.await;
7504
7505 // Set rust language override and assert overridden tabsize is sent to language server
7506 update_test_language_settings(cx, |settings| {
7507 settings.languages.insert(
7508 "Rust".into(),
7509 LanguageSettingsContent {
7510 tab_size: NonZeroU32::new(8),
7511 ..Default::default()
7512 },
7513 );
7514 });
7515
7516 editor.update_in(cx, |editor, window, cx| {
7517 editor.set_text("somehting_new\n", window, cx)
7518 });
7519 assert!(cx.read(|cx| editor.is_dirty(cx)));
7520 let save = editor
7521 .update_in(cx, |editor, window, cx| {
7522 editor.save(true, project.clone(), window, cx)
7523 })
7524 .unwrap();
7525 fake_server
7526 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7527 assert_eq!(
7528 params.text_document.uri,
7529 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7530 );
7531 assert_eq!(params.options.tab_size, 8);
7532 Ok(Some(vec![]))
7533 })
7534 .next()
7535 .await;
7536 cx.executor().start_waiting();
7537 save.await;
7538}
7539
7540#[gpui::test]
7541async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7542 init_test(cx, |_| {});
7543
7544 let cols = 4;
7545 let rows = 10;
7546 let sample_text_1 = sample_text(rows, cols, 'a');
7547 assert_eq!(
7548 sample_text_1,
7549 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7550 );
7551 let sample_text_2 = sample_text(rows, cols, 'l');
7552 assert_eq!(
7553 sample_text_2,
7554 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7555 );
7556 let sample_text_3 = sample_text(rows, cols, 'v');
7557 assert_eq!(
7558 sample_text_3,
7559 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7560 );
7561
7562 let fs = FakeFs::new(cx.executor());
7563 fs.insert_tree(
7564 path!("/a"),
7565 json!({
7566 "main.rs": sample_text_1,
7567 "other.rs": sample_text_2,
7568 "lib.rs": sample_text_3,
7569 }),
7570 )
7571 .await;
7572
7573 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7574 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7575 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7576
7577 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7578 language_registry.add(rust_lang());
7579 let mut fake_servers = language_registry.register_fake_lsp(
7580 "Rust",
7581 FakeLspAdapter {
7582 capabilities: lsp::ServerCapabilities {
7583 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7584 ..Default::default()
7585 },
7586 ..Default::default()
7587 },
7588 );
7589
7590 let worktree = project.update(cx, |project, cx| {
7591 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7592 assert_eq!(worktrees.len(), 1);
7593 worktrees.pop().unwrap()
7594 });
7595 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7596
7597 let buffer_1 = project
7598 .update(cx, |project, cx| {
7599 project.open_buffer((worktree_id, "main.rs"), cx)
7600 })
7601 .await
7602 .unwrap();
7603 let buffer_2 = project
7604 .update(cx, |project, cx| {
7605 project.open_buffer((worktree_id, "other.rs"), cx)
7606 })
7607 .await
7608 .unwrap();
7609 let buffer_3 = project
7610 .update(cx, |project, cx| {
7611 project.open_buffer((worktree_id, "lib.rs"), cx)
7612 })
7613 .await
7614 .unwrap();
7615
7616 let multi_buffer = cx.new(|cx| {
7617 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7618 multi_buffer.push_excerpts(
7619 buffer_1.clone(),
7620 [
7621 ExcerptRange {
7622 context: Point::new(0, 0)..Point::new(3, 0),
7623 primary: None,
7624 },
7625 ExcerptRange {
7626 context: Point::new(5, 0)..Point::new(7, 0),
7627 primary: None,
7628 },
7629 ExcerptRange {
7630 context: Point::new(9, 0)..Point::new(10, 4),
7631 primary: None,
7632 },
7633 ],
7634 cx,
7635 );
7636 multi_buffer.push_excerpts(
7637 buffer_2.clone(),
7638 [
7639 ExcerptRange {
7640 context: Point::new(0, 0)..Point::new(3, 0),
7641 primary: None,
7642 },
7643 ExcerptRange {
7644 context: Point::new(5, 0)..Point::new(7, 0),
7645 primary: None,
7646 },
7647 ExcerptRange {
7648 context: Point::new(9, 0)..Point::new(10, 4),
7649 primary: None,
7650 },
7651 ],
7652 cx,
7653 );
7654 multi_buffer.push_excerpts(
7655 buffer_3.clone(),
7656 [
7657 ExcerptRange {
7658 context: Point::new(0, 0)..Point::new(3, 0),
7659 primary: None,
7660 },
7661 ExcerptRange {
7662 context: Point::new(5, 0)..Point::new(7, 0),
7663 primary: None,
7664 },
7665 ExcerptRange {
7666 context: Point::new(9, 0)..Point::new(10, 4),
7667 primary: None,
7668 },
7669 ],
7670 cx,
7671 );
7672 multi_buffer
7673 });
7674 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7675 Editor::new(
7676 EditorMode::Full,
7677 multi_buffer,
7678 Some(project.clone()),
7679 window,
7680 cx,
7681 )
7682 });
7683
7684 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7685 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7686 s.select_ranges(Some(1..2))
7687 });
7688 editor.insert("|one|two|three|", window, cx);
7689 });
7690 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7691 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7692 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7693 s.select_ranges(Some(60..70))
7694 });
7695 editor.insert("|four|five|six|", window, cx);
7696 });
7697 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7698
7699 // First two buffers should be edited, but not the third one.
7700 assert_eq!(
7701 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7702 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7703 );
7704 buffer_1.update(cx, |buffer, _| {
7705 assert!(buffer.is_dirty());
7706 assert_eq!(
7707 buffer.text(),
7708 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7709 )
7710 });
7711 buffer_2.update(cx, |buffer, _| {
7712 assert!(buffer.is_dirty());
7713 assert_eq!(
7714 buffer.text(),
7715 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7716 )
7717 });
7718 buffer_3.update(cx, |buffer, _| {
7719 assert!(!buffer.is_dirty());
7720 assert_eq!(buffer.text(), sample_text_3,)
7721 });
7722 cx.executor().run_until_parked();
7723
7724 cx.executor().start_waiting();
7725 let save = multi_buffer_editor
7726 .update_in(cx, |editor, window, cx| {
7727 editor.save(true, project.clone(), window, cx)
7728 })
7729 .unwrap();
7730
7731 let fake_server = fake_servers.next().await.unwrap();
7732 fake_server
7733 .server
7734 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7735 Ok(Some(vec![lsp::TextEdit::new(
7736 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7737 format!("[{} formatted]", params.text_document.uri),
7738 )]))
7739 })
7740 .detach();
7741 save.await;
7742
7743 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7744 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7745 assert_eq!(
7746 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7747 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"),
7748 );
7749 buffer_1.update(cx, |buffer, _| {
7750 assert!(!buffer.is_dirty());
7751 assert_eq!(
7752 buffer.text(),
7753 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7754 )
7755 });
7756 buffer_2.update(cx, |buffer, _| {
7757 assert!(!buffer.is_dirty());
7758 assert_eq!(
7759 buffer.text(),
7760 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7761 )
7762 });
7763 buffer_3.update(cx, |buffer, _| {
7764 assert!(!buffer.is_dirty());
7765 assert_eq!(buffer.text(), sample_text_3,)
7766 });
7767}
7768
7769#[gpui::test]
7770async fn test_range_format_during_save(cx: &mut TestAppContext) {
7771 init_test(cx, |_| {});
7772
7773 let fs = FakeFs::new(cx.executor());
7774 fs.insert_file(path!("/file.rs"), Default::default()).await;
7775
7776 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7777
7778 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7779 language_registry.add(rust_lang());
7780 let mut fake_servers = language_registry.register_fake_lsp(
7781 "Rust",
7782 FakeLspAdapter {
7783 capabilities: lsp::ServerCapabilities {
7784 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7785 ..Default::default()
7786 },
7787 ..Default::default()
7788 },
7789 );
7790
7791 let buffer = project
7792 .update(cx, |project, cx| {
7793 project.open_local_buffer(path!("/file.rs"), cx)
7794 })
7795 .await
7796 .unwrap();
7797
7798 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7799 let (editor, cx) = cx.add_window_view(|window, cx| {
7800 build_editor_with_project(project.clone(), buffer, window, cx)
7801 });
7802 editor.update_in(cx, |editor, window, cx| {
7803 editor.set_text("one\ntwo\nthree\n", window, cx)
7804 });
7805 assert!(cx.read(|cx| editor.is_dirty(cx)));
7806
7807 cx.executor().start_waiting();
7808 let fake_server = fake_servers.next().await.unwrap();
7809
7810 let save = editor
7811 .update_in(cx, |editor, window, cx| {
7812 editor.save(true, project.clone(), window, cx)
7813 })
7814 .unwrap();
7815 fake_server
7816 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7817 assert_eq!(
7818 params.text_document.uri,
7819 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7820 );
7821 assert_eq!(params.options.tab_size, 4);
7822 Ok(Some(vec![lsp::TextEdit::new(
7823 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7824 ", ".to_string(),
7825 )]))
7826 })
7827 .next()
7828 .await;
7829 cx.executor().start_waiting();
7830 save.await;
7831 assert_eq!(
7832 editor.update(cx, |editor, cx| editor.text(cx)),
7833 "one, two\nthree\n"
7834 );
7835 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7836
7837 editor.update_in(cx, |editor, window, cx| {
7838 editor.set_text("one\ntwo\nthree\n", window, cx)
7839 });
7840 assert!(cx.read(|cx| editor.is_dirty(cx)));
7841
7842 // Ensure we can still save even if formatting hangs.
7843 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7844 move |params, _| async move {
7845 assert_eq!(
7846 params.text_document.uri,
7847 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7848 );
7849 futures::future::pending::<()>().await;
7850 unreachable!()
7851 },
7852 );
7853 let save = editor
7854 .update_in(cx, |editor, window, cx| {
7855 editor.save(true, project.clone(), window, cx)
7856 })
7857 .unwrap();
7858 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7859 cx.executor().start_waiting();
7860 save.await;
7861 assert_eq!(
7862 editor.update(cx, |editor, cx| editor.text(cx)),
7863 "one\ntwo\nthree\n"
7864 );
7865 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7866
7867 // For non-dirty buffer, no formatting request should be sent
7868 let save = editor
7869 .update_in(cx, |editor, window, cx| {
7870 editor.save(true, project.clone(), window, cx)
7871 })
7872 .unwrap();
7873 let _pending_format_request = fake_server
7874 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7875 panic!("Should not be invoked on non-dirty buffer");
7876 })
7877 .next();
7878 cx.executor().start_waiting();
7879 save.await;
7880
7881 // Set Rust language override and assert overridden tabsize is sent to language server
7882 update_test_language_settings(cx, |settings| {
7883 settings.languages.insert(
7884 "Rust".into(),
7885 LanguageSettingsContent {
7886 tab_size: NonZeroU32::new(8),
7887 ..Default::default()
7888 },
7889 );
7890 });
7891
7892 editor.update_in(cx, |editor, window, cx| {
7893 editor.set_text("somehting_new\n", window, cx)
7894 });
7895 assert!(cx.read(|cx| editor.is_dirty(cx)));
7896 let save = editor
7897 .update_in(cx, |editor, window, cx| {
7898 editor.save(true, project.clone(), window, cx)
7899 })
7900 .unwrap();
7901 fake_server
7902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7903 assert_eq!(
7904 params.text_document.uri,
7905 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7906 );
7907 assert_eq!(params.options.tab_size, 8);
7908 Ok(Some(vec![]))
7909 })
7910 .next()
7911 .await;
7912 cx.executor().start_waiting();
7913 save.await;
7914}
7915
7916#[gpui::test]
7917async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7918 init_test(cx, |settings| {
7919 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7920 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7921 ))
7922 });
7923
7924 let fs = FakeFs::new(cx.executor());
7925 fs.insert_file(path!("/file.rs"), Default::default()).await;
7926
7927 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7928
7929 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7930 language_registry.add(Arc::new(Language::new(
7931 LanguageConfig {
7932 name: "Rust".into(),
7933 matcher: LanguageMatcher {
7934 path_suffixes: vec!["rs".to_string()],
7935 ..Default::default()
7936 },
7937 ..LanguageConfig::default()
7938 },
7939 Some(tree_sitter_rust::LANGUAGE.into()),
7940 )));
7941 update_test_language_settings(cx, |settings| {
7942 // Enable Prettier formatting for the same buffer, and ensure
7943 // LSP is called instead of Prettier.
7944 settings.defaults.prettier = Some(PrettierSettings {
7945 allowed: true,
7946 ..PrettierSettings::default()
7947 });
7948 });
7949 let mut fake_servers = language_registry.register_fake_lsp(
7950 "Rust",
7951 FakeLspAdapter {
7952 capabilities: lsp::ServerCapabilities {
7953 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7954 ..Default::default()
7955 },
7956 ..Default::default()
7957 },
7958 );
7959
7960 let buffer = project
7961 .update(cx, |project, cx| {
7962 project.open_local_buffer(path!("/file.rs"), cx)
7963 })
7964 .await
7965 .unwrap();
7966
7967 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7968 let (editor, cx) = cx.add_window_view(|window, cx| {
7969 build_editor_with_project(project.clone(), buffer, window, cx)
7970 });
7971 editor.update_in(cx, |editor, window, cx| {
7972 editor.set_text("one\ntwo\nthree\n", window, cx)
7973 });
7974
7975 cx.executor().start_waiting();
7976 let fake_server = fake_servers.next().await.unwrap();
7977
7978 let format = editor
7979 .update_in(cx, |editor, window, cx| {
7980 editor.perform_format(
7981 project.clone(),
7982 FormatTrigger::Manual,
7983 FormatTarget::Buffers,
7984 window,
7985 cx,
7986 )
7987 })
7988 .unwrap();
7989 fake_server
7990 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7991 assert_eq!(
7992 params.text_document.uri,
7993 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7994 );
7995 assert_eq!(params.options.tab_size, 4);
7996 Ok(Some(vec![lsp::TextEdit::new(
7997 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7998 ", ".to_string(),
7999 )]))
8000 })
8001 .next()
8002 .await;
8003 cx.executor().start_waiting();
8004 format.await;
8005 assert_eq!(
8006 editor.update(cx, |editor, cx| editor.text(cx)),
8007 "one, two\nthree\n"
8008 );
8009
8010 editor.update_in(cx, |editor, window, cx| {
8011 editor.set_text("one\ntwo\nthree\n", window, cx)
8012 });
8013 // Ensure we don't lock if formatting hangs.
8014 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8015 assert_eq!(
8016 params.text_document.uri,
8017 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8018 );
8019 futures::future::pending::<()>().await;
8020 unreachable!()
8021 });
8022 let format = editor
8023 .update_in(cx, |editor, window, cx| {
8024 editor.perform_format(
8025 project,
8026 FormatTrigger::Manual,
8027 FormatTarget::Buffers,
8028 window,
8029 cx,
8030 )
8031 })
8032 .unwrap();
8033 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8034 cx.executor().start_waiting();
8035 format.await;
8036 assert_eq!(
8037 editor.update(cx, |editor, cx| editor.text(cx)),
8038 "one\ntwo\nthree\n"
8039 );
8040}
8041
8042#[gpui::test]
8043async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8044 init_test(cx, |settings| {
8045 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8046 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8047 ))
8048 });
8049
8050 let fs = FakeFs::new(cx.executor());
8051 fs.insert_file(path!("/file.ts"), Default::default()).await;
8052
8053 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8054
8055 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8056 language_registry.add(Arc::new(Language::new(
8057 LanguageConfig {
8058 name: "TypeScript".into(),
8059 matcher: LanguageMatcher {
8060 path_suffixes: vec!["ts".to_string()],
8061 ..Default::default()
8062 },
8063 ..LanguageConfig::default()
8064 },
8065 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8066 )));
8067 update_test_language_settings(cx, |settings| {
8068 settings.defaults.prettier = Some(PrettierSettings {
8069 allowed: true,
8070 ..PrettierSettings::default()
8071 });
8072 });
8073 let mut fake_servers = language_registry.register_fake_lsp(
8074 "TypeScript",
8075 FakeLspAdapter {
8076 capabilities: lsp::ServerCapabilities {
8077 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8078 ..Default::default()
8079 },
8080 ..Default::default()
8081 },
8082 );
8083
8084 let buffer = project
8085 .update(cx, |project, cx| {
8086 project.open_local_buffer(path!("/file.ts"), cx)
8087 })
8088 .await
8089 .unwrap();
8090
8091 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8092 let (editor, cx) = cx.add_window_view(|window, cx| {
8093 build_editor_with_project(project.clone(), buffer, window, cx)
8094 });
8095 editor.update_in(cx, |editor, window, cx| {
8096 editor.set_text(
8097 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8098 window,
8099 cx,
8100 )
8101 });
8102
8103 cx.executor().start_waiting();
8104 let fake_server = fake_servers.next().await.unwrap();
8105
8106 let format = editor
8107 .update_in(cx, |editor, window, cx| {
8108 editor.perform_code_action_kind(
8109 project.clone(),
8110 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8111 window,
8112 cx,
8113 )
8114 })
8115 .unwrap();
8116 fake_server
8117 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8118 assert_eq!(
8119 params.text_document.uri,
8120 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8121 );
8122 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8123 lsp::CodeAction {
8124 title: "Organize Imports".to_string(),
8125 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8126 edit: Some(lsp::WorkspaceEdit {
8127 changes: Some(
8128 [(
8129 params.text_document.uri.clone(),
8130 vec![lsp::TextEdit::new(
8131 lsp::Range::new(
8132 lsp::Position::new(1, 0),
8133 lsp::Position::new(2, 0),
8134 ),
8135 "".to_string(),
8136 )],
8137 )]
8138 .into_iter()
8139 .collect(),
8140 ),
8141 ..Default::default()
8142 }),
8143 ..Default::default()
8144 },
8145 )]))
8146 })
8147 .next()
8148 .await;
8149 cx.executor().start_waiting();
8150 format.await;
8151 assert_eq!(
8152 editor.update(cx, |editor, cx| editor.text(cx)),
8153 "import { a } from 'module';\n\nconst x = a;\n"
8154 );
8155
8156 editor.update_in(cx, |editor, window, cx| {
8157 editor.set_text(
8158 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8159 window,
8160 cx,
8161 )
8162 });
8163 // Ensure we don't lock if code action hangs.
8164 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8165 move |params, _| async move {
8166 assert_eq!(
8167 params.text_document.uri,
8168 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8169 );
8170 futures::future::pending::<()>().await;
8171 unreachable!()
8172 },
8173 );
8174 let format = editor
8175 .update_in(cx, |editor, window, cx| {
8176 editor.perform_code_action_kind(
8177 project,
8178 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8179 window,
8180 cx,
8181 )
8182 })
8183 .unwrap();
8184 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8185 cx.executor().start_waiting();
8186 format.await;
8187 assert_eq!(
8188 editor.update(cx, |editor, cx| editor.text(cx)),
8189 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8190 );
8191}
8192
8193#[gpui::test]
8194async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8195 init_test(cx, |_| {});
8196
8197 let mut cx = EditorLspTestContext::new_rust(
8198 lsp::ServerCapabilities {
8199 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8200 ..Default::default()
8201 },
8202 cx,
8203 )
8204 .await;
8205
8206 cx.set_state(indoc! {"
8207 one.twoˇ
8208 "});
8209
8210 // The format request takes a long time. When it completes, it inserts
8211 // a newline and an indent before the `.`
8212 cx.lsp
8213 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8214 let executor = cx.background_executor().clone();
8215 async move {
8216 executor.timer(Duration::from_millis(100)).await;
8217 Ok(Some(vec![lsp::TextEdit {
8218 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8219 new_text: "\n ".into(),
8220 }]))
8221 }
8222 });
8223
8224 // Submit a format request.
8225 let format_1 = cx
8226 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8227 .unwrap();
8228 cx.executor().run_until_parked();
8229
8230 // Submit a second format request.
8231 let format_2 = cx
8232 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8233 .unwrap();
8234 cx.executor().run_until_parked();
8235
8236 // Wait for both format requests to complete
8237 cx.executor().advance_clock(Duration::from_millis(200));
8238 cx.executor().start_waiting();
8239 format_1.await.unwrap();
8240 cx.executor().start_waiting();
8241 format_2.await.unwrap();
8242
8243 // The formatting edits only happens once.
8244 cx.assert_editor_state(indoc! {"
8245 one
8246 .twoˇ
8247 "});
8248}
8249
8250#[gpui::test]
8251async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8252 init_test(cx, |settings| {
8253 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8254 });
8255
8256 let mut cx = EditorLspTestContext::new_rust(
8257 lsp::ServerCapabilities {
8258 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8259 ..Default::default()
8260 },
8261 cx,
8262 )
8263 .await;
8264
8265 // Set up a buffer white some trailing whitespace and no trailing newline.
8266 cx.set_state(
8267 &[
8268 "one ", //
8269 "twoˇ", //
8270 "three ", //
8271 "four", //
8272 ]
8273 .join("\n"),
8274 );
8275
8276 // Submit a format request.
8277 let format = cx
8278 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8279 .unwrap();
8280
8281 // Record which buffer changes have been sent to the language server
8282 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8283 cx.lsp
8284 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8285 let buffer_changes = buffer_changes.clone();
8286 move |params, _| {
8287 buffer_changes.lock().extend(
8288 params
8289 .content_changes
8290 .into_iter()
8291 .map(|e| (e.range.unwrap(), e.text)),
8292 );
8293 }
8294 });
8295
8296 // Handle formatting requests to the language server.
8297 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8298 let buffer_changes = buffer_changes.clone();
8299 move |_, _| {
8300 // When formatting is requested, trailing whitespace has already been stripped,
8301 // and the trailing newline has already been added.
8302 assert_eq!(
8303 &buffer_changes.lock()[1..],
8304 &[
8305 (
8306 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8307 "".into()
8308 ),
8309 (
8310 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8311 "".into()
8312 ),
8313 (
8314 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8315 "\n".into()
8316 ),
8317 ]
8318 );
8319
8320 // Insert blank lines between each line of the buffer.
8321 async move {
8322 Ok(Some(vec![
8323 lsp::TextEdit {
8324 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8325 new_text: "\n".into(),
8326 },
8327 lsp::TextEdit {
8328 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8329 new_text: "\n".into(),
8330 },
8331 ]))
8332 }
8333 }
8334 });
8335
8336 // After formatting the buffer, the trailing whitespace is stripped,
8337 // a newline is appended, and the edits provided by the language server
8338 // have been applied.
8339 format.await.unwrap();
8340 cx.assert_editor_state(
8341 &[
8342 "one", //
8343 "", //
8344 "twoˇ", //
8345 "", //
8346 "three", //
8347 "four", //
8348 "", //
8349 ]
8350 .join("\n"),
8351 );
8352
8353 // Undoing the formatting undoes the trailing whitespace removal, the
8354 // trailing newline, and the LSP edits.
8355 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8356 cx.assert_editor_state(
8357 &[
8358 "one ", //
8359 "twoˇ", //
8360 "three ", //
8361 "four", //
8362 ]
8363 .join("\n"),
8364 );
8365}
8366
8367#[gpui::test]
8368async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8369 cx: &mut TestAppContext,
8370) {
8371 init_test(cx, |_| {});
8372
8373 cx.update(|cx| {
8374 cx.update_global::<SettingsStore, _>(|settings, cx| {
8375 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8376 settings.auto_signature_help = Some(true);
8377 });
8378 });
8379 });
8380
8381 let mut cx = EditorLspTestContext::new_rust(
8382 lsp::ServerCapabilities {
8383 signature_help_provider: Some(lsp::SignatureHelpOptions {
8384 ..Default::default()
8385 }),
8386 ..Default::default()
8387 },
8388 cx,
8389 )
8390 .await;
8391
8392 let language = Language::new(
8393 LanguageConfig {
8394 name: "Rust".into(),
8395 brackets: BracketPairConfig {
8396 pairs: vec![
8397 BracketPair {
8398 start: "{".to_string(),
8399 end: "}".to_string(),
8400 close: true,
8401 surround: true,
8402 newline: true,
8403 },
8404 BracketPair {
8405 start: "(".to_string(),
8406 end: ")".to_string(),
8407 close: true,
8408 surround: true,
8409 newline: true,
8410 },
8411 BracketPair {
8412 start: "/*".to_string(),
8413 end: " */".to_string(),
8414 close: true,
8415 surround: true,
8416 newline: true,
8417 },
8418 BracketPair {
8419 start: "[".to_string(),
8420 end: "]".to_string(),
8421 close: false,
8422 surround: false,
8423 newline: true,
8424 },
8425 BracketPair {
8426 start: "\"".to_string(),
8427 end: "\"".to_string(),
8428 close: true,
8429 surround: true,
8430 newline: false,
8431 },
8432 BracketPair {
8433 start: "<".to_string(),
8434 end: ">".to_string(),
8435 close: false,
8436 surround: true,
8437 newline: true,
8438 },
8439 ],
8440 ..Default::default()
8441 },
8442 autoclose_before: "})]".to_string(),
8443 ..Default::default()
8444 },
8445 Some(tree_sitter_rust::LANGUAGE.into()),
8446 );
8447 let language = Arc::new(language);
8448
8449 cx.language_registry().add(language.clone());
8450 cx.update_buffer(|buffer, cx| {
8451 buffer.set_language(Some(language), cx);
8452 });
8453
8454 cx.set_state(
8455 &r#"
8456 fn main() {
8457 sampleˇ
8458 }
8459 "#
8460 .unindent(),
8461 );
8462
8463 cx.update_editor(|editor, window, cx| {
8464 editor.handle_input("(", window, cx);
8465 });
8466 cx.assert_editor_state(
8467 &"
8468 fn main() {
8469 sample(ˇ)
8470 }
8471 "
8472 .unindent(),
8473 );
8474
8475 let mocked_response = lsp::SignatureHelp {
8476 signatures: vec![lsp::SignatureInformation {
8477 label: "fn sample(param1: u8, param2: u8)".to_string(),
8478 documentation: None,
8479 parameters: Some(vec![
8480 lsp::ParameterInformation {
8481 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8482 documentation: None,
8483 },
8484 lsp::ParameterInformation {
8485 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8486 documentation: None,
8487 },
8488 ]),
8489 active_parameter: None,
8490 }],
8491 active_signature: Some(0),
8492 active_parameter: Some(0),
8493 };
8494 handle_signature_help_request(&mut cx, mocked_response).await;
8495
8496 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8497 .await;
8498
8499 cx.editor(|editor, _, _| {
8500 let signature_help_state = editor.signature_help_state.popover().cloned();
8501 assert_eq!(
8502 signature_help_state.unwrap().label,
8503 "param1: u8, param2: u8"
8504 );
8505 });
8506}
8507
8508#[gpui::test]
8509async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8510 init_test(cx, |_| {});
8511
8512 cx.update(|cx| {
8513 cx.update_global::<SettingsStore, _>(|settings, cx| {
8514 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8515 settings.auto_signature_help = Some(false);
8516 settings.show_signature_help_after_edits = Some(false);
8517 });
8518 });
8519 });
8520
8521 let mut cx = EditorLspTestContext::new_rust(
8522 lsp::ServerCapabilities {
8523 signature_help_provider: Some(lsp::SignatureHelpOptions {
8524 ..Default::default()
8525 }),
8526 ..Default::default()
8527 },
8528 cx,
8529 )
8530 .await;
8531
8532 let language = Language::new(
8533 LanguageConfig {
8534 name: "Rust".into(),
8535 brackets: BracketPairConfig {
8536 pairs: vec![
8537 BracketPair {
8538 start: "{".to_string(),
8539 end: "}".to_string(),
8540 close: true,
8541 surround: true,
8542 newline: true,
8543 },
8544 BracketPair {
8545 start: "(".to_string(),
8546 end: ")".to_string(),
8547 close: true,
8548 surround: true,
8549 newline: true,
8550 },
8551 BracketPair {
8552 start: "/*".to_string(),
8553 end: " */".to_string(),
8554 close: true,
8555 surround: true,
8556 newline: true,
8557 },
8558 BracketPair {
8559 start: "[".to_string(),
8560 end: "]".to_string(),
8561 close: false,
8562 surround: false,
8563 newline: true,
8564 },
8565 BracketPair {
8566 start: "\"".to_string(),
8567 end: "\"".to_string(),
8568 close: true,
8569 surround: true,
8570 newline: false,
8571 },
8572 BracketPair {
8573 start: "<".to_string(),
8574 end: ">".to_string(),
8575 close: false,
8576 surround: true,
8577 newline: true,
8578 },
8579 ],
8580 ..Default::default()
8581 },
8582 autoclose_before: "})]".to_string(),
8583 ..Default::default()
8584 },
8585 Some(tree_sitter_rust::LANGUAGE.into()),
8586 );
8587 let language = Arc::new(language);
8588
8589 cx.language_registry().add(language.clone());
8590 cx.update_buffer(|buffer, cx| {
8591 buffer.set_language(Some(language), cx);
8592 });
8593
8594 // Ensure that signature_help is not called when no signature help is enabled.
8595 cx.set_state(
8596 &r#"
8597 fn main() {
8598 sampleˇ
8599 }
8600 "#
8601 .unindent(),
8602 );
8603 cx.update_editor(|editor, window, cx| {
8604 editor.handle_input("(", window, cx);
8605 });
8606 cx.assert_editor_state(
8607 &"
8608 fn main() {
8609 sample(ˇ)
8610 }
8611 "
8612 .unindent(),
8613 );
8614 cx.editor(|editor, _, _| {
8615 assert!(editor.signature_help_state.task().is_none());
8616 });
8617
8618 let mocked_response = lsp::SignatureHelp {
8619 signatures: vec![lsp::SignatureInformation {
8620 label: "fn sample(param1: u8, param2: u8)".to_string(),
8621 documentation: None,
8622 parameters: Some(vec![
8623 lsp::ParameterInformation {
8624 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8625 documentation: None,
8626 },
8627 lsp::ParameterInformation {
8628 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8629 documentation: None,
8630 },
8631 ]),
8632 active_parameter: None,
8633 }],
8634 active_signature: Some(0),
8635 active_parameter: Some(0),
8636 };
8637
8638 // Ensure that signature_help is called when enabled afte edits
8639 cx.update(|_, cx| {
8640 cx.update_global::<SettingsStore, _>(|settings, cx| {
8641 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8642 settings.auto_signature_help = Some(false);
8643 settings.show_signature_help_after_edits = Some(true);
8644 });
8645 });
8646 });
8647 cx.set_state(
8648 &r#"
8649 fn main() {
8650 sampleˇ
8651 }
8652 "#
8653 .unindent(),
8654 );
8655 cx.update_editor(|editor, window, cx| {
8656 editor.handle_input("(", window, cx);
8657 });
8658 cx.assert_editor_state(
8659 &"
8660 fn main() {
8661 sample(ˇ)
8662 }
8663 "
8664 .unindent(),
8665 );
8666 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8667 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8668 .await;
8669 cx.update_editor(|editor, _, _| {
8670 let signature_help_state = editor.signature_help_state.popover().cloned();
8671 assert!(signature_help_state.is_some());
8672 assert_eq!(
8673 signature_help_state.unwrap().label,
8674 "param1: u8, param2: u8"
8675 );
8676 editor.signature_help_state = SignatureHelpState::default();
8677 });
8678
8679 // Ensure that signature_help is called when auto signature help override is enabled
8680 cx.update(|_, cx| {
8681 cx.update_global::<SettingsStore, _>(|settings, cx| {
8682 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8683 settings.auto_signature_help = Some(true);
8684 settings.show_signature_help_after_edits = Some(false);
8685 });
8686 });
8687 });
8688 cx.set_state(
8689 &r#"
8690 fn main() {
8691 sampleˇ
8692 }
8693 "#
8694 .unindent(),
8695 );
8696 cx.update_editor(|editor, window, cx| {
8697 editor.handle_input("(", window, cx);
8698 });
8699 cx.assert_editor_state(
8700 &"
8701 fn main() {
8702 sample(ˇ)
8703 }
8704 "
8705 .unindent(),
8706 );
8707 handle_signature_help_request(&mut cx, mocked_response).await;
8708 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8709 .await;
8710 cx.editor(|editor, _, _| {
8711 let signature_help_state = editor.signature_help_state.popover().cloned();
8712 assert!(signature_help_state.is_some());
8713 assert_eq!(
8714 signature_help_state.unwrap().label,
8715 "param1: u8, param2: u8"
8716 );
8717 });
8718}
8719
8720#[gpui::test]
8721async fn test_signature_help(cx: &mut TestAppContext) {
8722 init_test(cx, |_| {});
8723 cx.update(|cx| {
8724 cx.update_global::<SettingsStore, _>(|settings, cx| {
8725 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8726 settings.auto_signature_help = Some(true);
8727 });
8728 });
8729 });
8730
8731 let mut cx = EditorLspTestContext::new_rust(
8732 lsp::ServerCapabilities {
8733 signature_help_provider: Some(lsp::SignatureHelpOptions {
8734 ..Default::default()
8735 }),
8736 ..Default::default()
8737 },
8738 cx,
8739 )
8740 .await;
8741
8742 // A test that directly calls `show_signature_help`
8743 cx.update_editor(|editor, window, cx| {
8744 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8745 });
8746
8747 let mocked_response = lsp::SignatureHelp {
8748 signatures: vec![lsp::SignatureInformation {
8749 label: "fn sample(param1: u8, param2: u8)".to_string(),
8750 documentation: None,
8751 parameters: Some(vec![
8752 lsp::ParameterInformation {
8753 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8754 documentation: None,
8755 },
8756 lsp::ParameterInformation {
8757 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8758 documentation: None,
8759 },
8760 ]),
8761 active_parameter: None,
8762 }],
8763 active_signature: Some(0),
8764 active_parameter: Some(0),
8765 };
8766 handle_signature_help_request(&mut cx, mocked_response).await;
8767
8768 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8769 .await;
8770
8771 cx.editor(|editor, _, _| {
8772 let signature_help_state = editor.signature_help_state.popover().cloned();
8773 assert!(signature_help_state.is_some());
8774 assert_eq!(
8775 signature_help_state.unwrap().label,
8776 "param1: u8, param2: u8"
8777 );
8778 });
8779
8780 // When exiting outside from inside the brackets, `signature_help` is closed.
8781 cx.set_state(indoc! {"
8782 fn main() {
8783 sample(ˇ);
8784 }
8785
8786 fn sample(param1: u8, param2: u8) {}
8787 "});
8788
8789 cx.update_editor(|editor, window, cx| {
8790 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8791 });
8792
8793 let mocked_response = lsp::SignatureHelp {
8794 signatures: Vec::new(),
8795 active_signature: None,
8796 active_parameter: None,
8797 };
8798 handle_signature_help_request(&mut cx, mocked_response).await;
8799
8800 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8801 .await;
8802
8803 cx.editor(|editor, _, _| {
8804 assert!(!editor.signature_help_state.is_shown());
8805 });
8806
8807 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8808 cx.set_state(indoc! {"
8809 fn main() {
8810 sample(ˇ);
8811 }
8812
8813 fn sample(param1: u8, param2: u8) {}
8814 "});
8815
8816 let mocked_response = lsp::SignatureHelp {
8817 signatures: vec![lsp::SignatureInformation {
8818 label: "fn sample(param1: u8, param2: u8)".to_string(),
8819 documentation: None,
8820 parameters: Some(vec![
8821 lsp::ParameterInformation {
8822 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8823 documentation: None,
8824 },
8825 lsp::ParameterInformation {
8826 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8827 documentation: None,
8828 },
8829 ]),
8830 active_parameter: None,
8831 }],
8832 active_signature: Some(0),
8833 active_parameter: Some(0),
8834 };
8835 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8836 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8837 .await;
8838 cx.editor(|editor, _, _| {
8839 assert!(editor.signature_help_state.is_shown());
8840 });
8841
8842 // Restore the popover with more parameter input
8843 cx.set_state(indoc! {"
8844 fn main() {
8845 sample(param1, param2ˇ);
8846 }
8847
8848 fn sample(param1: u8, param2: u8) {}
8849 "});
8850
8851 let mocked_response = lsp::SignatureHelp {
8852 signatures: vec![lsp::SignatureInformation {
8853 label: "fn sample(param1: u8, param2: u8)".to_string(),
8854 documentation: None,
8855 parameters: Some(vec![
8856 lsp::ParameterInformation {
8857 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8858 documentation: None,
8859 },
8860 lsp::ParameterInformation {
8861 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8862 documentation: None,
8863 },
8864 ]),
8865 active_parameter: None,
8866 }],
8867 active_signature: Some(0),
8868 active_parameter: Some(1),
8869 };
8870 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8871 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8872 .await;
8873
8874 // When selecting a range, the popover is gone.
8875 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8876 cx.update_editor(|editor, window, cx| {
8877 editor.change_selections(None, window, cx, |s| {
8878 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8879 })
8880 });
8881 cx.assert_editor_state(indoc! {"
8882 fn main() {
8883 sample(param1, «ˇparam2»);
8884 }
8885
8886 fn sample(param1: u8, param2: u8) {}
8887 "});
8888 cx.editor(|editor, _, _| {
8889 assert!(!editor.signature_help_state.is_shown());
8890 });
8891
8892 // When unselecting again, the popover is back if within the brackets.
8893 cx.update_editor(|editor, window, cx| {
8894 editor.change_selections(None, window, cx, |s| {
8895 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8896 })
8897 });
8898 cx.assert_editor_state(indoc! {"
8899 fn main() {
8900 sample(param1, ˇparam2);
8901 }
8902
8903 fn sample(param1: u8, param2: u8) {}
8904 "});
8905 handle_signature_help_request(&mut cx, mocked_response).await;
8906 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8907 .await;
8908 cx.editor(|editor, _, _| {
8909 assert!(editor.signature_help_state.is_shown());
8910 });
8911
8912 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8913 cx.update_editor(|editor, window, cx| {
8914 editor.change_selections(None, window, cx, |s| {
8915 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8916 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8917 })
8918 });
8919 cx.assert_editor_state(indoc! {"
8920 fn main() {
8921 sample(param1, ˇparam2);
8922 }
8923
8924 fn sample(param1: u8, param2: u8) {}
8925 "});
8926
8927 let mocked_response = lsp::SignatureHelp {
8928 signatures: vec![lsp::SignatureInformation {
8929 label: "fn sample(param1: u8, param2: u8)".to_string(),
8930 documentation: None,
8931 parameters: Some(vec![
8932 lsp::ParameterInformation {
8933 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8934 documentation: None,
8935 },
8936 lsp::ParameterInformation {
8937 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8938 documentation: None,
8939 },
8940 ]),
8941 active_parameter: None,
8942 }],
8943 active_signature: Some(0),
8944 active_parameter: Some(1),
8945 };
8946 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8947 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8948 .await;
8949 cx.update_editor(|editor, _, cx| {
8950 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8951 });
8952 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8953 .await;
8954 cx.update_editor(|editor, window, cx| {
8955 editor.change_selections(None, window, cx, |s| {
8956 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8957 })
8958 });
8959 cx.assert_editor_state(indoc! {"
8960 fn main() {
8961 sample(param1, «ˇparam2»);
8962 }
8963
8964 fn sample(param1: u8, param2: u8) {}
8965 "});
8966 cx.update_editor(|editor, window, cx| {
8967 editor.change_selections(None, window, cx, |s| {
8968 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8969 })
8970 });
8971 cx.assert_editor_state(indoc! {"
8972 fn main() {
8973 sample(param1, ˇparam2);
8974 }
8975
8976 fn sample(param1: u8, param2: u8) {}
8977 "});
8978 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8979 .await;
8980}
8981
8982#[gpui::test]
8983async fn test_completion(cx: &mut TestAppContext) {
8984 init_test(cx, |_| {});
8985
8986 let mut cx = EditorLspTestContext::new_rust(
8987 lsp::ServerCapabilities {
8988 completion_provider: Some(lsp::CompletionOptions {
8989 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8990 resolve_provider: Some(true),
8991 ..Default::default()
8992 }),
8993 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8994 ..Default::default()
8995 },
8996 cx,
8997 )
8998 .await;
8999 let counter = Arc::new(AtomicUsize::new(0));
9000
9001 cx.set_state(indoc! {"
9002 oneˇ
9003 two
9004 three
9005 "});
9006 cx.simulate_keystroke(".");
9007 handle_completion_request(
9008 &mut cx,
9009 indoc! {"
9010 one.|<>
9011 two
9012 three
9013 "},
9014 vec!["first_completion", "second_completion"],
9015 counter.clone(),
9016 )
9017 .await;
9018 cx.condition(|editor, _| editor.context_menu_visible())
9019 .await;
9020 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9021
9022 let _handler = handle_signature_help_request(
9023 &mut cx,
9024 lsp::SignatureHelp {
9025 signatures: vec![lsp::SignatureInformation {
9026 label: "test signature".to_string(),
9027 documentation: None,
9028 parameters: Some(vec![lsp::ParameterInformation {
9029 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9030 documentation: None,
9031 }]),
9032 active_parameter: None,
9033 }],
9034 active_signature: None,
9035 active_parameter: None,
9036 },
9037 );
9038 cx.update_editor(|editor, window, cx| {
9039 assert!(
9040 !editor.signature_help_state.is_shown(),
9041 "No signature help was called for"
9042 );
9043 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9044 });
9045 cx.run_until_parked();
9046 cx.update_editor(|editor, _, _| {
9047 assert!(
9048 !editor.signature_help_state.is_shown(),
9049 "No signature help should be shown when completions menu is open"
9050 );
9051 });
9052
9053 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9054 editor.context_menu_next(&Default::default(), window, cx);
9055 editor
9056 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9057 .unwrap()
9058 });
9059 cx.assert_editor_state(indoc! {"
9060 one.second_completionˇ
9061 two
9062 three
9063 "});
9064
9065 handle_resolve_completion_request(
9066 &mut cx,
9067 Some(vec![
9068 (
9069 //This overlaps with the primary completion edit which is
9070 //misbehavior from the LSP spec, test that we filter it out
9071 indoc! {"
9072 one.second_ˇcompletion
9073 two
9074 threeˇ
9075 "},
9076 "overlapping additional edit",
9077 ),
9078 (
9079 indoc! {"
9080 one.second_completion
9081 two
9082 threeˇ
9083 "},
9084 "\nadditional edit",
9085 ),
9086 ]),
9087 )
9088 .await;
9089 apply_additional_edits.await.unwrap();
9090 cx.assert_editor_state(indoc! {"
9091 one.second_completionˇ
9092 two
9093 three
9094 additional edit
9095 "});
9096
9097 cx.set_state(indoc! {"
9098 one.second_completion
9099 twoˇ
9100 threeˇ
9101 additional edit
9102 "});
9103 cx.simulate_keystroke(" ");
9104 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9105 cx.simulate_keystroke("s");
9106 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9107
9108 cx.assert_editor_state(indoc! {"
9109 one.second_completion
9110 two sˇ
9111 three sˇ
9112 additional edit
9113 "});
9114 handle_completion_request(
9115 &mut cx,
9116 indoc! {"
9117 one.second_completion
9118 two s
9119 three <s|>
9120 additional edit
9121 "},
9122 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9123 counter.clone(),
9124 )
9125 .await;
9126 cx.condition(|editor, _| editor.context_menu_visible())
9127 .await;
9128 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9129
9130 cx.simulate_keystroke("i");
9131
9132 handle_completion_request(
9133 &mut cx,
9134 indoc! {"
9135 one.second_completion
9136 two si
9137 three <si|>
9138 additional edit
9139 "},
9140 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9141 counter.clone(),
9142 )
9143 .await;
9144 cx.condition(|editor, _| editor.context_menu_visible())
9145 .await;
9146 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9147
9148 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9149 editor
9150 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9151 .unwrap()
9152 });
9153 cx.assert_editor_state(indoc! {"
9154 one.second_completion
9155 two sixth_completionˇ
9156 three sixth_completionˇ
9157 additional edit
9158 "});
9159
9160 apply_additional_edits.await.unwrap();
9161
9162 update_test_language_settings(&mut cx, |settings| {
9163 settings.defaults.show_completions_on_input = Some(false);
9164 });
9165 cx.set_state("editorˇ");
9166 cx.simulate_keystroke(".");
9167 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9168 cx.simulate_keystroke("c");
9169 cx.simulate_keystroke("l");
9170 cx.simulate_keystroke("o");
9171 cx.assert_editor_state("editor.cloˇ");
9172 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9173 cx.update_editor(|editor, window, cx| {
9174 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9175 });
9176 handle_completion_request(
9177 &mut cx,
9178 "editor.<clo|>",
9179 vec!["close", "clobber"],
9180 counter.clone(),
9181 )
9182 .await;
9183 cx.condition(|editor, _| editor.context_menu_visible())
9184 .await;
9185 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9186
9187 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9188 editor
9189 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9190 .unwrap()
9191 });
9192 cx.assert_editor_state("editor.closeˇ");
9193 handle_resolve_completion_request(&mut cx, None).await;
9194 apply_additional_edits.await.unwrap();
9195}
9196
9197#[gpui::test]
9198async fn test_words_completion(cx: &mut TestAppContext) {
9199 let lsp_fetch_timeout_ms = 10;
9200 init_test(cx, |language_settings| {
9201 language_settings.defaults.completions = Some(CompletionSettings {
9202 words: WordsCompletionMode::Fallback,
9203 lsp: true,
9204 lsp_fetch_timeout_ms: 10,
9205 });
9206 });
9207
9208 let mut cx = EditorLspTestContext::new_rust(
9209 lsp::ServerCapabilities {
9210 completion_provider: Some(lsp::CompletionOptions {
9211 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9212 ..lsp::CompletionOptions::default()
9213 }),
9214 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9215 ..lsp::ServerCapabilities::default()
9216 },
9217 cx,
9218 )
9219 .await;
9220
9221 let throttle_completions = Arc::new(AtomicBool::new(false));
9222
9223 let lsp_throttle_completions = throttle_completions.clone();
9224 let _completion_requests_handler =
9225 cx.lsp
9226 .server
9227 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9228 let lsp_throttle_completions = lsp_throttle_completions.clone();
9229 async move {
9230 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9231 cx.background_executor()
9232 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9233 .await;
9234 }
9235 Ok(Some(lsp::CompletionResponse::Array(vec![
9236 lsp::CompletionItem {
9237 label: "first".into(),
9238 ..lsp::CompletionItem::default()
9239 },
9240 lsp::CompletionItem {
9241 label: "last".into(),
9242 ..lsp::CompletionItem::default()
9243 },
9244 ])))
9245 }
9246 });
9247
9248 cx.set_state(indoc! {"
9249 oneˇ
9250 two
9251 three
9252 "});
9253 cx.simulate_keystroke(".");
9254 cx.executor().run_until_parked();
9255 cx.condition(|editor, _| editor.context_menu_visible())
9256 .await;
9257 cx.update_editor(|editor, window, cx| {
9258 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9259 {
9260 assert_eq!(
9261 completion_menu_entries(&menu),
9262 &["first", "last"],
9263 "When LSP server is fast to reply, no fallback word completions are used"
9264 );
9265 } else {
9266 panic!("expected completion menu to be open");
9267 }
9268 editor.cancel(&Cancel, window, cx);
9269 });
9270 cx.executor().run_until_parked();
9271 cx.condition(|editor, _| !editor.context_menu_visible())
9272 .await;
9273
9274 throttle_completions.store(true, atomic::Ordering::Release);
9275 cx.simulate_keystroke(".");
9276 cx.executor()
9277 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9278 cx.executor().run_until_parked();
9279 cx.condition(|editor, _| editor.context_menu_visible())
9280 .await;
9281 cx.update_editor(|editor, _, _| {
9282 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9283 {
9284 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9285 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9286 } else {
9287 panic!("expected completion menu to be open");
9288 }
9289 });
9290}
9291
9292#[gpui::test]
9293async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9294 init_test(cx, |language_settings| {
9295 language_settings.defaults.completions = Some(CompletionSettings {
9296 words: WordsCompletionMode::Enabled,
9297 lsp: true,
9298 lsp_fetch_timeout_ms: 0,
9299 });
9300 });
9301
9302 let mut cx = EditorLspTestContext::new_rust(
9303 lsp::ServerCapabilities {
9304 completion_provider: Some(lsp::CompletionOptions {
9305 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9306 ..lsp::CompletionOptions::default()
9307 }),
9308 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9309 ..lsp::ServerCapabilities::default()
9310 },
9311 cx,
9312 )
9313 .await;
9314
9315 let _completion_requests_handler =
9316 cx.lsp
9317 .server
9318 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9319 Ok(Some(lsp::CompletionResponse::Array(vec![
9320 lsp::CompletionItem {
9321 label: "first".into(),
9322 ..lsp::CompletionItem::default()
9323 },
9324 lsp::CompletionItem {
9325 label: "last".into(),
9326 ..lsp::CompletionItem::default()
9327 },
9328 ])))
9329 });
9330
9331 cx.set_state(indoc! {"ˇ
9332 first
9333 last
9334 second
9335 "});
9336 cx.simulate_keystroke(".");
9337 cx.executor().run_until_parked();
9338 cx.condition(|editor, _| editor.context_menu_visible())
9339 .await;
9340 cx.update_editor(|editor, window, cx| {
9341 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9342 {
9343 assert_eq!(
9344 completion_menu_entries(&menu),
9345 &["first", "last", "second"],
9346 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9347 );
9348 } else {
9349 panic!("expected completion menu to be open");
9350 }
9351 editor.cancel(&Cancel, window, cx);
9352 });
9353}
9354
9355#[gpui::test]
9356async fn test_multiline_completion(cx: &mut TestAppContext) {
9357 init_test(cx, |_| {});
9358
9359 let fs = FakeFs::new(cx.executor());
9360 fs.insert_tree(
9361 path!("/a"),
9362 json!({
9363 "main.ts": "a",
9364 }),
9365 )
9366 .await;
9367
9368 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9369 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9370 let typescript_language = Arc::new(Language::new(
9371 LanguageConfig {
9372 name: "TypeScript".into(),
9373 matcher: LanguageMatcher {
9374 path_suffixes: vec!["ts".to_string()],
9375 ..LanguageMatcher::default()
9376 },
9377 line_comments: vec!["// ".into()],
9378 ..LanguageConfig::default()
9379 },
9380 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9381 ));
9382 language_registry.add(typescript_language.clone());
9383 let mut fake_servers = language_registry.register_fake_lsp(
9384 "TypeScript",
9385 FakeLspAdapter {
9386 capabilities: lsp::ServerCapabilities {
9387 completion_provider: Some(lsp::CompletionOptions {
9388 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9389 ..lsp::CompletionOptions::default()
9390 }),
9391 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9392 ..lsp::ServerCapabilities::default()
9393 },
9394 // Emulate vtsls label generation
9395 label_for_completion: Some(Box::new(|item, _| {
9396 let text = if let Some(description) = item
9397 .label_details
9398 .as_ref()
9399 .and_then(|label_details| label_details.description.as_ref())
9400 {
9401 format!("{} {}", item.label, description)
9402 } else if let Some(detail) = &item.detail {
9403 format!("{} {}", item.label, detail)
9404 } else {
9405 item.label.clone()
9406 };
9407 let len = text.len();
9408 Some(language::CodeLabel {
9409 text,
9410 runs: Vec::new(),
9411 filter_range: 0..len,
9412 })
9413 })),
9414 ..FakeLspAdapter::default()
9415 },
9416 );
9417 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9418 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9419 let worktree_id = workspace
9420 .update(cx, |workspace, _window, cx| {
9421 workspace.project().update(cx, |project, cx| {
9422 project.worktrees(cx).next().unwrap().read(cx).id()
9423 })
9424 })
9425 .unwrap();
9426 let _buffer = project
9427 .update(cx, |project, cx| {
9428 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9429 })
9430 .await
9431 .unwrap();
9432 let editor = workspace
9433 .update(cx, |workspace, window, cx| {
9434 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9435 })
9436 .unwrap()
9437 .await
9438 .unwrap()
9439 .downcast::<Editor>()
9440 .unwrap();
9441 let fake_server = fake_servers.next().await.unwrap();
9442
9443 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9444 let multiline_label_2 = "a\nb\nc\n";
9445 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9446 let multiline_description = "d\ne\nf\n";
9447 let multiline_detail_2 = "g\nh\ni\n";
9448
9449 let mut completion_handle =
9450 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9451 Ok(Some(lsp::CompletionResponse::Array(vec![
9452 lsp::CompletionItem {
9453 label: multiline_label.to_string(),
9454 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9455 range: lsp::Range {
9456 start: lsp::Position {
9457 line: params.text_document_position.position.line,
9458 character: params.text_document_position.position.character,
9459 },
9460 end: lsp::Position {
9461 line: params.text_document_position.position.line,
9462 character: params.text_document_position.position.character,
9463 },
9464 },
9465 new_text: "new_text_1".to_string(),
9466 })),
9467 ..lsp::CompletionItem::default()
9468 },
9469 lsp::CompletionItem {
9470 label: "single line label 1".to_string(),
9471 detail: Some(multiline_detail.to_string()),
9472 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9473 range: lsp::Range {
9474 start: lsp::Position {
9475 line: params.text_document_position.position.line,
9476 character: params.text_document_position.position.character,
9477 },
9478 end: lsp::Position {
9479 line: params.text_document_position.position.line,
9480 character: params.text_document_position.position.character,
9481 },
9482 },
9483 new_text: "new_text_2".to_string(),
9484 })),
9485 ..lsp::CompletionItem::default()
9486 },
9487 lsp::CompletionItem {
9488 label: "single line label 2".to_string(),
9489 label_details: Some(lsp::CompletionItemLabelDetails {
9490 description: Some(multiline_description.to_string()),
9491 detail: None,
9492 }),
9493 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9494 range: lsp::Range {
9495 start: lsp::Position {
9496 line: params.text_document_position.position.line,
9497 character: params.text_document_position.position.character,
9498 },
9499 end: lsp::Position {
9500 line: params.text_document_position.position.line,
9501 character: params.text_document_position.position.character,
9502 },
9503 },
9504 new_text: "new_text_2".to_string(),
9505 })),
9506 ..lsp::CompletionItem::default()
9507 },
9508 lsp::CompletionItem {
9509 label: multiline_label_2.to_string(),
9510 detail: Some(multiline_detail_2.to_string()),
9511 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9512 range: lsp::Range {
9513 start: lsp::Position {
9514 line: params.text_document_position.position.line,
9515 character: params.text_document_position.position.character,
9516 },
9517 end: lsp::Position {
9518 line: params.text_document_position.position.line,
9519 character: params.text_document_position.position.character,
9520 },
9521 },
9522 new_text: "new_text_3".to_string(),
9523 })),
9524 ..lsp::CompletionItem::default()
9525 },
9526 lsp::CompletionItem {
9527 label: "Label with many spaces and \t but without newlines".to_string(),
9528 detail: Some(
9529 "Details with many spaces and \t but without newlines".to_string(),
9530 ),
9531 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9532 range: lsp::Range {
9533 start: lsp::Position {
9534 line: params.text_document_position.position.line,
9535 character: params.text_document_position.position.character,
9536 },
9537 end: lsp::Position {
9538 line: params.text_document_position.position.line,
9539 character: params.text_document_position.position.character,
9540 },
9541 },
9542 new_text: "new_text_4".to_string(),
9543 })),
9544 ..lsp::CompletionItem::default()
9545 },
9546 ])))
9547 });
9548
9549 editor.update_in(cx, |editor, window, cx| {
9550 cx.focus_self(window);
9551 editor.move_to_end(&MoveToEnd, window, cx);
9552 editor.handle_input(".", window, cx);
9553 });
9554 cx.run_until_parked();
9555 completion_handle.next().await.unwrap();
9556
9557 editor.update(cx, |editor, _| {
9558 assert!(editor.context_menu_visible());
9559 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9560 {
9561 let completion_labels = menu
9562 .completions
9563 .borrow()
9564 .iter()
9565 .map(|c| c.label.text.clone())
9566 .collect::<Vec<_>>();
9567 assert_eq!(
9568 completion_labels,
9569 &[
9570 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9571 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9572 "single line label 2 d e f ",
9573 "a b c g h i ",
9574 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9575 ],
9576 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9577 );
9578
9579 for completion in menu
9580 .completions
9581 .borrow()
9582 .iter() {
9583 assert_eq!(
9584 completion.label.filter_range,
9585 0..completion.label.text.len(),
9586 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9587 );
9588 }
9589
9590 } else {
9591 panic!("expected completion menu to be open");
9592 }
9593 });
9594}
9595
9596#[gpui::test]
9597async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9598 init_test(cx, |_| {});
9599 let mut cx = EditorLspTestContext::new_rust(
9600 lsp::ServerCapabilities {
9601 completion_provider: Some(lsp::CompletionOptions {
9602 trigger_characters: Some(vec![".".to_string()]),
9603 ..Default::default()
9604 }),
9605 ..Default::default()
9606 },
9607 cx,
9608 )
9609 .await;
9610 cx.lsp
9611 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9612 Ok(Some(lsp::CompletionResponse::Array(vec![
9613 lsp::CompletionItem {
9614 label: "first".into(),
9615 ..Default::default()
9616 },
9617 lsp::CompletionItem {
9618 label: "last".into(),
9619 ..Default::default()
9620 },
9621 ])))
9622 });
9623 cx.set_state("variableˇ");
9624 cx.simulate_keystroke(".");
9625 cx.executor().run_until_parked();
9626
9627 cx.update_editor(|editor, _, _| {
9628 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9629 {
9630 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9631 } else {
9632 panic!("expected completion menu to be open");
9633 }
9634 });
9635
9636 cx.update_editor(|editor, window, cx| {
9637 editor.move_page_down(&MovePageDown::default(), window, cx);
9638 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9639 {
9640 assert!(
9641 menu.selected_item == 1,
9642 "expected PageDown to select the last item from the context menu"
9643 );
9644 } else {
9645 panic!("expected completion menu to stay open after PageDown");
9646 }
9647 });
9648
9649 cx.update_editor(|editor, window, cx| {
9650 editor.move_page_up(&MovePageUp::default(), window, cx);
9651 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9652 {
9653 assert!(
9654 menu.selected_item == 0,
9655 "expected PageUp to select the first item from the context menu"
9656 );
9657 } else {
9658 panic!("expected completion menu to stay open after PageUp");
9659 }
9660 });
9661}
9662
9663#[gpui::test]
9664async fn test_completion_sort(cx: &mut TestAppContext) {
9665 init_test(cx, |_| {});
9666 let mut cx = EditorLspTestContext::new_rust(
9667 lsp::ServerCapabilities {
9668 completion_provider: Some(lsp::CompletionOptions {
9669 trigger_characters: Some(vec![".".to_string()]),
9670 ..Default::default()
9671 }),
9672 ..Default::default()
9673 },
9674 cx,
9675 )
9676 .await;
9677 cx.lsp
9678 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9679 Ok(Some(lsp::CompletionResponse::Array(vec![
9680 lsp::CompletionItem {
9681 label: "Range".into(),
9682 sort_text: Some("a".into()),
9683 ..Default::default()
9684 },
9685 lsp::CompletionItem {
9686 label: "r".into(),
9687 sort_text: Some("b".into()),
9688 ..Default::default()
9689 },
9690 lsp::CompletionItem {
9691 label: "ret".into(),
9692 sort_text: Some("c".into()),
9693 ..Default::default()
9694 },
9695 lsp::CompletionItem {
9696 label: "return".into(),
9697 sort_text: Some("d".into()),
9698 ..Default::default()
9699 },
9700 lsp::CompletionItem {
9701 label: "slice".into(),
9702 sort_text: Some("d".into()),
9703 ..Default::default()
9704 },
9705 ])))
9706 });
9707 cx.set_state("rˇ");
9708 cx.executor().run_until_parked();
9709 cx.update_editor(|editor, window, cx| {
9710 editor.show_completions(
9711 &ShowCompletions {
9712 trigger: Some("r".into()),
9713 },
9714 window,
9715 cx,
9716 );
9717 });
9718 cx.executor().run_until_parked();
9719
9720 cx.update_editor(|editor, _, _| {
9721 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9722 {
9723 assert_eq!(
9724 completion_menu_entries(&menu),
9725 &["r", "ret", "Range", "return"]
9726 );
9727 } else {
9728 panic!("expected completion menu to be open");
9729 }
9730 });
9731}
9732
9733#[gpui::test]
9734async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9735 init_test(cx, |_| {});
9736
9737 let mut cx = EditorLspTestContext::new_rust(
9738 lsp::ServerCapabilities {
9739 completion_provider: Some(lsp::CompletionOptions {
9740 trigger_characters: Some(vec![".".to_string()]),
9741 resolve_provider: Some(true),
9742 ..Default::default()
9743 }),
9744 ..Default::default()
9745 },
9746 cx,
9747 )
9748 .await;
9749
9750 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9751 cx.simulate_keystroke(".");
9752 let completion_item = lsp::CompletionItem {
9753 label: "Some".into(),
9754 kind: Some(lsp::CompletionItemKind::SNIPPET),
9755 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9756 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9757 kind: lsp::MarkupKind::Markdown,
9758 value: "```rust\nSome(2)\n```".to_string(),
9759 })),
9760 deprecated: Some(false),
9761 sort_text: Some("Some".to_string()),
9762 filter_text: Some("Some".to_string()),
9763 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9764 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9765 range: lsp::Range {
9766 start: lsp::Position {
9767 line: 0,
9768 character: 22,
9769 },
9770 end: lsp::Position {
9771 line: 0,
9772 character: 22,
9773 },
9774 },
9775 new_text: "Some(2)".to_string(),
9776 })),
9777 additional_text_edits: Some(vec![lsp::TextEdit {
9778 range: lsp::Range {
9779 start: lsp::Position {
9780 line: 0,
9781 character: 20,
9782 },
9783 end: lsp::Position {
9784 line: 0,
9785 character: 22,
9786 },
9787 },
9788 new_text: "".to_string(),
9789 }]),
9790 ..Default::default()
9791 };
9792
9793 let closure_completion_item = completion_item.clone();
9794 let counter = Arc::new(AtomicUsize::new(0));
9795 let counter_clone = counter.clone();
9796 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9797 let task_completion_item = closure_completion_item.clone();
9798 counter_clone.fetch_add(1, atomic::Ordering::Release);
9799 async move {
9800 Ok(Some(lsp::CompletionResponse::Array(vec![
9801 task_completion_item,
9802 ])))
9803 }
9804 });
9805
9806 cx.condition(|editor, _| editor.context_menu_visible())
9807 .await;
9808 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9809 assert!(request.next().await.is_some());
9810 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9811
9812 cx.simulate_keystroke("S");
9813 cx.simulate_keystroke("o");
9814 cx.simulate_keystroke("m");
9815 cx.condition(|editor, _| editor.context_menu_visible())
9816 .await;
9817 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9818 assert!(request.next().await.is_some());
9819 assert!(request.next().await.is_some());
9820 assert!(request.next().await.is_some());
9821 request.close();
9822 assert!(request.next().await.is_none());
9823 assert_eq!(
9824 counter.load(atomic::Ordering::Acquire),
9825 4,
9826 "With the completions menu open, only one LSP request should happen per input"
9827 );
9828}
9829
9830#[gpui::test]
9831async fn test_toggle_comment(cx: &mut TestAppContext) {
9832 init_test(cx, |_| {});
9833 let mut cx = EditorTestContext::new(cx).await;
9834 let language = Arc::new(Language::new(
9835 LanguageConfig {
9836 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9837 ..Default::default()
9838 },
9839 Some(tree_sitter_rust::LANGUAGE.into()),
9840 ));
9841 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9842
9843 // If multiple selections intersect a line, the line is only toggled once.
9844 cx.set_state(indoc! {"
9845 fn a() {
9846 «//b();
9847 ˇ»// «c();
9848 //ˇ» d();
9849 }
9850 "});
9851
9852 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9853
9854 cx.assert_editor_state(indoc! {"
9855 fn a() {
9856 «b();
9857 c();
9858 ˇ» d();
9859 }
9860 "});
9861
9862 // The comment prefix is inserted at the same column for every line in a
9863 // selection.
9864 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9865
9866 cx.assert_editor_state(indoc! {"
9867 fn a() {
9868 // «b();
9869 // c();
9870 ˇ»// d();
9871 }
9872 "});
9873
9874 // If a selection ends at the beginning of a line, that line is not toggled.
9875 cx.set_selections_state(indoc! {"
9876 fn a() {
9877 // b();
9878 «// c();
9879 ˇ» // d();
9880 }
9881 "});
9882
9883 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9884
9885 cx.assert_editor_state(indoc! {"
9886 fn a() {
9887 // b();
9888 «c();
9889 ˇ» // d();
9890 }
9891 "});
9892
9893 // If a selection span a single line and is empty, the line is toggled.
9894 cx.set_state(indoc! {"
9895 fn a() {
9896 a();
9897 b();
9898 ˇ
9899 }
9900 "});
9901
9902 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9903
9904 cx.assert_editor_state(indoc! {"
9905 fn a() {
9906 a();
9907 b();
9908 //•ˇ
9909 }
9910 "});
9911
9912 // If a selection span multiple lines, empty lines are not toggled.
9913 cx.set_state(indoc! {"
9914 fn a() {
9915 «a();
9916
9917 c();ˇ»
9918 }
9919 "});
9920
9921 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9922
9923 cx.assert_editor_state(indoc! {"
9924 fn a() {
9925 // «a();
9926
9927 // c();ˇ»
9928 }
9929 "});
9930
9931 // If a selection includes multiple comment prefixes, all lines are uncommented.
9932 cx.set_state(indoc! {"
9933 fn a() {
9934 «// a();
9935 /// b();
9936 //! c();ˇ»
9937 }
9938 "});
9939
9940 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9941
9942 cx.assert_editor_state(indoc! {"
9943 fn a() {
9944 «a();
9945 b();
9946 c();ˇ»
9947 }
9948 "});
9949}
9950
9951#[gpui::test]
9952async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9953 init_test(cx, |_| {});
9954 let mut cx = EditorTestContext::new(cx).await;
9955 let language = Arc::new(Language::new(
9956 LanguageConfig {
9957 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9958 ..Default::default()
9959 },
9960 Some(tree_sitter_rust::LANGUAGE.into()),
9961 ));
9962 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9963
9964 let toggle_comments = &ToggleComments {
9965 advance_downwards: false,
9966 ignore_indent: true,
9967 };
9968
9969 // If multiple selections intersect a line, the line is only toggled once.
9970 cx.set_state(indoc! {"
9971 fn a() {
9972 // «b();
9973 // c();
9974 // ˇ» d();
9975 }
9976 "});
9977
9978 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9979
9980 cx.assert_editor_state(indoc! {"
9981 fn a() {
9982 «b();
9983 c();
9984 ˇ» d();
9985 }
9986 "});
9987
9988 // The comment prefix is inserted at the beginning of each line
9989 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9990
9991 cx.assert_editor_state(indoc! {"
9992 fn a() {
9993 // «b();
9994 // c();
9995 // ˇ» d();
9996 }
9997 "});
9998
9999 // If a selection ends at the beginning of a line, that line is not toggled.
10000 cx.set_selections_state(indoc! {"
10001 fn a() {
10002 // b();
10003 // «c();
10004 ˇ»// d();
10005 }
10006 "});
10007
10008 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10009
10010 cx.assert_editor_state(indoc! {"
10011 fn a() {
10012 // b();
10013 «c();
10014 ˇ»// d();
10015 }
10016 "});
10017
10018 // If a selection span a single line and is empty, the line is toggled.
10019 cx.set_state(indoc! {"
10020 fn a() {
10021 a();
10022 b();
10023 ˇ
10024 }
10025 "});
10026
10027 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10028
10029 cx.assert_editor_state(indoc! {"
10030 fn a() {
10031 a();
10032 b();
10033 //ˇ
10034 }
10035 "});
10036
10037 // If a selection span multiple lines, empty lines are not toggled.
10038 cx.set_state(indoc! {"
10039 fn a() {
10040 «a();
10041
10042 c();ˇ»
10043 }
10044 "});
10045
10046 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10047
10048 cx.assert_editor_state(indoc! {"
10049 fn a() {
10050 // «a();
10051
10052 // c();ˇ»
10053 }
10054 "});
10055
10056 // If a selection includes multiple comment prefixes, all lines are uncommented.
10057 cx.set_state(indoc! {"
10058 fn a() {
10059 // «a();
10060 /// b();
10061 //! c();ˇ»
10062 }
10063 "});
10064
10065 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10066
10067 cx.assert_editor_state(indoc! {"
10068 fn a() {
10069 «a();
10070 b();
10071 c();ˇ»
10072 }
10073 "});
10074}
10075
10076#[gpui::test]
10077async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10078 init_test(cx, |_| {});
10079
10080 let language = Arc::new(Language::new(
10081 LanguageConfig {
10082 line_comments: vec!["// ".into()],
10083 ..Default::default()
10084 },
10085 Some(tree_sitter_rust::LANGUAGE.into()),
10086 ));
10087
10088 let mut cx = EditorTestContext::new(cx).await;
10089
10090 cx.language_registry().add(language.clone());
10091 cx.update_buffer(|buffer, cx| {
10092 buffer.set_language(Some(language), cx);
10093 });
10094
10095 let toggle_comments = &ToggleComments {
10096 advance_downwards: true,
10097 ignore_indent: false,
10098 };
10099
10100 // Single cursor on one line -> advance
10101 // Cursor moves horizontally 3 characters as well on non-blank line
10102 cx.set_state(indoc!(
10103 "fn a() {
10104 ˇdog();
10105 cat();
10106 }"
10107 ));
10108 cx.update_editor(|editor, window, cx| {
10109 editor.toggle_comments(toggle_comments, window, cx);
10110 });
10111 cx.assert_editor_state(indoc!(
10112 "fn a() {
10113 // dog();
10114 catˇ();
10115 }"
10116 ));
10117
10118 // Single selection on one line -> don't advance
10119 cx.set_state(indoc!(
10120 "fn a() {
10121 «dog()ˇ»;
10122 cat();
10123 }"
10124 ));
10125 cx.update_editor(|editor, window, cx| {
10126 editor.toggle_comments(toggle_comments, window, cx);
10127 });
10128 cx.assert_editor_state(indoc!(
10129 "fn a() {
10130 // «dog()ˇ»;
10131 cat();
10132 }"
10133 ));
10134
10135 // Multiple cursors on one line -> advance
10136 cx.set_state(indoc!(
10137 "fn a() {
10138 ˇdˇog();
10139 cat();
10140 }"
10141 ));
10142 cx.update_editor(|editor, window, cx| {
10143 editor.toggle_comments(toggle_comments, window, cx);
10144 });
10145 cx.assert_editor_state(indoc!(
10146 "fn a() {
10147 // dog();
10148 catˇ(ˇ);
10149 }"
10150 ));
10151
10152 // Multiple cursors on one line, with selection -> don't advance
10153 cx.set_state(indoc!(
10154 "fn a() {
10155 ˇdˇog«()ˇ»;
10156 cat();
10157 }"
10158 ));
10159 cx.update_editor(|editor, window, cx| {
10160 editor.toggle_comments(toggle_comments, window, cx);
10161 });
10162 cx.assert_editor_state(indoc!(
10163 "fn a() {
10164 // ˇdˇog«()ˇ»;
10165 cat();
10166 }"
10167 ));
10168
10169 // Single cursor on one line -> advance
10170 // Cursor moves to column 0 on blank line
10171 cx.set_state(indoc!(
10172 "fn a() {
10173 ˇdog();
10174
10175 cat();
10176 }"
10177 ));
10178 cx.update_editor(|editor, window, cx| {
10179 editor.toggle_comments(toggle_comments, window, cx);
10180 });
10181 cx.assert_editor_state(indoc!(
10182 "fn a() {
10183 // dog();
10184 ˇ
10185 cat();
10186 }"
10187 ));
10188
10189 // Single cursor on one line -> advance
10190 // Cursor starts and ends at column 0
10191 cx.set_state(indoc!(
10192 "fn a() {
10193 ˇ dog();
10194 cat();
10195 }"
10196 ));
10197 cx.update_editor(|editor, window, cx| {
10198 editor.toggle_comments(toggle_comments, window, cx);
10199 });
10200 cx.assert_editor_state(indoc!(
10201 "fn a() {
10202 // dog();
10203 ˇ cat();
10204 }"
10205 ));
10206}
10207
10208#[gpui::test]
10209async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10210 init_test(cx, |_| {});
10211
10212 let mut cx = EditorTestContext::new(cx).await;
10213
10214 let html_language = Arc::new(
10215 Language::new(
10216 LanguageConfig {
10217 name: "HTML".into(),
10218 block_comment: Some(("<!-- ".into(), " -->".into())),
10219 ..Default::default()
10220 },
10221 Some(tree_sitter_html::LANGUAGE.into()),
10222 )
10223 .with_injection_query(
10224 r#"
10225 (script_element
10226 (raw_text) @injection.content
10227 (#set! injection.language "javascript"))
10228 "#,
10229 )
10230 .unwrap(),
10231 );
10232
10233 let javascript_language = Arc::new(Language::new(
10234 LanguageConfig {
10235 name: "JavaScript".into(),
10236 line_comments: vec!["// ".into()],
10237 ..Default::default()
10238 },
10239 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10240 ));
10241
10242 cx.language_registry().add(html_language.clone());
10243 cx.language_registry().add(javascript_language.clone());
10244 cx.update_buffer(|buffer, cx| {
10245 buffer.set_language(Some(html_language), cx);
10246 });
10247
10248 // Toggle comments for empty selections
10249 cx.set_state(
10250 &r#"
10251 <p>A</p>ˇ
10252 <p>B</p>ˇ
10253 <p>C</p>ˇ
10254 "#
10255 .unindent(),
10256 );
10257 cx.update_editor(|editor, window, cx| {
10258 editor.toggle_comments(&ToggleComments::default(), window, cx)
10259 });
10260 cx.assert_editor_state(
10261 &r#"
10262 <!-- <p>A</p>ˇ -->
10263 <!-- <p>B</p>ˇ -->
10264 <!-- <p>C</p>ˇ -->
10265 "#
10266 .unindent(),
10267 );
10268 cx.update_editor(|editor, window, cx| {
10269 editor.toggle_comments(&ToggleComments::default(), window, cx)
10270 });
10271 cx.assert_editor_state(
10272 &r#"
10273 <p>A</p>ˇ
10274 <p>B</p>ˇ
10275 <p>C</p>ˇ
10276 "#
10277 .unindent(),
10278 );
10279
10280 // Toggle comments for mixture of empty and non-empty selections, where
10281 // multiple selections occupy a given line.
10282 cx.set_state(
10283 &r#"
10284 <p>A«</p>
10285 <p>ˇ»B</p>ˇ
10286 <p>C«</p>
10287 <p>ˇ»D</p>ˇ
10288 "#
10289 .unindent(),
10290 );
10291
10292 cx.update_editor(|editor, window, cx| {
10293 editor.toggle_comments(&ToggleComments::default(), window, cx)
10294 });
10295 cx.assert_editor_state(
10296 &r#"
10297 <!-- <p>A«</p>
10298 <p>ˇ»B</p>ˇ -->
10299 <!-- <p>C«</p>
10300 <p>ˇ»D</p>ˇ -->
10301 "#
10302 .unindent(),
10303 );
10304 cx.update_editor(|editor, window, cx| {
10305 editor.toggle_comments(&ToggleComments::default(), window, cx)
10306 });
10307 cx.assert_editor_state(
10308 &r#"
10309 <p>A«</p>
10310 <p>ˇ»B</p>ˇ
10311 <p>C«</p>
10312 <p>ˇ»D</p>ˇ
10313 "#
10314 .unindent(),
10315 );
10316
10317 // Toggle comments when different languages are active for different
10318 // selections.
10319 cx.set_state(
10320 &r#"
10321 ˇ<script>
10322 ˇvar x = new Y();
10323 ˇ</script>
10324 "#
10325 .unindent(),
10326 );
10327 cx.executor().run_until_parked();
10328 cx.update_editor(|editor, window, cx| {
10329 editor.toggle_comments(&ToggleComments::default(), window, cx)
10330 });
10331 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10332 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10333 cx.assert_editor_state(
10334 &r#"
10335 <!-- ˇ<script> -->
10336 // ˇvar x = new Y();
10337 <!-- ˇ</script> -->
10338 "#
10339 .unindent(),
10340 );
10341}
10342
10343#[gpui::test]
10344fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10345 init_test(cx, |_| {});
10346
10347 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10348 let multibuffer = cx.new(|cx| {
10349 let mut multibuffer = MultiBuffer::new(ReadWrite);
10350 multibuffer.push_excerpts(
10351 buffer.clone(),
10352 [
10353 ExcerptRange {
10354 context: Point::new(0, 0)..Point::new(0, 4),
10355 primary: None,
10356 },
10357 ExcerptRange {
10358 context: Point::new(1, 0)..Point::new(1, 4),
10359 primary: None,
10360 },
10361 ],
10362 cx,
10363 );
10364 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10365 multibuffer
10366 });
10367
10368 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10369 editor.update_in(cx, |editor, window, cx| {
10370 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10371 editor.change_selections(None, window, cx, |s| {
10372 s.select_ranges([
10373 Point::new(0, 0)..Point::new(0, 0),
10374 Point::new(1, 0)..Point::new(1, 0),
10375 ])
10376 });
10377
10378 editor.handle_input("X", window, cx);
10379 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10380 assert_eq!(
10381 editor.selections.ranges(cx),
10382 [
10383 Point::new(0, 1)..Point::new(0, 1),
10384 Point::new(1, 1)..Point::new(1, 1),
10385 ]
10386 );
10387
10388 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10389 editor.change_selections(None, window, cx, |s| {
10390 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10391 });
10392 editor.backspace(&Default::default(), window, cx);
10393 assert_eq!(editor.text(cx), "Xa\nbbb");
10394 assert_eq!(
10395 editor.selections.ranges(cx),
10396 [Point::new(1, 0)..Point::new(1, 0)]
10397 );
10398
10399 editor.change_selections(None, window, cx, |s| {
10400 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10401 });
10402 editor.backspace(&Default::default(), window, cx);
10403 assert_eq!(editor.text(cx), "X\nbb");
10404 assert_eq!(
10405 editor.selections.ranges(cx),
10406 [Point::new(0, 1)..Point::new(0, 1)]
10407 );
10408 });
10409}
10410
10411#[gpui::test]
10412fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10413 init_test(cx, |_| {});
10414
10415 let markers = vec![('[', ']').into(), ('(', ')').into()];
10416 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10417 indoc! {"
10418 [aaaa
10419 (bbbb]
10420 cccc)",
10421 },
10422 markers.clone(),
10423 );
10424 let excerpt_ranges = markers.into_iter().map(|marker| {
10425 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10426 ExcerptRange {
10427 context,
10428 primary: None,
10429 }
10430 });
10431 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10432 let multibuffer = cx.new(|cx| {
10433 let mut multibuffer = MultiBuffer::new(ReadWrite);
10434 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10435 multibuffer
10436 });
10437
10438 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10439 editor.update_in(cx, |editor, window, cx| {
10440 let (expected_text, selection_ranges) = marked_text_ranges(
10441 indoc! {"
10442 aaaa
10443 bˇbbb
10444 bˇbbˇb
10445 cccc"
10446 },
10447 true,
10448 );
10449 assert_eq!(editor.text(cx), expected_text);
10450 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10451
10452 editor.handle_input("X", window, cx);
10453
10454 let (expected_text, expected_selections) = marked_text_ranges(
10455 indoc! {"
10456 aaaa
10457 bXˇbbXb
10458 bXˇbbXˇb
10459 cccc"
10460 },
10461 false,
10462 );
10463 assert_eq!(editor.text(cx), expected_text);
10464 assert_eq!(editor.selections.ranges(cx), expected_selections);
10465
10466 editor.newline(&Newline, window, cx);
10467 let (expected_text, expected_selections) = marked_text_ranges(
10468 indoc! {"
10469 aaaa
10470 bX
10471 ˇbbX
10472 b
10473 bX
10474 ˇbbX
10475 ˇb
10476 cccc"
10477 },
10478 false,
10479 );
10480 assert_eq!(editor.text(cx), expected_text);
10481 assert_eq!(editor.selections.ranges(cx), expected_selections);
10482 });
10483}
10484
10485#[gpui::test]
10486fn test_refresh_selections(cx: &mut TestAppContext) {
10487 init_test(cx, |_| {});
10488
10489 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10490 let mut excerpt1_id = None;
10491 let multibuffer = cx.new(|cx| {
10492 let mut multibuffer = MultiBuffer::new(ReadWrite);
10493 excerpt1_id = multibuffer
10494 .push_excerpts(
10495 buffer.clone(),
10496 [
10497 ExcerptRange {
10498 context: Point::new(0, 0)..Point::new(1, 4),
10499 primary: None,
10500 },
10501 ExcerptRange {
10502 context: Point::new(1, 0)..Point::new(2, 4),
10503 primary: None,
10504 },
10505 ],
10506 cx,
10507 )
10508 .into_iter()
10509 .next();
10510 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10511 multibuffer
10512 });
10513
10514 let editor = cx.add_window(|window, cx| {
10515 let mut editor = build_editor(multibuffer.clone(), window, cx);
10516 let snapshot = editor.snapshot(window, cx);
10517 editor.change_selections(None, window, cx, |s| {
10518 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10519 });
10520 editor.begin_selection(
10521 Point::new(2, 1).to_display_point(&snapshot),
10522 true,
10523 1,
10524 window,
10525 cx,
10526 );
10527 assert_eq!(
10528 editor.selections.ranges(cx),
10529 [
10530 Point::new(1, 3)..Point::new(1, 3),
10531 Point::new(2, 1)..Point::new(2, 1),
10532 ]
10533 );
10534 editor
10535 });
10536
10537 // Refreshing selections is a no-op when excerpts haven't changed.
10538 _ = editor.update(cx, |editor, window, cx| {
10539 editor.change_selections(None, window, cx, |s| s.refresh());
10540 assert_eq!(
10541 editor.selections.ranges(cx),
10542 [
10543 Point::new(1, 3)..Point::new(1, 3),
10544 Point::new(2, 1)..Point::new(2, 1),
10545 ]
10546 );
10547 });
10548
10549 multibuffer.update(cx, |multibuffer, cx| {
10550 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10551 });
10552 _ = editor.update(cx, |editor, window, cx| {
10553 // Removing an excerpt causes the first selection to become degenerate.
10554 assert_eq!(
10555 editor.selections.ranges(cx),
10556 [
10557 Point::new(0, 0)..Point::new(0, 0),
10558 Point::new(0, 1)..Point::new(0, 1)
10559 ]
10560 );
10561
10562 // Refreshing selections will relocate the first selection to the original buffer
10563 // location.
10564 editor.change_selections(None, window, cx, |s| s.refresh());
10565 assert_eq!(
10566 editor.selections.ranges(cx),
10567 [
10568 Point::new(0, 1)..Point::new(0, 1),
10569 Point::new(0, 3)..Point::new(0, 3)
10570 ]
10571 );
10572 assert!(editor.selections.pending_anchor().is_some());
10573 });
10574}
10575
10576#[gpui::test]
10577fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10578 init_test(cx, |_| {});
10579
10580 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10581 let mut excerpt1_id = None;
10582 let multibuffer = cx.new(|cx| {
10583 let mut multibuffer = MultiBuffer::new(ReadWrite);
10584 excerpt1_id = multibuffer
10585 .push_excerpts(
10586 buffer.clone(),
10587 [
10588 ExcerptRange {
10589 context: Point::new(0, 0)..Point::new(1, 4),
10590 primary: None,
10591 },
10592 ExcerptRange {
10593 context: Point::new(1, 0)..Point::new(2, 4),
10594 primary: None,
10595 },
10596 ],
10597 cx,
10598 )
10599 .into_iter()
10600 .next();
10601 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10602 multibuffer
10603 });
10604
10605 let editor = cx.add_window(|window, cx| {
10606 let mut editor = build_editor(multibuffer.clone(), window, cx);
10607 let snapshot = editor.snapshot(window, cx);
10608 editor.begin_selection(
10609 Point::new(1, 3).to_display_point(&snapshot),
10610 false,
10611 1,
10612 window,
10613 cx,
10614 );
10615 assert_eq!(
10616 editor.selections.ranges(cx),
10617 [Point::new(1, 3)..Point::new(1, 3)]
10618 );
10619 editor
10620 });
10621
10622 multibuffer.update(cx, |multibuffer, cx| {
10623 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10624 });
10625 _ = editor.update(cx, |editor, window, cx| {
10626 assert_eq!(
10627 editor.selections.ranges(cx),
10628 [Point::new(0, 0)..Point::new(0, 0)]
10629 );
10630
10631 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10632 editor.change_selections(None, window, cx, |s| s.refresh());
10633 assert_eq!(
10634 editor.selections.ranges(cx),
10635 [Point::new(0, 3)..Point::new(0, 3)]
10636 );
10637 assert!(editor.selections.pending_anchor().is_some());
10638 });
10639}
10640
10641#[gpui::test]
10642async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10643 init_test(cx, |_| {});
10644
10645 let language = Arc::new(
10646 Language::new(
10647 LanguageConfig {
10648 brackets: BracketPairConfig {
10649 pairs: vec![
10650 BracketPair {
10651 start: "{".to_string(),
10652 end: "}".to_string(),
10653 close: true,
10654 surround: true,
10655 newline: true,
10656 },
10657 BracketPair {
10658 start: "/* ".to_string(),
10659 end: " */".to_string(),
10660 close: true,
10661 surround: true,
10662 newline: true,
10663 },
10664 ],
10665 ..Default::default()
10666 },
10667 ..Default::default()
10668 },
10669 Some(tree_sitter_rust::LANGUAGE.into()),
10670 )
10671 .with_indents_query("")
10672 .unwrap(),
10673 );
10674
10675 let text = concat!(
10676 "{ }\n", //
10677 " x\n", //
10678 " /* */\n", //
10679 "x\n", //
10680 "{{} }\n", //
10681 );
10682
10683 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10685 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10686 editor
10687 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10688 .await;
10689
10690 editor.update_in(cx, |editor, window, cx| {
10691 editor.change_selections(None, window, cx, |s| {
10692 s.select_display_ranges([
10693 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10694 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10695 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10696 ])
10697 });
10698 editor.newline(&Newline, window, cx);
10699
10700 assert_eq!(
10701 editor.buffer().read(cx).read(cx).text(),
10702 concat!(
10703 "{ \n", // Suppress rustfmt
10704 "\n", //
10705 "}\n", //
10706 " x\n", //
10707 " /* \n", //
10708 " \n", //
10709 " */\n", //
10710 "x\n", //
10711 "{{} \n", //
10712 "}\n", //
10713 )
10714 );
10715 });
10716}
10717
10718#[gpui::test]
10719fn test_highlighted_ranges(cx: &mut TestAppContext) {
10720 init_test(cx, |_| {});
10721
10722 let editor = cx.add_window(|window, cx| {
10723 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10724 build_editor(buffer.clone(), window, cx)
10725 });
10726
10727 _ = editor.update(cx, |editor, window, cx| {
10728 struct Type1;
10729 struct Type2;
10730
10731 let buffer = editor.buffer.read(cx).snapshot(cx);
10732
10733 let anchor_range =
10734 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10735
10736 editor.highlight_background::<Type1>(
10737 &[
10738 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10739 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10740 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10741 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10742 ],
10743 |_| Hsla::red(),
10744 cx,
10745 );
10746 editor.highlight_background::<Type2>(
10747 &[
10748 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10749 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10750 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10751 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10752 ],
10753 |_| Hsla::green(),
10754 cx,
10755 );
10756
10757 let snapshot = editor.snapshot(window, cx);
10758 let mut highlighted_ranges = editor.background_highlights_in_range(
10759 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10760 &snapshot,
10761 cx.theme().colors(),
10762 );
10763 // Enforce a consistent ordering based on color without relying on the ordering of the
10764 // highlight's `TypeId` which is non-executor.
10765 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10766 assert_eq!(
10767 highlighted_ranges,
10768 &[
10769 (
10770 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10771 Hsla::red(),
10772 ),
10773 (
10774 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10775 Hsla::red(),
10776 ),
10777 (
10778 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10779 Hsla::green(),
10780 ),
10781 (
10782 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10783 Hsla::green(),
10784 ),
10785 ]
10786 );
10787 assert_eq!(
10788 editor.background_highlights_in_range(
10789 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10790 &snapshot,
10791 cx.theme().colors(),
10792 ),
10793 &[(
10794 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10795 Hsla::red(),
10796 )]
10797 );
10798 });
10799}
10800
10801#[gpui::test]
10802async fn test_following(cx: &mut TestAppContext) {
10803 init_test(cx, |_| {});
10804
10805 let fs = FakeFs::new(cx.executor());
10806 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10807
10808 let buffer = project.update(cx, |project, cx| {
10809 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10810 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10811 });
10812 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10813 let follower = cx.update(|cx| {
10814 cx.open_window(
10815 WindowOptions {
10816 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10817 gpui::Point::new(px(0.), px(0.)),
10818 gpui::Point::new(px(10.), px(80.)),
10819 ))),
10820 ..Default::default()
10821 },
10822 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10823 )
10824 .unwrap()
10825 });
10826
10827 let is_still_following = Rc::new(RefCell::new(true));
10828 let follower_edit_event_count = Rc::new(RefCell::new(0));
10829 let pending_update = Rc::new(RefCell::new(None));
10830 let leader_entity = leader.root(cx).unwrap();
10831 let follower_entity = follower.root(cx).unwrap();
10832 _ = follower.update(cx, {
10833 let update = pending_update.clone();
10834 let is_still_following = is_still_following.clone();
10835 let follower_edit_event_count = follower_edit_event_count.clone();
10836 |_, window, cx| {
10837 cx.subscribe_in(
10838 &leader_entity,
10839 window,
10840 move |_, leader, event, window, cx| {
10841 leader.read(cx).add_event_to_update_proto(
10842 event,
10843 &mut update.borrow_mut(),
10844 window,
10845 cx,
10846 );
10847 },
10848 )
10849 .detach();
10850
10851 cx.subscribe_in(
10852 &follower_entity,
10853 window,
10854 move |_, _, event: &EditorEvent, _window, _cx| {
10855 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10856 *is_still_following.borrow_mut() = false;
10857 }
10858
10859 if let EditorEvent::BufferEdited = event {
10860 *follower_edit_event_count.borrow_mut() += 1;
10861 }
10862 },
10863 )
10864 .detach();
10865 }
10866 });
10867
10868 // Update the selections only
10869 _ = leader.update(cx, |leader, window, cx| {
10870 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10871 });
10872 follower
10873 .update(cx, |follower, window, cx| {
10874 follower.apply_update_proto(
10875 &project,
10876 pending_update.borrow_mut().take().unwrap(),
10877 window,
10878 cx,
10879 )
10880 })
10881 .unwrap()
10882 .await
10883 .unwrap();
10884 _ = follower.update(cx, |follower, _, cx| {
10885 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10886 });
10887 assert!(*is_still_following.borrow());
10888 assert_eq!(*follower_edit_event_count.borrow(), 0);
10889
10890 // Update the scroll position only
10891 _ = leader.update(cx, |leader, window, cx| {
10892 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10893 });
10894 follower
10895 .update(cx, |follower, window, cx| {
10896 follower.apply_update_proto(
10897 &project,
10898 pending_update.borrow_mut().take().unwrap(),
10899 window,
10900 cx,
10901 )
10902 })
10903 .unwrap()
10904 .await
10905 .unwrap();
10906 assert_eq!(
10907 follower
10908 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10909 .unwrap(),
10910 gpui::Point::new(1.5, 3.5)
10911 );
10912 assert!(*is_still_following.borrow());
10913 assert_eq!(*follower_edit_event_count.borrow(), 0);
10914
10915 // Update the selections and scroll position. The follower's scroll position is updated
10916 // via autoscroll, not via the leader's exact scroll position.
10917 _ = leader.update(cx, |leader, window, cx| {
10918 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10919 leader.request_autoscroll(Autoscroll::newest(), cx);
10920 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10921 });
10922 follower
10923 .update(cx, |follower, window, cx| {
10924 follower.apply_update_proto(
10925 &project,
10926 pending_update.borrow_mut().take().unwrap(),
10927 window,
10928 cx,
10929 )
10930 })
10931 .unwrap()
10932 .await
10933 .unwrap();
10934 _ = follower.update(cx, |follower, _, cx| {
10935 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10936 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10937 });
10938 assert!(*is_still_following.borrow());
10939
10940 // Creating a pending selection that precedes another selection
10941 _ = leader.update(cx, |leader, window, cx| {
10942 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10943 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10944 });
10945 follower
10946 .update(cx, |follower, window, cx| {
10947 follower.apply_update_proto(
10948 &project,
10949 pending_update.borrow_mut().take().unwrap(),
10950 window,
10951 cx,
10952 )
10953 })
10954 .unwrap()
10955 .await
10956 .unwrap();
10957 _ = follower.update(cx, |follower, _, cx| {
10958 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10959 });
10960 assert!(*is_still_following.borrow());
10961
10962 // Extend the pending selection so that it surrounds another selection
10963 _ = leader.update(cx, |leader, window, cx| {
10964 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10965 });
10966 follower
10967 .update(cx, |follower, window, cx| {
10968 follower.apply_update_proto(
10969 &project,
10970 pending_update.borrow_mut().take().unwrap(),
10971 window,
10972 cx,
10973 )
10974 })
10975 .unwrap()
10976 .await
10977 .unwrap();
10978 _ = follower.update(cx, |follower, _, cx| {
10979 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10980 });
10981
10982 // Scrolling locally breaks the follow
10983 _ = follower.update(cx, |follower, window, cx| {
10984 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10985 follower.set_scroll_anchor(
10986 ScrollAnchor {
10987 anchor: top_anchor,
10988 offset: gpui::Point::new(0.0, 0.5),
10989 },
10990 window,
10991 cx,
10992 );
10993 });
10994 assert!(!(*is_still_following.borrow()));
10995}
10996
10997#[gpui::test]
10998async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10999 init_test(cx, |_| {});
11000
11001 let fs = FakeFs::new(cx.executor());
11002 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11003 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11004 let pane = workspace
11005 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11006 .unwrap();
11007
11008 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11009
11010 let leader = pane.update_in(cx, |_, window, cx| {
11011 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11012 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11013 });
11014
11015 // Start following the editor when it has no excerpts.
11016 let mut state_message =
11017 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11018 let workspace_entity = workspace.root(cx).unwrap();
11019 let follower_1 = cx
11020 .update_window(*workspace.deref(), |_, window, cx| {
11021 Editor::from_state_proto(
11022 workspace_entity,
11023 ViewId {
11024 creator: Default::default(),
11025 id: 0,
11026 },
11027 &mut state_message,
11028 window,
11029 cx,
11030 )
11031 })
11032 .unwrap()
11033 .unwrap()
11034 .await
11035 .unwrap();
11036
11037 let update_message = Rc::new(RefCell::new(None));
11038 follower_1.update_in(cx, {
11039 let update = update_message.clone();
11040 |_, window, cx| {
11041 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11042 leader.read(cx).add_event_to_update_proto(
11043 event,
11044 &mut update.borrow_mut(),
11045 window,
11046 cx,
11047 );
11048 })
11049 .detach();
11050 }
11051 });
11052
11053 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11054 (
11055 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11056 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11057 )
11058 });
11059
11060 // Insert some excerpts.
11061 leader.update(cx, |leader, cx| {
11062 leader.buffer.update(cx, |multibuffer, cx| {
11063 let excerpt_ids = multibuffer.push_excerpts(
11064 buffer_1.clone(),
11065 [
11066 ExcerptRange {
11067 context: 1..6,
11068 primary: None,
11069 },
11070 ExcerptRange {
11071 context: 12..15,
11072 primary: None,
11073 },
11074 ExcerptRange {
11075 context: 0..3,
11076 primary: None,
11077 },
11078 ],
11079 cx,
11080 );
11081 multibuffer.insert_excerpts_after(
11082 excerpt_ids[0],
11083 buffer_2.clone(),
11084 [
11085 ExcerptRange {
11086 context: 8..12,
11087 primary: None,
11088 },
11089 ExcerptRange {
11090 context: 0..6,
11091 primary: None,
11092 },
11093 ],
11094 cx,
11095 );
11096 });
11097 });
11098
11099 // Apply the update of adding the excerpts.
11100 follower_1
11101 .update_in(cx, |follower, window, cx| {
11102 follower.apply_update_proto(
11103 &project,
11104 update_message.borrow().clone().unwrap(),
11105 window,
11106 cx,
11107 )
11108 })
11109 .await
11110 .unwrap();
11111 assert_eq!(
11112 follower_1.update(cx, |editor, cx| editor.text(cx)),
11113 leader.update(cx, |editor, cx| editor.text(cx))
11114 );
11115 update_message.borrow_mut().take();
11116
11117 // Start following separately after it already has excerpts.
11118 let mut state_message =
11119 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11120 let workspace_entity = workspace.root(cx).unwrap();
11121 let follower_2 = cx
11122 .update_window(*workspace.deref(), |_, window, cx| {
11123 Editor::from_state_proto(
11124 workspace_entity,
11125 ViewId {
11126 creator: Default::default(),
11127 id: 0,
11128 },
11129 &mut state_message,
11130 window,
11131 cx,
11132 )
11133 })
11134 .unwrap()
11135 .unwrap()
11136 .await
11137 .unwrap();
11138 assert_eq!(
11139 follower_2.update(cx, |editor, cx| editor.text(cx)),
11140 leader.update(cx, |editor, cx| editor.text(cx))
11141 );
11142
11143 // Remove some excerpts.
11144 leader.update(cx, |leader, cx| {
11145 leader.buffer.update(cx, |multibuffer, cx| {
11146 let excerpt_ids = multibuffer.excerpt_ids();
11147 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11148 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11149 });
11150 });
11151
11152 // Apply the update of removing the excerpts.
11153 follower_1
11154 .update_in(cx, |follower, window, cx| {
11155 follower.apply_update_proto(
11156 &project,
11157 update_message.borrow().clone().unwrap(),
11158 window,
11159 cx,
11160 )
11161 })
11162 .await
11163 .unwrap();
11164 follower_2
11165 .update_in(cx, |follower, window, cx| {
11166 follower.apply_update_proto(
11167 &project,
11168 update_message.borrow().clone().unwrap(),
11169 window,
11170 cx,
11171 )
11172 })
11173 .await
11174 .unwrap();
11175 update_message.borrow_mut().take();
11176 assert_eq!(
11177 follower_1.update(cx, |editor, cx| editor.text(cx)),
11178 leader.update(cx, |editor, cx| editor.text(cx))
11179 );
11180}
11181
11182#[gpui::test]
11183async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11184 init_test(cx, |_| {});
11185
11186 let mut cx = EditorTestContext::new(cx).await;
11187 let lsp_store =
11188 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11189
11190 cx.set_state(indoc! {"
11191 ˇfn func(abc def: i32) -> u32 {
11192 }
11193 "});
11194
11195 cx.update(|_, cx| {
11196 lsp_store.update(cx, |lsp_store, cx| {
11197 lsp_store
11198 .update_diagnostics(
11199 LanguageServerId(0),
11200 lsp::PublishDiagnosticsParams {
11201 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11202 version: None,
11203 diagnostics: vec![
11204 lsp::Diagnostic {
11205 range: lsp::Range::new(
11206 lsp::Position::new(0, 11),
11207 lsp::Position::new(0, 12),
11208 ),
11209 severity: Some(lsp::DiagnosticSeverity::ERROR),
11210 ..Default::default()
11211 },
11212 lsp::Diagnostic {
11213 range: lsp::Range::new(
11214 lsp::Position::new(0, 12),
11215 lsp::Position::new(0, 15),
11216 ),
11217 severity: Some(lsp::DiagnosticSeverity::ERROR),
11218 ..Default::default()
11219 },
11220 lsp::Diagnostic {
11221 range: lsp::Range::new(
11222 lsp::Position::new(0, 25),
11223 lsp::Position::new(0, 28),
11224 ),
11225 severity: Some(lsp::DiagnosticSeverity::ERROR),
11226 ..Default::default()
11227 },
11228 ],
11229 },
11230 &[],
11231 cx,
11232 )
11233 .unwrap()
11234 });
11235 });
11236
11237 executor.run_until_parked();
11238
11239 cx.update_editor(|editor, window, cx| {
11240 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11241 });
11242
11243 cx.assert_editor_state(indoc! {"
11244 fn func(abc def: i32) -> ˇu32 {
11245 }
11246 "});
11247
11248 cx.update_editor(|editor, window, cx| {
11249 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11250 });
11251
11252 cx.assert_editor_state(indoc! {"
11253 fn func(abc ˇdef: i32) -> u32 {
11254 }
11255 "});
11256
11257 cx.update_editor(|editor, window, cx| {
11258 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11259 });
11260
11261 cx.assert_editor_state(indoc! {"
11262 fn func(abcˇ def: i32) -> u32 {
11263 }
11264 "});
11265
11266 cx.update_editor(|editor, window, cx| {
11267 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11268 });
11269
11270 cx.assert_editor_state(indoc! {"
11271 fn func(abc def: i32) -> ˇu32 {
11272 }
11273 "});
11274}
11275
11276#[gpui::test]
11277async fn cycle_through_same_place_diagnostics(
11278 executor: BackgroundExecutor,
11279 cx: &mut TestAppContext,
11280) {
11281 init_test(cx, |_| {});
11282
11283 let mut cx = EditorTestContext::new(cx).await;
11284 let lsp_store =
11285 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11286
11287 cx.set_state(indoc! {"
11288 ˇfn func(abc def: i32) -> u32 {
11289 }
11290 "});
11291
11292 cx.update(|_, cx| {
11293 lsp_store.update(cx, |lsp_store, cx| {
11294 lsp_store
11295 .update_diagnostics(
11296 LanguageServerId(0),
11297 lsp::PublishDiagnosticsParams {
11298 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11299 version: None,
11300 diagnostics: vec![
11301 lsp::Diagnostic {
11302 range: lsp::Range::new(
11303 lsp::Position::new(0, 11),
11304 lsp::Position::new(0, 12),
11305 ),
11306 severity: Some(lsp::DiagnosticSeverity::ERROR),
11307 ..Default::default()
11308 },
11309 lsp::Diagnostic {
11310 range: lsp::Range::new(
11311 lsp::Position::new(0, 12),
11312 lsp::Position::new(0, 15),
11313 ),
11314 severity: Some(lsp::DiagnosticSeverity::ERROR),
11315 ..Default::default()
11316 },
11317 lsp::Diagnostic {
11318 range: lsp::Range::new(
11319 lsp::Position::new(0, 12),
11320 lsp::Position::new(0, 15),
11321 ),
11322 severity: Some(lsp::DiagnosticSeverity::ERROR),
11323 ..Default::default()
11324 },
11325 lsp::Diagnostic {
11326 range: lsp::Range::new(
11327 lsp::Position::new(0, 25),
11328 lsp::Position::new(0, 28),
11329 ),
11330 severity: Some(lsp::DiagnosticSeverity::ERROR),
11331 ..Default::default()
11332 },
11333 ],
11334 },
11335 &[],
11336 cx,
11337 )
11338 .unwrap()
11339 });
11340 });
11341 executor.run_until_parked();
11342
11343 //// Backward
11344
11345 // Fourth diagnostic
11346 cx.update_editor(|editor, window, cx| {
11347 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11348 });
11349 cx.assert_editor_state(indoc! {"
11350 fn func(abc def: i32) -> ˇu32 {
11351 }
11352 "});
11353
11354 // Third diagnostic
11355 cx.update_editor(|editor, window, cx| {
11356 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11357 });
11358 cx.assert_editor_state(indoc! {"
11359 fn func(abc ˇdef: i32) -> u32 {
11360 }
11361 "});
11362
11363 // Second diagnostic, same place
11364 cx.update_editor(|editor, window, cx| {
11365 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11366 });
11367 cx.assert_editor_state(indoc! {"
11368 fn func(abc ˇdef: i32) -> u32 {
11369 }
11370 "});
11371
11372 // First diagnostic
11373 cx.update_editor(|editor, window, cx| {
11374 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11375 });
11376 cx.assert_editor_state(indoc! {"
11377 fn func(abcˇ def: i32) -> u32 {
11378 }
11379 "});
11380
11381 // Wrapped over, fourth diagnostic
11382 cx.update_editor(|editor, window, cx| {
11383 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11384 });
11385 cx.assert_editor_state(indoc! {"
11386 fn func(abc def: i32) -> ˇu32 {
11387 }
11388 "});
11389
11390 cx.update_editor(|editor, window, cx| {
11391 editor.move_to_beginning(&MoveToBeginning, window, cx);
11392 });
11393 cx.assert_editor_state(indoc! {"
11394 ˇfn func(abc def: i32) -> u32 {
11395 }
11396 "});
11397
11398 //// Forward
11399
11400 // First diagnostic
11401 cx.update_editor(|editor, window, cx| {
11402 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11403 });
11404 cx.assert_editor_state(indoc! {"
11405 fn func(abcˇ def: i32) -> u32 {
11406 }
11407 "});
11408
11409 // Second diagnostic
11410 cx.update_editor(|editor, window, cx| {
11411 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11412 });
11413 cx.assert_editor_state(indoc! {"
11414 fn func(abc ˇdef: i32) -> u32 {
11415 }
11416 "});
11417
11418 // Third diagnostic, same place
11419 cx.update_editor(|editor, window, cx| {
11420 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11421 });
11422 cx.assert_editor_state(indoc! {"
11423 fn func(abc ˇdef: i32) -> u32 {
11424 }
11425 "});
11426
11427 // Fourth diagnostic
11428 cx.update_editor(|editor, window, cx| {
11429 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11430 });
11431 cx.assert_editor_state(indoc! {"
11432 fn func(abc def: i32) -> ˇu32 {
11433 }
11434 "});
11435
11436 // Wrapped around, first diagnostic
11437 cx.update_editor(|editor, window, cx| {
11438 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11439 });
11440 cx.assert_editor_state(indoc! {"
11441 fn func(abcˇ def: i32) -> u32 {
11442 }
11443 "});
11444}
11445
11446#[gpui::test]
11447async fn active_diagnostics_dismiss_after_invalidation(
11448 executor: BackgroundExecutor,
11449 cx: &mut TestAppContext,
11450) {
11451 init_test(cx, |_| {});
11452
11453 let mut cx = EditorTestContext::new(cx).await;
11454 let lsp_store =
11455 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11456
11457 cx.set_state(indoc! {"
11458 ˇfn func(abc def: i32) -> u32 {
11459 }
11460 "});
11461
11462 let message = "Something's wrong!";
11463 cx.update(|_, cx| {
11464 lsp_store.update(cx, |lsp_store, cx| {
11465 lsp_store
11466 .update_diagnostics(
11467 LanguageServerId(0),
11468 lsp::PublishDiagnosticsParams {
11469 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11470 version: None,
11471 diagnostics: vec![lsp::Diagnostic {
11472 range: lsp::Range::new(
11473 lsp::Position::new(0, 11),
11474 lsp::Position::new(0, 12),
11475 ),
11476 severity: Some(lsp::DiagnosticSeverity::ERROR),
11477 message: message.to_string(),
11478 ..Default::default()
11479 }],
11480 },
11481 &[],
11482 cx,
11483 )
11484 .unwrap()
11485 });
11486 });
11487 executor.run_until_parked();
11488
11489 cx.update_editor(|editor, window, cx| {
11490 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11491 assert_eq!(
11492 editor
11493 .active_diagnostics
11494 .as_ref()
11495 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11496 Some(message),
11497 "Should have a diagnostics group activated"
11498 );
11499 });
11500 cx.assert_editor_state(indoc! {"
11501 fn func(abcˇ def: i32) -> u32 {
11502 }
11503 "});
11504
11505 cx.update(|_, cx| {
11506 lsp_store.update(cx, |lsp_store, cx| {
11507 lsp_store
11508 .update_diagnostics(
11509 LanguageServerId(0),
11510 lsp::PublishDiagnosticsParams {
11511 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11512 version: None,
11513 diagnostics: Vec::new(),
11514 },
11515 &[],
11516 cx,
11517 )
11518 .unwrap()
11519 });
11520 });
11521 executor.run_until_parked();
11522 cx.update_editor(|editor, _, _| {
11523 assert_eq!(
11524 editor.active_diagnostics, None,
11525 "After no diagnostics set to the editor, no diagnostics should be active"
11526 );
11527 });
11528 cx.assert_editor_state(indoc! {"
11529 fn func(abcˇ def: i32) -> u32 {
11530 }
11531 "});
11532
11533 cx.update_editor(|editor, window, cx| {
11534 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11535 assert_eq!(
11536 editor.active_diagnostics, None,
11537 "Should be no diagnostics to go to and activate"
11538 );
11539 });
11540 cx.assert_editor_state(indoc! {"
11541 fn func(abcˇ def: i32) -> u32 {
11542 }
11543 "});
11544}
11545
11546#[gpui::test]
11547async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11548 init_test(cx, |_| {});
11549
11550 let mut cx = EditorTestContext::new(cx).await;
11551
11552 cx.set_state(indoc! {"
11553 fn func(abˇc def: i32) -> u32 {
11554 }
11555 "});
11556 let lsp_store =
11557 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11558
11559 cx.update(|_, cx| {
11560 lsp_store.update(cx, |lsp_store, cx| {
11561 lsp_store.update_diagnostics(
11562 LanguageServerId(0),
11563 lsp::PublishDiagnosticsParams {
11564 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11565 version: None,
11566 diagnostics: vec![lsp::Diagnostic {
11567 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11568 severity: Some(lsp::DiagnosticSeverity::ERROR),
11569 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11570 ..Default::default()
11571 }],
11572 },
11573 &[],
11574 cx,
11575 )
11576 })
11577 }).unwrap();
11578 cx.run_until_parked();
11579 cx.update_editor(|editor, window, cx| {
11580 hover_popover::hover(editor, &Default::default(), window, cx)
11581 });
11582 cx.run_until_parked();
11583 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11584}
11585
11586#[gpui::test]
11587async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11588 init_test(cx, |_| {});
11589
11590 let mut cx = EditorTestContext::new(cx).await;
11591
11592 let diff_base = r#"
11593 use some::mod;
11594
11595 const A: u32 = 42;
11596
11597 fn main() {
11598 println!("hello");
11599
11600 println!("world");
11601 }
11602 "#
11603 .unindent();
11604
11605 // Edits are modified, removed, modified, added
11606 cx.set_state(
11607 &r#"
11608 use some::modified;
11609
11610 ˇ
11611 fn main() {
11612 println!("hello there");
11613
11614 println!("around the");
11615 println!("world");
11616 }
11617 "#
11618 .unindent(),
11619 );
11620
11621 cx.set_head_text(&diff_base);
11622 executor.run_until_parked();
11623
11624 cx.update_editor(|editor, window, cx| {
11625 //Wrap around the bottom of the buffer
11626 for _ in 0..3 {
11627 editor.go_to_next_hunk(&GoToHunk, window, cx);
11628 }
11629 });
11630
11631 cx.assert_editor_state(
11632 &r#"
11633 ˇuse some::modified;
11634
11635
11636 fn main() {
11637 println!("hello there");
11638
11639 println!("around the");
11640 println!("world");
11641 }
11642 "#
11643 .unindent(),
11644 );
11645
11646 cx.update_editor(|editor, window, cx| {
11647 //Wrap around the top of the buffer
11648 for _ in 0..2 {
11649 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11650 }
11651 });
11652
11653 cx.assert_editor_state(
11654 &r#"
11655 use some::modified;
11656
11657
11658 fn main() {
11659 ˇ println!("hello there");
11660
11661 println!("around the");
11662 println!("world");
11663 }
11664 "#
11665 .unindent(),
11666 );
11667
11668 cx.update_editor(|editor, window, cx| {
11669 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11670 });
11671
11672 cx.assert_editor_state(
11673 &r#"
11674 use some::modified;
11675
11676 ˇ
11677 fn main() {
11678 println!("hello there");
11679
11680 println!("around the");
11681 println!("world");
11682 }
11683 "#
11684 .unindent(),
11685 );
11686
11687 cx.update_editor(|editor, window, cx| {
11688 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11689 });
11690
11691 cx.assert_editor_state(
11692 &r#"
11693 ˇuse some::modified;
11694
11695
11696 fn main() {
11697 println!("hello there");
11698
11699 println!("around the");
11700 println!("world");
11701 }
11702 "#
11703 .unindent(),
11704 );
11705
11706 cx.update_editor(|editor, window, cx| {
11707 for _ in 0..2 {
11708 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11709 }
11710 });
11711
11712 cx.assert_editor_state(
11713 &r#"
11714 use some::modified;
11715
11716
11717 fn main() {
11718 ˇ println!("hello there");
11719
11720 println!("around the");
11721 println!("world");
11722 }
11723 "#
11724 .unindent(),
11725 );
11726
11727 cx.update_editor(|editor, window, cx| {
11728 editor.fold(&Fold, window, cx);
11729 });
11730
11731 cx.update_editor(|editor, window, cx| {
11732 editor.go_to_next_hunk(&GoToHunk, window, cx);
11733 });
11734
11735 cx.assert_editor_state(
11736 &r#"
11737 ˇuse some::modified;
11738
11739
11740 fn main() {
11741 println!("hello there");
11742
11743 println!("around the");
11744 println!("world");
11745 }
11746 "#
11747 .unindent(),
11748 );
11749}
11750
11751#[test]
11752fn test_split_words() {
11753 fn split(text: &str) -> Vec<&str> {
11754 split_words(text).collect()
11755 }
11756
11757 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11758 assert_eq!(split("hello_world"), &["hello_", "world"]);
11759 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11760 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11761 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11762 assert_eq!(split("helloworld"), &["helloworld"]);
11763
11764 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11765}
11766
11767#[gpui::test]
11768async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11769 init_test(cx, |_| {});
11770
11771 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11772 let mut assert = |before, after| {
11773 let _state_context = cx.set_state(before);
11774 cx.run_until_parked();
11775 cx.update_editor(|editor, window, cx| {
11776 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11777 });
11778 cx.assert_editor_state(after);
11779 };
11780
11781 // Outside bracket jumps to outside of matching bracket
11782 assert("console.logˇ(var);", "console.log(var)ˇ;");
11783 assert("console.log(var)ˇ;", "console.logˇ(var);");
11784
11785 // Inside bracket jumps to inside of matching bracket
11786 assert("console.log(ˇvar);", "console.log(varˇ);");
11787 assert("console.log(varˇ);", "console.log(ˇvar);");
11788
11789 // When outside a bracket and inside, favor jumping to the inside bracket
11790 assert(
11791 "console.log('foo', [1, 2, 3]ˇ);",
11792 "console.log(ˇ'foo', [1, 2, 3]);",
11793 );
11794 assert(
11795 "console.log(ˇ'foo', [1, 2, 3]);",
11796 "console.log('foo', [1, 2, 3]ˇ);",
11797 );
11798
11799 // Bias forward if two options are equally likely
11800 assert(
11801 "let result = curried_fun()ˇ();",
11802 "let result = curried_fun()()ˇ;",
11803 );
11804
11805 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11806 assert(
11807 indoc! {"
11808 function test() {
11809 console.log('test')ˇ
11810 }"},
11811 indoc! {"
11812 function test() {
11813 console.logˇ('test')
11814 }"},
11815 );
11816}
11817
11818#[gpui::test]
11819async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11820 init_test(cx, |_| {});
11821
11822 let fs = FakeFs::new(cx.executor());
11823 fs.insert_tree(
11824 path!("/a"),
11825 json!({
11826 "main.rs": "fn main() { let a = 5; }",
11827 "other.rs": "// Test file",
11828 }),
11829 )
11830 .await;
11831 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11832
11833 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11834 language_registry.add(Arc::new(Language::new(
11835 LanguageConfig {
11836 name: "Rust".into(),
11837 matcher: LanguageMatcher {
11838 path_suffixes: vec!["rs".to_string()],
11839 ..Default::default()
11840 },
11841 brackets: BracketPairConfig {
11842 pairs: vec![BracketPair {
11843 start: "{".to_string(),
11844 end: "}".to_string(),
11845 close: true,
11846 surround: true,
11847 newline: true,
11848 }],
11849 disabled_scopes_by_bracket_ix: Vec::new(),
11850 },
11851 ..Default::default()
11852 },
11853 Some(tree_sitter_rust::LANGUAGE.into()),
11854 )));
11855 let mut fake_servers = language_registry.register_fake_lsp(
11856 "Rust",
11857 FakeLspAdapter {
11858 capabilities: lsp::ServerCapabilities {
11859 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11860 first_trigger_character: "{".to_string(),
11861 more_trigger_character: None,
11862 }),
11863 ..Default::default()
11864 },
11865 ..Default::default()
11866 },
11867 );
11868
11869 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11870
11871 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11872
11873 let worktree_id = workspace
11874 .update(cx, |workspace, _, cx| {
11875 workspace.project().update(cx, |project, cx| {
11876 project.worktrees(cx).next().unwrap().read(cx).id()
11877 })
11878 })
11879 .unwrap();
11880
11881 let buffer = project
11882 .update(cx, |project, cx| {
11883 project.open_local_buffer(path!("/a/main.rs"), cx)
11884 })
11885 .await
11886 .unwrap();
11887 let editor_handle = workspace
11888 .update(cx, |workspace, window, cx| {
11889 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11890 })
11891 .unwrap()
11892 .await
11893 .unwrap()
11894 .downcast::<Editor>()
11895 .unwrap();
11896
11897 cx.executor().start_waiting();
11898 let fake_server = fake_servers.next().await.unwrap();
11899
11900 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11901 assert_eq!(
11902 params.text_document_position.text_document.uri,
11903 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11904 );
11905 assert_eq!(
11906 params.text_document_position.position,
11907 lsp::Position::new(0, 21),
11908 );
11909
11910 Ok(Some(vec![lsp::TextEdit {
11911 new_text: "]".to_string(),
11912 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11913 }]))
11914 });
11915
11916 editor_handle.update_in(cx, |editor, window, cx| {
11917 window.focus(&editor.focus_handle(cx));
11918 editor.change_selections(None, window, cx, |s| {
11919 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11920 });
11921 editor.handle_input("{", window, cx);
11922 });
11923
11924 cx.executor().run_until_parked();
11925
11926 buffer.update(cx, |buffer, _| {
11927 assert_eq!(
11928 buffer.text(),
11929 "fn main() { let a = {5}; }",
11930 "No extra braces from on type formatting should appear in the buffer"
11931 )
11932 });
11933}
11934
11935#[gpui::test]
11936async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11937 init_test(cx, |_| {});
11938
11939 let fs = FakeFs::new(cx.executor());
11940 fs.insert_tree(
11941 path!("/a"),
11942 json!({
11943 "main.rs": "fn main() { let a = 5; }",
11944 "other.rs": "// Test file",
11945 }),
11946 )
11947 .await;
11948
11949 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11950
11951 let server_restarts = Arc::new(AtomicUsize::new(0));
11952 let closure_restarts = Arc::clone(&server_restarts);
11953 let language_server_name = "test language server";
11954 let language_name: LanguageName = "Rust".into();
11955
11956 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11957 language_registry.add(Arc::new(Language::new(
11958 LanguageConfig {
11959 name: language_name.clone(),
11960 matcher: LanguageMatcher {
11961 path_suffixes: vec!["rs".to_string()],
11962 ..Default::default()
11963 },
11964 ..Default::default()
11965 },
11966 Some(tree_sitter_rust::LANGUAGE.into()),
11967 )));
11968 let mut fake_servers = language_registry.register_fake_lsp(
11969 "Rust",
11970 FakeLspAdapter {
11971 name: language_server_name,
11972 initialization_options: Some(json!({
11973 "testOptionValue": true
11974 })),
11975 initializer: Some(Box::new(move |fake_server| {
11976 let task_restarts = Arc::clone(&closure_restarts);
11977 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11978 task_restarts.fetch_add(1, atomic::Ordering::Release);
11979 futures::future::ready(Ok(()))
11980 });
11981 })),
11982 ..Default::default()
11983 },
11984 );
11985
11986 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11987 let _buffer = project
11988 .update(cx, |project, cx| {
11989 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11990 })
11991 .await
11992 .unwrap();
11993 let _fake_server = fake_servers.next().await.unwrap();
11994 update_test_language_settings(cx, |language_settings| {
11995 language_settings.languages.insert(
11996 language_name.clone(),
11997 LanguageSettingsContent {
11998 tab_size: NonZeroU32::new(8),
11999 ..Default::default()
12000 },
12001 );
12002 });
12003 cx.executor().run_until_parked();
12004 assert_eq!(
12005 server_restarts.load(atomic::Ordering::Acquire),
12006 0,
12007 "Should not restart LSP server on an unrelated change"
12008 );
12009
12010 update_test_project_settings(cx, |project_settings| {
12011 project_settings.lsp.insert(
12012 "Some other server name".into(),
12013 LspSettings {
12014 binary: None,
12015 settings: None,
12016 initialization_options: Some(json!({
12017 "some other init value": false
12018 })),
12019 },
12020 );
12021 });
12022 cx.executor().run_until_parked();
12023 assert_eq!(
12024 server_restarts.load(atomic::Ordering::Acquire),
12025 0,
12026 "Should not restart LSP server on an unrelated LSP settings change"
12027 );
12028
12029 update_test_project_settings(cx, |project_settings| {
12030 project_settings.lsp.insert(
12031 language_server_name.into(),
12032 LspSettings {
12033 binary: None,
12034 settings: None,
12035 initialization_options: Some(json!({
12036 "anotherInitValue": false
12037 })),
12038 },
12039 );
12040 });
12041 cx.executor().run_until_parked();
12042 assert_eq!(
12043 server_restarts.load(atomic::Ordering::Acquire),
12044 1,
12045 "Should restart LSP server on a related LSP settings change"
12046 );
12047
12048 update_test_project_settings(cx, |project_settings| {
12049 project_settings.lsp.insert(
12050 language_server_name.into(),
12051 LspSettings {
12052 binary: None,
12053 settings: None,
12054 initialization_options: Some(json!({
12055 "anotherInitValue": false
12056 })),
12057 },
12058 );
12059 });
12060 cx.executor().run_until_parked();
12061 assert_eq!(
12062 server_restarts.load(atomic::Ordering::Acquire),
12063 1,
12064 "Should not restart LSP server on a related LSP settings change that is the same"
12065 );
12066
12067 update_test_project_settings(cx, |project_settings| {
12068 project_settings.lsp.insert(
12069 language_server_name.into(),
12070 LspSettings {
12071 binary: None,
12072 settings: None,
12073 initialization_options: None,
12074 },
12075 );
12076 });
12077 cx.executor().run_until_parked();
12078 assert_eq!(
12079 server_restarts.load(atomic::Ordering::Acquire),
12080 2,
12081 "Should restart LSP server on another related LSP settings change"
12082 );
12083}
12084
12085#[gpui::test]
12086async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12087 init_test(cx, |_| {});
12088
12089 let mut cx = EditorLspTestContext::new_rust(
12090 lsp::ServerCapabilities {
12091 completion_provider: Some(lsp::CompletionOptions {
12092 trigger_characters: Some(vec![".".to_string()]),
12093 resolve_provider: Some(true),
12094 ..Default::default()
12095 }),
12096 ..Default::default()
12097 },
12098 cx,
12099 )
12100 .await;
12101
12102 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12103 cx.simulate_keystroke(".");
12104 let completion_item = lsp::CompletionItem {
12105 label: "some".into(),
12106 kind: Some(lsp::CompletionItemKind::SNIPPET),
12107 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12108 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12109 kind: lsp::MarkupKind::Markdown,
12110 value: "```rust\nSome(2)\n```".to_string(),
12111 })),
12112 deprecated: Some(false),
12113 sort_text: Some("fffffff2".to_string()),
12114 filter_text: Some("some".to_string()),
12115 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12116 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12117 range: lsp::Range {
12118 start: lsp::Position {
12119 line: 0,
12120 character: 22,
12121 },
12122 end: lsp::Position {
12123 line: 0,
12124 character: 22,
12125 },
12126 },
12127 new_text: "Some(2)".to_string(),
12128 })),
12129 additional_text_edits: Some(vec![lsp::TextEdit {
12130 range: lsp::Range {
12131 start: lsp::Position {
12132 line: 0,
12133 character: 20,
12134 },
12135 end: lsp::Position {
12136 line: 0,
12137 character: 22,
12138 },
12139 },
12140 new_text: "".to_string(),
12141 }]),
12142 ..Default::default()
12143 };
12144
12145 let closure_completion_item = completion_item.clone();
12146 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12147 let task_completion_item = closure_completion_item.clone();
12148 async move {
12149 Ok(Some(lsp::CompletionResponse::Array(vec![
12150 task_completion_item,
12151 ])))
12152 }
12153 });
12154
12155 request.next().await;
12156
12157 cx.condition(|editor, _| editor.context_menu_visible())
12158 .await;
12159 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12160 editor
12161 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12162 .unwrap()
12163 });
12164 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12165
12166 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12167 let task_completion_item = completion_item.clone();
12168 async move { Ok(task_completion_item) }
12169 })
12170 .next()
12171 .await
12172 .unwrap();
12173 apply_additional_edits.await.unwrap();
12174 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12175}
12176
12177#[gpui::test]
12178async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12179 init_test(cx, |_| {});
12180
12181 let mut cx = EditorLspTestContext::new_rust(
12182 lsp::ServerCapabilities {
12183 completion_provider: Some(lsp::CompletionOptions {
12184 trigger_characters: Some(vec![".".to_string()]),
12185 resolve_provider: Some(true),
12186 ..Default::default()
12187 }),
12188 ..Default::default()
12189 },
12190 cx,
12191 )
12192 .await;
12193
12194 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12195 cx.simulate_keystroke(".");
12196
12197 let item1 = lsp::CompletionItem {
12198 label: "method id()".to_string(),
12199 filter_text: Some("id".to_string()),
12200 detail: None,
12201 documentation: None,
12202 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12203 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12204 new_text: ".id".to_string(),
12205 })),
12206 ..lsp::CompletionItem::default()
12207 };
12208
12209 let item2 = lsp::CompletionItem {
12210 label: "other".to_string(),
12211 filter_text: Some("other".to_string()),
12212 detail: None,
12213 documentation: None,
12214 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12215 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12216 new_text: ".other".to_string(),
12217 })),
12218 ..lsp::CompletionItem::default()
12219 };
12220
12221 let item1 = item1.clone();
12222 cx.handle_request::<lsp::request::Completion, _, _>({
12223 let item1 = item1.clone();
12224 move |_, _, _| {
12225 let item1 = item1.clone();
12226 let item2 = item2.clone();
12227 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12228 }
12229 })
12230 .next()
12231 .await;
12232
12233 cx.condition(|editor, _| editor.context_menu_visible())
12234 .await;
12235 cx.update_editor(|editor, _, _| {
12236 let context_menu = editor.context_menu.borrow_mut();
12237 let context_menu = context_menu
12238 .as_ref()
12239 .expect("Should have the context menu deployed");
12240 match context_menu {
12241 CodeContextMenu::Completions(completions_menu) => {
12242 let completions = completions_menu.completions.borrow_mut();
12243 assert_eq!(
12244 completions
12245 .iter()
12246 .map(|completion| &completion.label.text)
12247 .collect::<Vec<_>>(),
12248 vec!["method id()", "other"]
12249 )
12250 }
12251 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12252 }
12253 });
12254
12255 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12256 let item1 = item1.clone();
12257 move |_, item_to_resolve, _| {
12258 let item1 = item1.clone();
12259 async move {
12260 if item1 == item_to_resolve {
12261 Ok(lsp::CompletionItem {
12262 label: "method id()".to_string(),
12263 filter_text: Some("id".to_string()),
12264 detail: Some("Now resolved!".to_string()),
12265 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12266 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12267 range: lsp::Range::new(
12268 lsp::Position::new(0, 22),
12269 lsp::Position::new(0, 22),
12270 ),
12271 new_text: ".id".to_string(),
12272 })),
12273 ..lsp::CompletionItem::default()
12274 })
12275 } else {
12276 Ok(item_to_resolve)
12277 }
12278 }
12279 }
12280 })
12281 .next()
12282 .await
12283 .unwrap();
12284 cx.run_until_parked();
12285
12286 cx.update_editor(|editor, window, cx| {
12287 editor.context_menu_next(&Default::default(), window, cx);
12288 });
12289
12290 cx.update_editor(|editor, _, _| {
12291 let context_menu = editor.context_menu.borrow_mut();
12292 let context_menu = context_menu
12293 .as_ref()
12294 .expect("Should have the context menu deployed");
12295 match context_menu {
12296 CodeContextMenu::Completions(completions_menu) => {
12297 let completions = completions_menu.completions.borrow_mut();
12298 assert_eq!(
12299 completions
12300 .iter()
12301 .map(|completion| &completion.label.text)
12302 .collect::<Vec<_>>(),
12303 vec!["method id() Now resolved!", "other"],
12304 "Should update first completion label, but not second as the filter text did not match."
12305 );
12306 }
12307 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12308 }
12309 });
12310}
12311
12312#[gpui::test]
12313async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12314 init_test(cx, |_| {});
12315
12316 let mut cx = EditorLspTestContext::new_rust(
12317 lsp::ServerCapabilities {
12318 completion_provider: Some(lsp::CompletionOptions {
12319 trigger_characters: Some(vec![".".to_string()]),
12320 resolve_provider: Some(true),
12321 ..Default::default()
12322 }),
12323 ..Default::default()
12324 },
12325 cx,
12326 )
12327 .await;
12328
12329 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12330 cx.simulate_keystroke(".");
12331
12332 let unresolved_item_1 = lsp::CompletionItem {
12333 label: "id".to_string(),
12334 filter_text: Some("id".to_string()),
12335 detail: None,
12336 documentation: None,
12337 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12338 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12339 new_text: ".id".to_string(),
12340 })),
12341 ..lsp::CompletionItem::default()
12342 };
12343 let resolved_item_1 = lsp::CompletionItem {
12344 additional_text_edits: Some(vec![lsp::TextEdit {
12345 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12346 new_text: "!!".to_string(),
12347 }]),
12348 ..unresolved_item_1.clone()
12349 };
12350 let unresolved_item_2 = lsp::CompletionItem {
12351 label: "other".to_string(),
12352 filter_text: Some("other".to_string()),
12353 detail: None,
12354 documentation: None,
12355 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12356 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12357 new_text: ".other".to_string(),
12358 })),
12359 ..lsp::CompletionItem::default()
12360 };
12361 let resolved_item_2 = lsp::CompletionItem {
12362 additional_text_edits: Some(vec![lsp::TextEdit {
12363 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12364 new_text: "??".to_string(),
12365 }]),
12366 ..unresolved_item_2.clone()
12367 };
12368
12369 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12370 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12371 cx.lsp
12372 .server
12373 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12374 let unresolved_item_1 = unresolved_item_1.clone();
12375 let resolved_item_1 = resolved_item_1.clone();
12376 let unresolved_item_2 = unresolved_item_2.clone();
12377 let resolved_item_2 = resolved_item_2.clone();
12378 let resolve_requests_1 = resolve_requests_1.clone();
12379 let resolve_requests_2 = resolve_requests_2.clone();
12380 move |unresolved_request, _| {
12381 let unresolved_item_1 = unresolved_item_1.clone();
12382 let resolved_item_1 = resolved_item_1.clone();
12383 let unresolved_item_2 = unresolved_item_2.clone();
12384 let resolved_item_2 = resolved_item_2.clone();
12385 let resolve_requests_1 = resolve_requests_1.clone();
12386 let resolve_requests_2 = resolve_requests_2.clone();
12387 async move {
12388 if unresolved_request == unresolved_item_1 {
12389 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12390 Ok(resolved_item_1.clone())
12391 } else if unresolved_request == unresolved_item_2 {
12392 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12393 Ok(resolved_item_2.clone())
12394 } else {
12395 panic!("Unexpected completion item {unresolved_request:?}")
12396 }
12397 }
12398 }
12399 })
12400 .detach();
12401
12402 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12403 let unresolved_item_1 = unresolved_item_1.clone();
12404 let unresolved_item_2 = unresolved_item_2.clone();
12405 async move {
12406 Ok(Some(lsp::CompletionResponse::Array(vec![
12407 unresolved_item_1,
12408 unresolved_item_2,
12409 ])))
12410 }
12411 })
12412 .next()
12413 .await;
12414
12415 cx.condition(|editor, _| editor.context_menu_visible())
12416 .await;
12417 cx.update_editor(|editor, _, _| {
12418 let context_menu = editor.context_menu.borrow_mut();
12419 let context_menu = context_menu
12420 .as_ref()
12421 .expect("Should have the context menu deployed");
12422 match context_menu {
12423 CodeContextMenu::Completions(completions_menu) => {
12424 let completions = completions_menu.completions.borrow_mut();
12425 assert_eq!(
12426 completions
12427 .iter()
12428 .map(|completion| &completion.label.text)
12429 .collect::<Vec<_>>(),
12430 vec!["id", "other"]
12431 )
12432 }
12433 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12434 }
12435 });
12436 cx.run_until_parked();
12437
12438 cx.update_editor(|editor, window, cx| {
12439 editor.context_menu_next(&ContextMenuNext, window, cx);
12440 });
12441 cx.run_until_parked();
12442 cx.update_editor(|editor, window, cx| {
12443 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12444 });
12445 cx.run_until_parked();
12446 cx.update_editor(|editor, window, cx| {
12447 editor.context_menu_next(&ContextMenuNext, window, cx);
12448 });
12449 cx.run_until_parked();
12450 cx.update_editor(|editor, window, cx| {
12451 editor
12452 .compose_completion(&ComposeCompletion::default(), window, cx)
12453 .expect("No task returned")
12454 })
12455 .await
12456 .expect("Completion failed");
12457 cx.run_until_parked();
12458
12459 cx.update_editor(|editor, _, cx| {
12460 assert_eq!(
12461 resolve_requests_1.load(atomic::Ordering::Acquire),
12462 1,
12463 "Should always resolve once despite multiple selections"
12464 );
12465 assert_eq!(
12466 resolve_requests_2.load(atomic::Ordering::Acquire),
12467 1,
12468 "Should always resolve once after multiple selections and applying the completion"
12469 );
12470 assert_eq!(
12471 editor.text(cx),
12472 "fn main() { let a = ??.other; }",
12473 "Should use resolved data when applying the completion"
12474 );
12475 });
12476}
12477
12478#[gpui::test]
12479async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12480 init_test(cx, |_| {});
12481
12482 let item_0 = lsp::CompletionItem {
12483 label: "abs".into(),
12484 insert_text: Some("abs".into()),
12485 data: Some(json!({ "very": "special"})),
12486 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12487 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12488 lsp::InsertReplaceEdit {
12489 new_text: "abs".to_string(),
12490 insert: lsp::Range::default(),
12491 replace: lsp::Range::default(),
12492 },
12493 )),
12494 ..lsp::CompletionItem::default()
12495 };
12496 let items = iter::once(item_0.clone())
12497 .chain((11..51).map(|i| lsp::CompletionItem {
12498 label: format!("item_{}", i),
12499 insert_text: Some(format!("item_{}", i)),
12500 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12501 ..lsp::CompletionItem::default()
12502 }))
12503 .collect::<Vec<_>>();
12504
12505 let default_commit_characters = vec!["?".to_string()];
12506 let default_data = json!({ "default": "data"});
12507 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12508 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12509 let default_edit_range = lsp::Range {
12510 start: lsp::Position {
12511 line: 0,
12512 character: 5,
12513 },
12514 end: lsp::Position {
12515 line: 0,
12516 character: 5,
12517 },
12518 };
12519
12520 let mut cx = EditorLspTestContext::new_rust(
12521 lsp::ServerCapabilities {
12522 completion_provider: Some(lsp::CompletionOptions {
12523 trigger_characters: Some(vec![".".to_string()]),
12524 resolve_provider: Some(true),
12525 ..Default::default()
12526 }),
12527 ..Default::default()
12528 },
12529 cx,
12530 )
12531 .await;
12532
12533 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12534 cx.simulate_keystroke(".");
12535
12536 let completion_data = default_data.clone();
12537 let completion_characters = default_commit_characters.clone();
12538 let completion_items = items.clone();
12539 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12540 let default_data = completion_data.clone();
12541 let default_commit_characters = completion_characters.clone();
12542 let items = completion_items.clone();
12543 async move {
12544 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12545 items,
12546 item_defaults: Some(lsp::CompletionListItemDefaults {
12547 data: Some(default_data.clone()),
12548 commit_characters: Some(default_commit_characters.clone()),
12549 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12550 default_edit_range,
12551 )),
12552 insert_text_format: Some(default_insert_text_format),
12553 insert_text_mode: Some(default_insert_text_mode),
12554 }),
12555 ..lsp::CompletionList::default()
12556 })))
12557 }
12558 })
12559 .next()
12560 .await;
12561
12562 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12563 cx.lsp
12564 .server
12565 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12566 let closure_resolved_items = resolved_items.clone();
12567 move |item_to_resolve, _| {
12568 let closure_resolved_items = closure_resolved_items.clone();
12569 async move {
12570 closure_resolved_items.lock().push(item_to_resolve.clone());
12571 Ok(item_to_resolve)
12572 }
12573 }
12574 })
12575 .detach();
12576
12577 cx.condition(|editor, _| editor.context_menu_visible())
12578 .await;
12579 cx.run_until_parked();
12580 cx.update_editor(|editor, _, _| {
12581 let menu = editor.context_menu.borrow_mut();
12582 match menu.as_ref().expect("should have the completions menu") {
12583 CodeContextMenu::Completions(completions_menu) => {
12584 assert_eq!(
12585 completions_menu
12586 .entries
12587 .borrow()
12588 .iter()
12589 .map(|mat| mat.string.clone())
12590 .collect::<Vec<String>>(),
12591 items
12592 .iter()
12593 .map(|completion| completion.label.clone())
12594 .collect::<Vec<String>>()
12595 );
12596 }
12597 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12598 }
12599 });
12600 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12601 // with 4 from the end.
12602 assert_eq!(
12603 *resolved_items.lock(),
12604 [&items[0..16], &items[items.len() - 4..items.len()]]
12605 .concat()
12606 .iter()
12607 .cloned()
12608 .map(|mut item| {
12609 if item.data.is_none() {
12610 item.data = Some(default_data.clone());
12611 }
12612 item
12613 })
12614 .collect::<Vec<lsp::CompletionItem>>(),
12615 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12616 );
12617 resolved_items.lock().clear();
12618
12619 cx.update_editor(|editor, window, cx| {
12620 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12621 });
12622 cx.run_until_parked();
12623 // Completions that have already been resolved are skipped.
12624 assert_eq!(
12625 *resolved_items.lock(),
12626 items[items.len() - 16..items.len() - 4]
12627 .iter()
12628 .cloned()
12629 .map(|mut item| {
12630 if item.data.is_none() {
12631 item.data = Some(default_data.clone());
12632 }
12633 item
12634 })
12635 .collect::<Vec<lsp::CompletionItem>>()
12636 );
12637 resolved_items.lock().clear();
12638}
12639
12640#[gpui::test]
12641async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12642 init_test(cx, |_| {});
12643
12644 let mut cx = EditorLspTestContext::new(
12645 Language::new(
12646 LanguageConfig {
12647 matcher: LanguageMatcher {
12648 path_suffixes: vec!["jsx".into()],
12649 ..Default::default()
12650 },
12651 overrides: [(
12652 "element".into(),
12653 LanguageConfigOverride {
12654 word_characters: Override::Set(['-'].into_iter().collect()),
12655 ..Default::default()
12656 },
12657 )]
12658 .into_iter()
12659 .collect(),
12660 ..Default::default()
12661 },
12662 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12663 )
12664 .with_override_query("(jsx_self_closing_element) @element")
12665 .unwrap(),
12666 lsp::ServerCapabilities {
12667 completion_provider: Some(lsp::CompletionOptions {
12668 trigger_characters: Some(vec![":".to_string()]),
12669 ..Default::default()
12670 }),
12671 ..Default::default()
12672 },
12673 cx,
12674 )
12675 .await;
12676
12677 cx.lsp
12678 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12679 Ok(Some(lsp::CompletionResponse::Array(vec![
12680 lsp::CompletionItem {
12681 label: "bg-blue".into(),
12682 ..Default::default()
12683 },
12684 lsp::CompletionItem {
12685 label: "bg-red".into(),
12686 ..Default::default()
12687 },
12688 lsp::CompletionItem {
12689 label: "bg-yellow".into(),
12690 ..Default::default()
12691 },
12692 ])))
12693 });
12694
12695 cx.set_state(r#"<p class="bgˇ" />"#);
12696
12697 // Trigger completion when typing a dash, because the dash is an extra
12698 // word character in the 'element' scope, which contains the cursor.
12699 cx.simulate_keystroke("-");
12700 cx.executor().run_until_parked();
12701 cx.update_editor(|editor, _, _| {
12702 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12703 {
12704 assert_eq!(
12705 completion_menu_entries(&menu),
12706 &["bg-red", "bg-blue", "bg-yellow"]
12707 );
12708 } else {
12709 panic!("expected completion menu to be open");
12710 }
12711 });
12712
12713 cx.simulate_keystroke("l");
12714 cx.executor().run_until_parked();
12715 cx.update_editor(|editor, _, _| {
12716 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12717 {
12718 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12719 } else {
12720 panic!("expected completion menu to be open");
12721 }
12722 });
12723
12724 // When filtering completions, consider the character after the '-' to
12725 // be the start of a subword.
12726 cx.set_state(r#"<p class="yelˇ" />"#);
12727 cx.simulate_keystroke("l");
12728 cx.executor().run_until_parked();
12729 cx.update_editor(|editor, _, _| {
12730 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12731 {
12732 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12733 } else {
12734 panic!("expected completion menu to be open");
12735 }
12736 });
12737}
12738
12739fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12740 let entries = menu.entries.borrow();
12741 entries.iter().map(|mat| mat.string.clone()).collect()
12742}
12743
12744#[gpui::test]
12745async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12746 init_test(cx, |settings| {
12747 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12748 FormatterList(vec![Formatter::Prettier].into()),
12749 ))
12750 });
12751
12752 let fs = FakeFs::new(cx.executor());
12753 fs.insert_file(path!("/file.ts"), Default::default()).await;
12754
12755 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12756 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12757
12758 language_registry.add(Arc::new(Language::new(
12759 LanguageConfig {
12760 name: "TypeScript".into(),
12761 matcher: LanguageMatcher {
12762 path_suffixes: vec!["ts".to_string()],
12763 ..Default::default()
12764 },
12765 ..Default::default()
12766 },
12767 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12768 )));
12769 update_test_language_settings(cx, |settings| {
12770 settings.defaults.prettier = Some(PrettierSettings {
12771 allowed: true,
12772 ..PrettierSettings::default()
12773 });
12774 });
12775
12776 let test_plugin = "test_plugin";
12777 let _ = language_registry.register_fake_lsp(
12778 "TypeScript",
12779 FakeLspAdapter {
12780 prettier_plugins: vec![test_plugin],
12781 ..Default::default()
12782 },
12783 );
12784
12785 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12786 let buffer = project
12787 .update(cx, |project, cx| {
12788 project.open_local_buffer(path!("/file.ts"), cx)
12789 })
12790 .await
12791 .unwrap();
12792
12793 let buffer_text = "one\ntwo\nthree\n";
12794 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12795 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12796 editor.update_in(cx, |editor, window, cx| {
12797 editor.set_text(buffer_text, window, cx)
12798 });
12799
12800 editor
12801 .update_in(cx, |editor, window, cx| {
12802 editor.perform_format(
12803 project.clone(),
12804 FormatTrigger::Manual,
12805 FormatTarget::Buffers,
12806 window,
12807 cx,
12808 )
12809 })
12810 .unwrap()
12811 .await;
12812 assert_eq!(
12813 editor.update(cx, |editor, cx| editor.text(cx)),
12814 buffer_text.to_string() + prettier_format_suffix,
12815 "Test prettier formatting was not applied to the original buffer text",
12816 );
12817
12818 update_test_language_settings(cx, |settings| {
12819 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12820 });
12821 let format = editor.update_in(cx, |editor, window, cx| {
12822 editor.perform_format(
12823 project.clone(),
12824 FormatTrigger::Manual,
12825 FormatTarget::Buffers,
12826 window,
12827 cx,
12828 )
12829 });
12830 format.await.unwrap();
12831 assert_eq!(
12832 editor.update(cx, |editor, cx| editor.text(cx)),
12833 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12834 "Autoformatting (via test prettier) was not applied to the original buffer text",
12835 );
12836}
12837
12838#[gpui::test]
12839async fn test_addition_reverts(cx: &mut TestAppContext) {
12840 init_test(cx, |_| {});
12841 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12842 let base_text = indoc! {r#"
12843 struct Row;
12844 struct Row1;
12845 struct Row2;
12846
12847 struct Row4;
12848 struct Row5;
12849 struct Row6;
12850
12851 struct Row8;
12852 struct Row9;
12853 struct Row10;"#};
12854
12855 // When addition hunks are not adjacent to carets, no hunk revert is performed
12856 assert_hunk_revert(
12857 indoc! {r#"struct Row;
12858 struct Row1;
12859 struct Row1.1;
12860 struct Row1.2;
12861 struct Row2;ˇ
12862
12863 struct Row4;
12864 struct Row5;
12865 struct Row6;
12866
12867 struct Row8;
12868 ˇstruct Row9;
12869 struct Row9.1;
12870 struct Row9.2;
12871 struct Row9.3;
12872 struct Row10;"#},
12873 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12874 indoc! {r#"struct Row;
12875 struct Row1;
12876 struct Row1.1;
12877 struct Row1.2;
12878 struct Row2;ˇ
12879
12880 struct Row4;
12881 struct Row5;
12882 struct Row6;
12883
12884 struct Row8;
12885 ˇstruct Row9;
12886 struct Row9.1;
12887 struct Row9.2;
12888 struct Row9.3;
12889 struct Row10;"#},
12890 base_text,
12891 &mut cx,
12892 );
12893 // Same for selections
12894 assert_hunk_revert(
12895 indoc! {r#"struct Row;
12896 struct Row1;
12897 struct Row2;
12898 struct Row2.1;
12899 struct Row2.2;
12900 «ˇ
12901 struct Row4;
12902 struct» Row5;
12903 «struct Row6;
12904 ˇ»
12905 struct Row9.1;
12906 struct Row9.2;
12907 struct Row9.3;
12908 struct Row8;
12909 struct Row9;
12910 struct Row10;"#},
12911 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12912 indoc! {r#"struct Row;
12913 struct Row1;
12914 struct Row2;
12915 struct Row2.1;
12916 struct Row2.2;
12917 «ˇ
12918 struct Row4;
12919 struct» Row5;
12920 «struct Row6;
12921 ˇ»
12922 struct Row9.1;
12923 struct Row9.2;
12924 struct Row9.3;
12925 struct Row8;
12926 struct Row9;
12927 struct Row10;"#},
12928 base_text,
12929 &mut cx,
12930 );
12931
12932 // When carets and selections intersect the addition hunks, those are reverted.
12933 // Adjacent carets got merged.
12934 assert_hunk_revert(
12935 indoc! {r#"struct Row;
12936 ˇ// something on the top
12937 struct Row1;
12938 struct Row2;
12939 struct Roˇw3.1;
12940 struct Row2.2;
12941 struct Row2.3;ˇ
12942
12943 struct Row4;
12944 struct ˇRow5.1;
12945 struct Row5.2;
12946 struct «Rowˇ»5.3;
12947 struct Row5;
12948 struct Row6;
12949 ˇ
12950 struct Row9.1;
12951 struct «Rowˇ»9.2;
12952 struct «ˇRow»9.3;
12953 struct Row8;
12954 struct Row9;
12955 «ˇ// something on bottom»
12956 struct Row10;"#},
12957 vec![
12958 DiffHunkStatusKind::Added,
12959 DiffHunkStatusKind::Added,
12960 DiffHunkStatusKind::Added,
12961 DiffHunkStatusKind::Added,
12962 DiffHunkStatusKind::Added,
12963 ],
12964 indoc! {r#"struct Row;
12965 ˇstruct Row1;
12966 struct Row2;
12967 ˇ
12968 struct Row4;
12969 ˇstruct Row5;
12970 struct Row6;
12971 ˇ
12972 ˇstruct Row8;
12973 struct Row9;
12974 ˇstruct Row10;"#},
12975 base_text,
12976 &mut cx,
12977 );
12978}
12979
12980#[gpui::test]
12981async fn test_modification_reverts(cx: &mut TestAppContext) {
12982 init_test(cx, |_| {});
12983 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12984 let base_text = indoc! {r#"
12985 struct Row;
12986 struct Row1;
12987 struct Row2;
12988
12989 struct Row4;
12990 struct Row5;
12991 struct Row6;
12992
12993 struct Row8;
12994 struct Row9;
12995 struct Row10;"#};
12996
12997 // Modification hunks behave the same as the addition ones.
12998 assert_hunk_revert(
12999 indoc! {r#"struct Row;
13000 struct Row1;
13001 struct Row33;
13002 ˇ
13003 struct Row4;
13004 struct Row5;
13005 struct Row6;
13006 ˇ
13007 struct Row99;
13008 struct Row9;
13009 struct Row10;"#},
13010 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13011 indoc! {r#"struct Row;
13012 struct Row1;
13013 struct Row33;
13014 ˇ
13015 struct Row4;
13016 struct Row5;
13017 struct Row6;
13018 ˇ
13019 struct Row99;
13020 struct Row9;
13021 struct Row10;"#},
13022 base_text,
13023 &mut cx,
13024 );
13025 assert_hunk_revert(
13026 indoc! {r#"struct Row;
13027 struct Row1;
13028 struct Row33;
13029 «ˇ
13030 struct Row4;
13031 struct» Row5;
13032 «struct Row6;
13033 ˇ»
13034 struct Row99;
13035 struct Row9;
13036 struct Row10;"#},
13037 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13038 indoc! {r#"struct Row;
13039 struct Row1;
13040 struct Row33;
13041 «ˇ
13042 struct Row4;
13043 struct» Row5;
13044 «struct Row6;
13045 ˇ»
13046 struct Row99;
13047 struct Row9;
13048 struct Row10;"#},
13049 base_text,
13050 &mut cx,
13051 );
13052
13053 assert_hunk_revert(
13054 indoc! {r#"ˇstruct Row1.1;
13055 struct Row1;
13056 «ˇstr»uct Row22;
13057
13058 struct ˇRow44;
13059 struct Row5;
13060 struct «Rˇ»ow66;ˇ
13061
13062 «struˇ»ct Row88;
13063 struct Row9;
13064 struct Row1011;ˇ"#},
13065 vec![
13066 DiffHunkStatusKind::Modified,
13067 DiffHunkStatusKind::Modified,
13068 DiffHunkStatusKind::Modified,
13069 DiffHunkStatusKind::Modified,
13070 DiffHunkStatusKind::Modified,
13071 DiffHunkStatusKind::Modified,
13072 ],
13073 indoc! {r#"struct Row;
13074 ˇstruct Row1;
13075 struct Row2;
13076 ˇ
13077 struct Row4;
13078 ˇstruct Row5;
13079 struct Row6;
13080 ˇ
13081 struct Row8;
13082 ˇstruct Row9;
13083 struct Row10;ˇ"#},
13084 base_text,
13085 &mut cx,
13086 );
13087}
13088
13089#[gpui::test]
13090async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13091 init_test(cx, |_| {});
13092 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13093 let base_text = indoc! {r#"
13094 one
13095
13096 two
13097 three
13098 "#};
13099
13100 cx.set_head_text(base_text);
13101 cx.set_state("\nˇ\n");
13102 cx.executor().run_until_parked();
13103 cx.update_editor(|editor, _window, cx| {
13104 editor.expand_selected_diff_hunks(cx);
13105 });
13106 cx.executor().run_until_parked();
13107 cx.update_editor(|editor, window, cx| {
13108 editor.backspace(&Default::default(), window, cx);
13109 });
13110 cx.run_until_parked();
13111 cx.assert_state_with_diff(
13112 indoc! {r#"
13113
13114 - two
13115 - threeˇ
13116 +
13117 "#}
13118 .to_string(),
13119 );
13120}
13121
13122#[gpui::test]
13123async fn test_deletion_reverts(cx: &mut TestAppContext) {
13124 init_test(cx, |_| {});
13125 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13126 let base_text = indoc! {r#"struct Row;
13127struct Row1;
13128struct Row2;
13129
13130struct Row4;
13131struct Row5;
13132struct Row6;
13133
13134struct Row8;
13135struct Row9;
13136struct Row10;"#};
13137
13138 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13139 assert_hunk_revert(
13140 indoc! {r#"struct Row;
13141 struct Row2;
13142
13143 ˇstruct Row4;
13144 struct Row5;
13145 struct Row6;
13146 ˇ
13147 struct Row8;
13148 struct Row10;"#},
13149 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13150 indoc! {r#"struct Row;
13151 struct Row2;
13152
13153 ˇstruct Row4;
13154 struct Row5;
13155 struct Row6;
13156 ˇ
13157 struct Row8;
13158 struct Row10;"#},
13159 base_text,
13160 &mut cx,
13161 );
13162 assert_hunk_revert(
13163 indoc! {r#"struct Row;
13164 struct Row2;
13165
13166 «ˇstruct Row4;
13167 struct» Row5;
13168 «struct Row6;
13169 ˇ»
13170 struct Row8;
13171 struct Row10;"#},
13172 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13173 indoc! {r#"struct Row;
13174 struct Row2;
13175
13176 «ˇstruct Row4;
13177 struct» Row5;
13178 «struct Row6;
13179 ˇ»
13180 struct Row8;
13181 struct Row10;"#},
13182 base_text,
13183 &mut cx,
13184 );
13185
13186 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13187 assert_hunk_revert(
13188 indoc! {r#"struct Row;
13189 ˇstruct Row2;
13190
13191 struct Row4;
13192 struct Row5;
13193 struct Row6;
13194
13195 struct Row8;ˇ
13196 struct Row10;"#},
13197 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13198 indoc! {r#"struct Row;
13199 struct Row1;
13200 ˇstruct Row2;
13201
13202 struct Row4;
13203 struct Row5;
13204 struct Row6;
13205
13206 struct Row8;ˇ
13207 struct Row9;
13208 struct Row10;"#},
13209 base_text,
13210 &mut cx,
13211 );
13212 assert_hunk_revert(
13213 indoc! {r#"struct Row;
13214 struct Row2«ˇ;
13215 struct Row4;
13216 struct» Row5;
13217 «struct Row6;
13218
13219 struct Row8;ˇ»
13220 struct Row10;"#},
13221 vec![
13222 DiffHunkStatusKind::Deleted,
13223 DiffHunkStatusKind::Deleted,
13224 DiffHunkStatusKind::Deleted,
13225 ],
13226 indoc! {r#"struct Row;
13227 struct Row1;
13228 struct Row2«ˇ;
13229
13230 struct Row4;
13231 struct» Row5;
13232 «struct Row6;
13233
13234 struct Row8;ˇ»
13235 struct Row9;
13236 struct Row10;"#},
13237 base_text,
13238 &mut cx,
13239 );
13240}
13241
13242#[gpui::test]
13243async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13244 init_test(cx, |_| {});
13245
13246 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13247 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13248 let base_text_3 =
13249 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13250
13251 let text_1 = edit_first_char_of_every_line(base_text_1);
13252 let text_2 = edit_first_char_of_every_line(base_text_2);
13253 let text_3 = edit_first_char_of_every_line(base_text_3);
13254
13255 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13256 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13257 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13258
13259 let multibuffer = cx.new(|cx| {
13260 let mut multibuffer = MultiBuffer::new(ReadWrite);
13261 multibuffer.push_excerpts(
13262 buffer_1.clone(),
13263 [
13264 ExcerptRange {
13265 context: Point::new(0, 0)..Point::new(3, 0),
13266 primary: None,
13267 },
13268 ExcerptRange {
13269 context: Point::new(5, 0)..Point::new(7, 0),
13270 primary: None,
13271 },
13272 ExcerptRange {
13273 context: Point::new(9, 0)..Point::new(10, 4),
13274 primary: None,
13275 },
13276 ],
13277 cx,
13278 );
13279 multibuffer.push_excerpts(
13280 buffer_2.clone(),
13281 [
13282 ExcerptRange {
13283 context: Point::new(0, 0)..Point::new(3, 0),
13284 primary: None,
13285 },
13286 ExcerptRange {
13287 context: Point::new(5, 0)..Point::new(7, 0),
13288 primary: None,
13289 },
13290 ExcerptRange {
13291 context: Point::new(9, 0)..Point::new(10, 4),
13292 primary: None,
13293 },
13294 ],
13295 cx,
13296 );
13297 multibuffer.push_excerpts(
13298 buffer_3.clone(),
13299 [
13300 ExcerptRange {
13301 context: Point::new(0, 0)..Point::new(3, 0),
13302 primary: None,
13303 },
13304 ExcerptRange {
13305 context: Point::new(5, 0)..Point::new(7, 0),
13306 primary: None,
13307 },
13308 ExcerptRange {
13309 context: Point::new(9, 0)..Point::new(10, 4),
13310 primary: None,
13311 },
13312 ],
13313 cx,
13314 );
13315 multibuffer
13316 });
13317
13318 let fs = FakeFs::new(cx.executor());
13319 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13320 let (editor, cx) = cx
13321 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13322 editor.update_in(cx, |editor, _window, cx| {
13323 for (buffer, diff_base) in [
13324 (buffer_1.clone(), base_text_1),
13325 (buffer_2.clone(), base_text_2),
13326 (buffer_3.clone(), base_text_3),
13327 ] {
13328 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13329 editor
13330 .buffer
13331 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13332 }
13333 });
13334 cx.executor().run_until_parked();
13335
13336 editor.update_in(cx, |editor, window, cx| {
13337 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}");
13338 editor.select_all(&SelectAll, window, cx);
13339 editor.git_restore(&Default::default(), window, cx);
13340 });
13341 cx.executor().run_until_parked();
13342
13343 // When all ranges are selected, all buffer hunks are reverted.
13344 editor.update(cx, |editor, cx| {
13345 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");
13346 });
13347 buffer_1.update(cx, |buffer, _| {
13348 assert_eq!(buffer.text(), base_text_1);
13349 });
13350 buffer_2.update(cx, |buffer, _| {
13351 assert_eq!(buffer.text(), base_text_2);
13352 });
13353 buffer_3.update(cx, |buffer, _| {
13354 assert_eq!(buffer.text(), base_text_3);
13355 });
13356
13357 editor.update_in(cx, |editor, window, cx| {
13358 editor.undo(&Default::default(), window, cx);
13359 });
13360
13361 editor.update_in(cx, |editor, window, cx| {
13362 editor.change_selections(None, window, cx, |s| {
13363 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13364 });
13365 editor.git_restore(&Default::default(), window, cx);
13366 });
13367
13368 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13369 // but not affect buffer_2 and its related excerpts.
13370 editor.update(cx, |editor, cx| {
13371 assert_eq!(
13372 editor.text(cx),
13373 "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}"
13374 );
13375 });
13376 buffer_1.update(cx, |buffer, _| {
13377 assert_eq!(buffer.text(), base_text_1);
13378 });
13379 buffer_2.update(cx, |buffer, _| {
13380 assert_eq!(
13381 buffer.text(),
13382 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13383 );
13384 });
13385 buffer_3.update(cx, |buffer, _| {
13386 assert_eq!(
13387 buffer.text(),
13388 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13389 );
13390 });
13391
13392 fn edit_first_char_of_every_line(text: &str) -> String {
13393 text.split('\n')
13394 .map(|line| format!("X{}", &line[1..]))
13395 .collect::<Vec<_>>()
13396 .join("\n")
13397 }
13398}
13399
13400#[gpui::test]
13401async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13402 init_test(cx, |_| {});
13403
13404 let cols = 4;
13405 let rows = 10;
13406 let sample_text_1 = sample_text(rows, cols, 'a');
13407 assert_eq!(
13408 sample_text_1,
13409 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13410 );
13411 let sample_text_2 = sample_text(rows, cols, 'l');
13412 assert_eq!(
13413 sample_text_2,
13414 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13415 );
13416 let sample_text_3 = sample_text(rows, cols, 'v');
13417 assert_eq!(
13418 sample_text_3,
13419 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13420 );
13421
13422 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13423 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13424 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13425
13426 let multi_buffer = cx.new(|cx| {
13427 let mut multibuffer = MultiBuffer::new(ReadWrite);
13428 multibuffer.push_excerpts(
13429 buffer_1.clone(),
13430 [
13431 ExcerptRange {
13432 context: Point::new(0, 0)..Point::new(3, 0),
13433 primary: None,
13434 },
13435 ExcerptRange {
13436 context: Point::new(5, 0)..Point::new(7, 0),
13437 primary: None,
13438 },
13439 ExcerptRange {
13440 context: Point::new(9, 0)..Point::new(10, 4),
13441 primary: None,
13442 },
13443 ],
13444 cx,
13445 );
13446 multibuffer.push_excerpts(
13447 buffer_2.clone(),
13448 [
13449 ExcerptRange {
13450 context: Point::new(0, 0)..Point::new(3, 0),
13451 primary: None,
13452 },
13453 ExcerptRange {
13454 context: Point::new(5, 0)..Point::new(7, 0),
13455 primary: None,
13456 },
13457 ExcerptRange {
13458 context: Point::new(9, 0)..Point::new(10, 4),
13459 primary: None,
13460 },
13461 ],
13462 cx,
13463 );
13464 multibuffer.push_excerpts(
13465 buffer_3.clone(),
13466 [
13467 ExcerptRange {
13468 context: Point::new(0, 0)..Point::new(3, 0),
13469 primary: None,
13470 },
13471 ExcerptRange {
13472 context: Point::new(5, 0)..Point::new(7, 0),
13473 primary: None,
13474 },
13475 ExcerptRange {
13476 context: Point::new(9, 0)..Point::new(10, 4),
13477 primary: None,
13478 },
13479 ],
13480 cx,
13481 );
13482 multibuffer
13483 });
13484
13485 let fs = FakeFs::new(cx.executor());
13486 fs.insert_tree(
13487 "/a",
13488 json!({
13489 "main.rs": sample_text_1,
13490 "other.rs": sample_text_2,
13491 "lib.rs": sample_text_3,
13492 }),
13493 )
13494 .await;
13495 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13496 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13497 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13498 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13499 Editor::new(
13500 EditorMode::Full,
13501 multi_buffer,
13502 Some(project.clone()),
13503 window,
13504 cx,
13505 )
13506 });
13507 let multibuffer_item_id = workspace
13508 .update(cx, |workspace, window, cx| {
13509 assert!(
13510 workspace.active_item(cx).is_none(),
13511 "active item should be None before the first item is added"
13512 );
13513 workspace.add_item_to_active_pane(
13514 Box::new(multi_buffer_editor.clone()),
13515 None,
13516 true,
13517 window,
13518 cx,
13519 );
13520 let active_item = workspace
13521 .active_item(cx)
13522 .expect("should have an active item after adding the multi buffer");
13523 assert!(
13524 !active_item.is_singleton(cx),
13525 "A multi buffer was expected to active after adding"
13526 );
13527 active_item.item_id()
13528 })
13529 .unwrap();
13530 cx.executor().run_until_parked();
13531
13532 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13533 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13534 s.select_ranges(Some(1..2))
13535 });
13536 editor.open_excerpts(&OpenExcerpts, window, cx);
13537 });
13538 cx.executor().run_until_parked();
13539 let first_item_id = workspace
13540 .update(cx, |workspace, window, cx| {
13541 let active_item = workspace
13542 .active_item(cx)
13543 .expect("should have an active item after navigating into the 1st buffer");
13544 let first_item_id = active_item.item_id();
13545 assert_ne!(
13546 first_item_id, multibuffer_item_id,
13547 "Should navigate into the 1st buffer and activate it"
13548 );
13549 assert!(
13550 active_item.is_singleton(cx),
13551 "New active item should be a singleton buffer"
13552 );
13553 assert_eq!(
13554 active_item
13555 .act_as::<Editor>(cx)
13556 .expect("should have navigated into an editor for the 1st buffer")
13557 .read(cx)
13558 .text(cx),
13559 sample_text_1
13560 );
13561
13562 workspace
13563 .go_back(workspace.active_pane().downgrade(), window, cx)
13564 .detach_and_log_err(cx);
13565
13566 first_item_id
13567 })
13568 .unwrap();
13569 cx.executor().run_until_parked();
13570 workspace
13571 .update(cx, |workspace, _, cx| {
13572 let active_item = workspace
13573 .active_item(cx)
13574 .expect("should have an active item after navigating back");
13575 assert_eq!(
13576 active_item.item_id(),
13577 multibuffer_item_id,
13578 "Should navigate back to the multi buffer"
13579 );
13580 assert!(!active_item.is_singleton(cx));
13581 })
13582 .unwrap();
13583
13584 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13585 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13586 s.select_ranges(Some(39..40))
13587 });
13588 editor.open_excerpts(&OpenExcerpts, window, cx);
13589 });
13590 cx.executor().run_until_parked();
13591 let second_item_id = workspace
13592 .update(cx, |workspace, window, cx| {
13593 let active_item = workspace
13594 .active_item(cx)
13595 .expect("should have an active item after navigating into the 2nd buffer");
13596 let second_item_id = active_item.item_id();
13597 assert_ne!(
13598 second_item_id, multibuffer_item_id,
13599 "Should navigate away from the multibuffer"
13600 );
13601 assert_ne!(
13602 second_item_id, first_item_id,
13603 "Should navigate into the 2nd buffer and activate it"
13604 );
13605 assert!(
13606 active_item.is_singleton(cx),
13607 "New active item should be a singleton buffer"
13608 );
13609 assert_eq!(
13610 active_item
13611 .act_as::<Editor>(cx)
13612 .expect("should have navigated into an editor")
13613 .read(cx)
13614 .text(cx),
13615 sample_text_2
13616 );
13617
13618 workspace
13619 .go_back(workspace.active_pane().downgrade(), window, cx)
13620 .detach_and_log_err(cx);
13621
13622 second_item_id
13623 })
13624 .unwrap();
13625 cx.executor().run_until_parked();
13626 workspace
13627 .update(cx, |workspace, _, cx| {
13628 let active_item = workspace
13629 .active_item(cx)
13630 .expect("should have an active item after navigating back from the 2nd buffer");
13631 assert_eq!(
13632 active_item.item_id(),
13633 multibuffer_item_id,
13634 "Should navigate back from the 2nd buffer to the multi buffer"
13635 );
13636 assert!(!active_item.is_singleton(cx));
13637 })
13638 .unwrap();
13639
13640 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13641 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13642 s.select_ranges(Some(70..70))
13643 });
13644 editor.open_excerpts(&OpenExcerpts, window, cx);
13645 });
13646 cx.executor().run_until_parked();
13647 workspace
13648 .update(cx, |workspace, window, cx| {
13649 let active_item = workspace
13650 .active_item(cx)
13651 .expect("should have an active item after navigating into the 3rd buffer");
13652 let third_item_id = active_item.item_id();
13653 assert_ne!(
13654 third_item_id, multibuffer_item_id,
13655 "Should navigate into the 3rd buffer and activate it"
13656 );
13657 assert_ne!(third_item_id, first_item_id);
13658 assert_ne!(third_item_id, second_item_id);
13659 assert!(
13660 active_item.is_singleton(cx),
13661 "New active item should be a singleton buffer"
13662 );
13663 assert_eq!(
13664 active_item
13665 .act_as::<Editor>(cx)
13666 .expect("should have navigated into an editor")
13667 .read(cx)
13668 .text(cx),
13669 sample_text_3
13670 );
13671
13672 workspace
13673 .go_back(workspace.active_pane().downgrade(), window, cx)
13674 .detach_and_log_err(cx);
13675 })
13676 .unwrap();
13677 cx.executor().run_until_parked();
13678 workspace
13679 .update(cx, |workspace, _, cx| {
13680 let active_item = workspace
13681 .active_item(cx)
13682 .expect("should have an active item after navigating back from the 3rd buffer");
13683 assert_eq!(
13684 active_item.item_id(),
13685 multibuffer_item_id,
13686 "Should navigate back from the 3rd buffer to the multi buffer"
13687 );
13688 assert!(!active_item.is_singleton(cx));
13689 })
13690 .unwrap();
13691}
13692
13693#[gpui::test]
13694async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13695 init_test(cx, |_| {});
13696
13697 let mut cx = EditorTestContext::new(cx).await;
13698
13699 let diff_base = r#"
13700 use some::mod;
13701
13702 const A: u32 = 42;
13703
13704 fn main() {
13705 println!("hello");
13706
13707 println!("world");
13708 }
13709 "#
13710 .unindent();
13711
13712 cx.set_state(
13713 &r#"
13714 use some::modified;
13715
13716 ˇ
13717 fn main() {
13718 println!("hello there");
13719
13720 println!("around the");
13721 println!("world");
13722 }
13723 "#
13724 .unindent(),
13725 );
13726
13727 cx.set_head_text(&diff_base);
13728 executor.run_until_parked();
13729
13730 cx.update_editor(|editor, window, cx| {
13731 editor.go_to_next_hunk(&GoToHunk, window, cx);
13732 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13733 });
13734 executor.run_until_parked();
13735 cx.assert_state_with_diff(
13736 r#"
13737 use some::modified;
13738
13739
13740 fn main() {
13741 - println!("hello");
13742 + ˇ println!("hello there");
13743
13744 println!("around the");
13745 println!("world");
13746 }
13747 "#
13748 .unindent(),
13749 );
13750
13751 cx.update_editor(|editor, window, cx| {
13752 for _ in 0..2 {
13753 editor.go_to_next_hunk(&GoToHunk, window, cx);
13754 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13755 }
13756 });
13757 executor.run_until_parked();
13758 cx.assert_state_with_diff(
13759 r#"
13760 - use some::mod;
13761 + ˇuse some::modified;
13762
13763
13764 fn main() {
13765 - println!("hello");
13766 + println!("hello there");
13767
13768 + println!("around the");
13769 println!("world");
13770 }
13771 "#
13772 .unindent(),
13773 );
13774
13775 cx.update_editor(|editor, window, cx| {
13776 editor.go_to_next_hunk(&GoToHunk, window, cx);
13777 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13778 });
13779 executor.run_until_parked();
13780 cx.assert_state_with_diff(
13781 r#"
13782 - use some::mod;
13783 + use some::modified;
13784
13785 - const A: u32 = 42;
13786 ˇ
13787 fn main() {
13788 - println!("hello");
13789 + println!("hello there");
13790
13791 + println!("around the");
13792 println!("world");
13793 }
13794 "#
13795 .unindent(),
13796 );
13797
13798 cx.update_editor(|editor, window, cx| {
13799 editor.cancel(&Cancel, window, cx);
13800 });
13801
13802 cx.assert_state_with_diff(
13803 r#"
13804 use some::modified;
13805
13806 ˇ
13807 fn main() {
13808 println!("hello there");
13809
13810 println!("around the");
13811 println!("world");
13812 }
13813 "#
13814 .unindent(),
13815 );
13816}
13817
13818#[gpui::test]
13819async fn test_diff_base_change_with_expanded_diff_hunks(
13820 executor: BackgroundExecutor,
13821 cx: &mut TestAppContext,
13822) {
13823 init_test(cx, |_| {});
13824
13825 let mut cx = EditorTestContext::new(cx).await;
13826
13827 let diff_base = r#"
13828 use some::mod1;
13829 use some::mod2;
13830
13831 const A: u32 = 42;
13832 const B: u32 = 42;
13833 const C: u32 = 42;
13834
13835 fn main() {
13836 println!("hello");
13837
13838 println!("world");
13839 }
13840 "#
13841 .unindent();
13842
13843 cx.set_state(
13844 &r#"
13845 use some::mod2;
13846
13847 const A: u32 = 42;
13848 const C: u32 = 42;
13849
13850 fn main(ˇ) {
13851 //println!("hello");
13852
13853 println!("world");
13854 //
13855 //
13856 }
13857 "#
13858 .unindent(),
13859 );
13860
13861 cx.set_head_text(&diff_base);
13862 executor.run_until_parked();
13863
13864 cx.update_editor(|editor, window, cx| {
13865 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13866 });
13867 executor.run_until_parked();
13868 cx.assert_state_with_diff(
13869 r#"
13870 - use some::mod1;
13871 use some::mod2;
13872
13873 const A: u32 = 42;
13874 - const B: u32 = 42;
13875 const C: u32 = 42;
13876
13877 fn main(ˇ) {
13878 - println!("hello");
13879 + //println!("hello");
13880
13881 println!("world");
13882 + //
13883 + //
13884 }
13885 "#
13886 .unindent(),
13887 );
13888
13889 cx.set_head_text("new diff base!");
13890 executor.run_until_parked();
13891 cx.assert_state_with_diff(
13892 r#"
13893 - new diff base!
13894 + use some::mod2;
13895 +
13896 + const A: u32 = 42;
13897 + const C: u32 = 42;
13898 +
13899 + fn main(ˇ) {
13900 + //println!("hello");
13901 +
13902 + println!("world");
13903 + //
13904 + //
13905 + }
13906 "#
13907 .unindent(),
13908 );
13909}
13910
13911#[gpui::test]
13912async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13913 init_test(cx, |_| {});
13914
13915 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13916 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13917 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13918 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13919 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13920 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13921
13922 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13923 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13924 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13925
13926 let multi_buffer = cx.new(|cx| {
13927 let mut multibuffer = MultiBuffer::new(ReadWrite);
13928 multibuffer.push_excerpts(
13929 buffer_1.clone(),
13930 [
13931 ExcerptRange {
13932 context: Point::new(0, 0)..Point::new(3, 0),
13933 primary: None,
13934 },
13935 ExcerptRange {
13936 context: Point::new(5, 0)..Point::new(7, 0),
13937 primary: None,
13938 },
13939 ExcerptRange {
13940 context: Point::new(9, 0)..Point::new(10, 3),
13941 primary: None,
13942 },
13943 ],
13944 cx,
13945 );
13946 multibuffer.push_excerpts(
13947 buffer_2.clone(),
13948 [
13949 ExcerptRange {
13950 context: Point::new(0, 0)..Point::new(3, 0),
13951 primary: None,
13952 },
13953 ExcerptRange {
13954 context: Point::new(5, 0)..Point::new(7, 0),
13955 primary: None,
13956 },
13957 ExcerptRange {
13958 context: Point::new(9, 0)..Point::new(10, 3),
13959 primary: None,
13960 },
13961 ],
13962 cx,
13963 );
13964 multibuffer.push_excerpts(
13965 buffer_3.clone(),
13966 [
13967 ExcerptRange {
13968 context: Point::new(0, 0)..Point::new(3, 0),
13969 primary: None,
13970 },
13971 ExcerptRange {
13972 context: Point::new(5, 0)..Point::new(7, 0),
13973 primary: None,
13974 },
13975 ExcerptRange {
13976 context: Point::new(9, 0)..Point::new(10, 3),
13977 primary: None,
13978 },
13979 ],
13980 cx,
13981 );
13982 multibuffer
13983 });
13984
13985 let editor =
13986 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
13987 editor
13988 .update(cx, |editor, _window, cx| {
13989 for (buffer, diff_base) in [
13990 (buffer_1.clone(), file_1_old),
13991 (buffer_2.clone(), file_2_old),
13992 (buffer_3.clone(), file_3_old),
13993 ] {
13994 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13995 editor
13996 .buffer
13997 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13998 }
13999 })
14000 .unwrap();
14001
14002 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14003 cx.run_until_parked();
14004
14005 cx.assert_editor_state(
14006 &"
14007 ˇaaa
14008 ccc
14009 ddd
14010
14011 ggg
14012 hhh
14013
14014
14015 lll
14016 mmm
14017 NNN
14018
14019 qqq
14020 rrr
14021
14022 uuu
14023 111
14024 222
14025 333
14026
14027 666
14028 777
14029
14030 000
14031 !!!"
14032 .unindent(),
14033 );
14034
14035 cx.update_editor(|editor, window, cx| {
14036 editor.select_all(&SelectAll, window, cx);
14037 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14038 });
14039 cx.executor().run_until_parked();
14040
14041 cx.assert_state_with_diff(
14042 "
14043 «aaa
14044 - bbb
14045 ccc
14046 ddd
14047
14048 ggg
14049 hhh
14050
14051
14052 lll
14053 mmm
14054 - nnn
14055 + NNN
14056
14057 qqq
14058 rrr
14059
14060 uuu
14061 111
14062 222
14063 333
14064
14065 + 666
14066 777
14067
14068 000
14069 !!!ˇ»"
14070 .unindent(),
14071 );
14072}
14073
14074#[gpui::test]
14075async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14076 init_test(cx, |_| {});
14077
14078 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14079 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14080
14081 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14082 let multi_buffer = cx.new(|cx| {
14083 let mut multibuffer = MultiBuffer::new(ReadWrite);
14084 multibuffer.push_excerpts(
14085 buffer.clone(),
14086 [
14087 ExcerptRange {
14088 context: Point::new(0, 0)..Point::new(2, 0),
14089 primary: None,
14090 },
14091 ExcerptRange {
14092 context: Point::new(4, 0)..Point::new(7, 0),
14093 primary: None,
14094 },
14095 ExcerptRange {
14096 context: Point::new(9, 0)..Point::new(10, 0),
14097 primary: None,
14098 },
14099 ],
14100 cx,
14101 );
14102 multibuffer
14103 });
14104
14105 let editor =
14106 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14107 editor
14108 .update(cx, |editor, _window, cx| {
14109 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14110 editor
14111 .buffer
14112 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14113 })
14114 .unwrap();
14115
14116 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14117 cx.run_until_parked();
14118
14119 cx.update_editor(|editor, window, cx| {
14120 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14121 });
14122 cx.executor().run_until_parked();
14123
14124 // When the start of a hunk coincides with the start of its excerpt,
14125 // the hunk is expanded. When the start of a a hunk is earlier than
14126 // the start of its excerpt, the hunk is not expanded.
14127 cx.assert_state_with_diff(
14128 "
14129 ˇaaa
14130 - bbb
14131 + BBB
14132
14133 - ddd
14134 - eee
14135 + DDD
14136 + EEE
14137 fff
14138
14139 iii
14140 "
14141 .unindent(),
14142 );
14143}
14144
14145#[gpui::test]
14146async fn test_edits_around_expanded_insertion_hunks(
14147 executor: BackgroundExecutor,
14148 cx: &mut TestAppContext,
14149) {
14150 init_test(cx, |_| {});
14151
14152 let mut cx = EditorTestContext::new(cx).await;
14153
14154 let diff_base = r#"
14155 use some::mod1;
14156 use some::mod2;
14157
14158 const A: u32 = 42;
14159
14160 fn main() {
14161 println!("hello");
14162
14163 println!("world");
14164 }
14165 "#
14166 .unindent();
14167 executor.run_until_parked();
14168 cx.set_state(
14169 &r#"
14170 use some::mod1;
14171 use some::mod2;
14172
14173 const A: u32 = 42;
14174 const B: u32 = 42;
14175 const C: u32 = 42;
14176 ˇ
14177
14178 fn main() {
14179 println!("hello");
14180
14181 println!("world");
14182 }
14183 "#
14184 .unindent(),
14185 );
14186
14187 cx.set_head_text(&diff_base);
14188 executor.run_until_parked();
14189
14190 cx.update_editor(|editor, window, cx| {
14191 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14192 });
14193 executor.run_until_parked();
14194
14195 cx.assert_state_with_diff(
14196 r#"
14197 use some::mod1;
14198 use some::mod2;
14199
14200 const A: u32 = 42;
14201 + const B: u32 = 42;
14202 + const C: u32 = 42;
14203 + ˇ
14204
14205 fn main() {
14206 println!("hello");
14207
14208 println!("world");
14209 }
14210 "#
14211 .unindent(),
14212 );
14213
14214 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14215 executor.run_until_parked();
14216
14217 cx.assert_state_with_diff(
14218 r#"
14219 use some::mod1;
14220 use some::mod2;
14221
14222 const A: u32 = 42;
14223 + const B: u32 = 42;
14224 + const C: u32 = 42;
14225 + const D: u32 = 42;
14226 + ˇ
14227
14228 fn main() {
14229 println!("hello");
14230
14231 println!("world");
14232 }
14233 "#
14234 .unindent(),
14235 );
14236
14237 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14238 executor.run_until_parked();
14239
14240 cx.assert_state_with_diff(
14241 r#"
14242 use some::mod1;
14243 use some::mod2;
14244
14245 const A: u32 = 42;
14246 + const B: u32 = 42;
14247 + const C: u32 = 42;
14248 + const D: u32 = 42;
14249 + const E: u32 = 42;
14250 + ˇ
14251
14252 fn main() {
14253 println!("hello");
14254
14255 println!("world");
14256 }
14257 "#
14258 .unindent(),
14259 );
14260
14261 cx.update_editor(|editor, window, cx| {
14262 editor.delete_line(&DeleteLine, window, cx);
14263 });
14264 executor.run_until_parked();
14265
14266 cx.assert_state_with_diff(
14267 r#"
14268 use some::mod1;
14269 use some::mod2;
14270
14271 const A: u32 = 42;
14272 + const B: u32 = 42;
14273 + const C: u32 = 42;
14274 + const D: u32 = 42;
14275 + const E: u32 = 42;
14276 ˇ
14277 fn main() {
14278 println!("hello");
14279
14280 println!("world");
14281 }
14282 "#
14283 .unindent(),
14284 );
14285
14286 cx.update_editor(|editor, window, cx| {
14287 editor.move_up(&MoveUp, window, cx);
14288 editor.delete_line(&DeleteLine, window, cx);
14289 editor.move_up(&MoveUp, window, cx);
14290 editor.delete_line(&DeleteLine, window, cx);
14291 editor.move_up(&MoveUp, window, cx);
14292 editor.delete_line(&DeleteLine, window, cx);
14293 });
14294 executor.run_until_parked();
14295 cx.assert_state_with_diff(
14296 r#"
14297 use some::mod1;
14298 use some::mod2;
14299
14300 const A: u32 = 42;
14301 + const B: u32 = 42;
14302 ˇ
14303 fn main() {
14304 println!("hello");
14305
14306 println!("world");
14307 }
14308 "#
14309 .unindent(),
14310 );
14311
14312 cx.update_editor(|editor, window, cx| {
14313 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14314 editor.delete_line(&DeleteLine, window, cx);
14315 });
14316 executor.run_until_parked();
14317 cx.assert_state_with_diff(
14318 r#"
14319 ˇ
14320 fn main() {
14321 println!("hello");
14322
14323 println!("world");
14324 }
14325 "#
14326 .unindent(),
14327 );
14328}
14329
14330#[gpui::test]
14331async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14332 init_test(cx, |_| {});
14333
14334 let mut cx = EditorTestContext::new(cx).await;
14335 cx.set_head_text(indoc! { "
14336 one
14337 two
14338 three
14339 four
14340 five
14341 "
14342 });
14343 cx.set_state(indoc! { "
14344 one
14345 ˇthree
14346 five
14347 "});
14348 cx.run_until_parked();
14349 cx.update_editor(|editor, window, cx| {
14350 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14351 });
14352 cx.assert_state_with_diff(
14353 indoc! { "
14354 one
14355 - two
14356 ˇthree
14357 - four
14358 five
14359 "}
14360 .to_string(),
14361 );
14362 cx.update_editor(|editor, window, cx| {
14363 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14364 });
14365
14366 cx.assert_state_with_diff(
14367 indoc! { "
14368 one
14369 ˇthree
14370 five
14371 "}
14372 .to_string(),
14373 );
14374
14375 cx.set_state(indoc! { "
14376 one
14377 ˇTWO
14378 three
14379 four
14380 five
14381 "});
14382 cx.run_until_parked();
14383 cx.update_editor(|editor, window, cx| {
14384 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14385 });
14386
14387 cx.assert_state_with_diff(
14388 indoc! { "
14389 one
14390 - two
14391 + ˇTWO
14392 three
14393 four
14394 five
14395 "}
14396 .to_string(),
14397 );
14398 cx.update_editor(|editor, window, cx| {
14399 editor.move_up(&Default::default(), window, cx);
14400 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14401 });
14402 cx.assert_state_with_diff(
14403 indoc! { "
14404 one
14405 ˇTWO
14406 three
14407 four
14408 five
14409 "}
14410 .to_string(),
14411 );
14412}
14413
14414#[gpui::test]
14415async fn test_edits_around_expanded_deletion_hunks(
14416 executor: BackgroundExecutor,
14417 cx: &mut TestAppContext,
14418) {
14419 init_test(cx, |_| {});
14420
14421 let mut cx = EditorTestContext::new(cx).await;
14422
14423 let diff_base = r#"
14424 use some::mod1;
14425 use some::mod2;
14426
14427 const A: u32 = 42;
14428 const B: u32 = 42;
14429 const C: u32 = 42;
14430
14431
14432 fn main() {
14433 println!("hello");
14434
14435 println!("world");
14436 }
14437 "#
14438 .unindent();
14439 executor.run_until_parked();
14440 cx.set_state(
14441 &r#"
14442 use some::mod1;
14443 use some::mod2;
14444
14445 ˇconst B: u32 = 42;
14446 const C: u32 = 42;
14447
14448
14449 fn main() {
14450 println!("hello");
14451
14452 println!("world");
14453 }
14454 "#
14455 .unindent(),
14456 );
14457
14458 cx.set_head_text(&diff_base);
14459 executor.run_until_parked();
14460
14461 cx.update_editor(|editor, window, cx| {
14462 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14463 });
14464 executor.run_until_parked();
14465
14466 cx.assert_state_with_diff(
14467 r#"
14468 use some::mod1;
14469 use some::mod2;
14470
14471 - const A: u32 = 42;
14472 ˇconst B: u32 = 42;
14473 const C: u32 = 42;
14474
14475
14476 fn main() {
14477 println!("hello");
14478
14479 println!("world");
14480 }
14481 "#
14482 .unindent(),
14483 );
14484
14485 cx.update_editor(|editor, window, cx| {
14486 editor.delete_line(&DeleteLine, window, cx);
14487 });
14488 executor.run_until_parked();
14489 cx.assert_state_with_diff(
14490 r#"
14491 use some::mod1;
14492 use some::mod2;
14493
14494 - const A: u32 = 42;
14495 - const B: u32 = 42;
14496 ˇconst C: u32 = 42;
14497
14498
14499 fn main() {
14500 println!("hello");
14501
14502 println!("world");
14503 }
14504 "#
14505 .unindent(),
14506 );
14507
14508 cx.update_editor(|editor, window, cx| {
14509 editor.delete_line(&DeleteLine, window, cx);
14510 });
14511 executor.run_until_parked();
14512 cx.assert_state_with_diff(
14513 r#"
14514 use some::mod1;
14515 use some::mod2;
14516
14517 - const A: u32 = 42;
14518 - const B: u32 = 42;
14519 - const C: u32 = 42;
14520 ˇ
14521
14522 fn main() {
14523 println!("hello");
14524
14525 println!("world");
14526 }
14527 "#
14528 .unindent(),
14529 );
14530
14531 cx.update_editor(|editor, window, cx| {
14532 editor.handle_input("replacement", window, cx);
14533 });
14534 executor.run_until_parked();
14535 cx.assert_state_with_diff(
14536 r#"
14537 use some::mod1;
14538 use some::mod2;
14539
14540 - const A: u32 = 42;
14541 - const B: u32 = 42;
14542 - const C: u32 = 42;
14543 -
14544 + replacementˇ
14545
14546 fn main() {
14547 println!("hello");
14548
14549 println!("world");
14550 }
14551 "#
14552 .unindent(),
14553 );
14554}
14555
14556#[gpui::test]
14557async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14558 init_test(cx, |_| {});
14559
14560 let mut cx = EditorTestContext::new(cx).await;
14561
14562 let base_text = r#"
14563 one
14564 two
14565 three
14566 four
14567 five
14568 "#
14569 .unindent();
14570 executor.run_until_parked();
14571 cx.set_state(
14572 &r#"
14573 one
14574 two
14575 fˇour
14576 five
14577 "#
14578 .unindent(),
14579 );
14580
14581 cx.set_head_text(&base_text);
14582 executor.run_until_parked();
14583
14584 cx.update_editor(|editor, window, cx| {
14585 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14586 });
14587 executor.run_until_parked();
14588
14589 cx.assert_state_with_diff(
14590 r#"
14591 one
14592 two
14593 - three
14594 fˇour
14595 five
14596 "#
14597 .unindent(),
14598 );
14599
14600 cx.update_editor(|editor, window, cx| {
14601 editor.backspace(&Backspace, window, cx);
14602 editor.backspace(&Backspace, window, cx);
14603 });
14604 executor.run_until_parked();
14605 cx.assert_state_with_diff(
14606 r#"
14607 one
14608 two
14609 - threeˇ
14610 - four
14611 + our
14612 five
14613 "#
14614 .unindent(),
14615 );
14616}
14617
14618#[gpui::test]
14619async fn test_edit_after_expanded_modification_hunk(
14620 executor: BackgroundExecutor,
14621 cx: &mut TestAppContext,
14622) {
14623 init_test(cx, |_| {});
14624
14625 let mut cx = EditorTestContext::new(cx).await;
14626
14627 let diff_base = r#"
14628 use some::mod1;
14629 use some::mod2;
14630
14631 const A: u32 = 42;
14632 const B: u32 = 42;
14633 const C: u32 = 42;
14634 const D: u32 = 42;
14635
14636
14637 fn main() {
14638 println!("hello");
14639
14640 println!("world");
14641 }"#
14642 .unindent();
14643
14644 cx.set_state(
14645 &r#"
14646 use some::mod1;
14647 use some::mod2;
14648
14649 const A: u32 = 42;
14650 const B: u32 = 42;
14651 const C: u32 = 43ˇ
14652 const D: u32 = 42;
14653
14654
14655 fn main() {
14656 println!("hello");
14657
14658 println!("world");
14659 }"#
14660 .unindent(),
14661 );
14662
14663 cx.set_head_text(&diff_base);
14664 executor.run_until_parked();
14665 cx.update_editor(|editor, window, cx| {
14666 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14667 });
14668 executor.run_until_parked();
14669
14670 cx.assert_state_with_diff(
14671 r#"
14672 use some::mod1;
14673 use some::mod2;
14674
14675 const A: u32 = 42;
14676 const B: u32 = 42;
14677 - const C: u32 = 42;
14678 + const C: u32 = 43ˇ
14679 const D: u32 = 42;
14680
14681
14682 fn main() {
14683 println!("hello");
14684
14685 println!("world");
14686 }"#
14687 .unindent(),
14688 );
14689
14690 cx.update_editor(|editor, window, cx| {
14691 editor.handle_input("\nnew_line\n", window, cx);
14692 });
14693 executor.run_until_parked();
14694
14695 cx.assert_state_with_diff(
14696 r#"
14697 use some::mod1;
14698 use some::mod2;
14699
14700 const A: u32 = 42;
14701 const B: u32 = 42;
14702 - const C: u32 = 42;
14703 + const C: u32 = 43
14704 + new_line
14705 + ˇ
14706 const D: u32 = 42;
14707
14708
14709 fn main() {
14710 println!("hello");
14711
14712 println!("world");
14713 }"#
14714 .unindent(),
14715 );
14716}
14717
14718#[gpui::test]
14719async fn test_stage_and_unstage_added_file_hunk(
14720 executor: BackgroundExecutor,
14721 cx: &mut TestAppContext,
14722) {
14723 init_test(cx, |_| {});
14724
14725 let mut cx = EditorTestContext::new(cx).await;
14726 cx.update_editor(|editor, _, cx| {
14727 editor.set_expand_all_diff_hunks(cx);
14728 });
14729
14730 let working_copy = r#"
14731 ˇfn main() {
14732 println!("hello, world!");
14733 }
14734 "#
14735 .unindent();
14736
14737 cx.set_state(&working_copy);
14738 executor.run_until_parked();
14739
14740 cx.assert_state_with_diff(
14741 r#"
14742 + ˇfn main() {
14743 + println!("hello, world!");
14744 + }
14745 "#
14746 .unindent(),
14747 );
14748 cx.assert_index_text(None);
14749
14750 cx.update_editor(|editor, window, cx| {
14751 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14752 });
14753 executor.run_until_parked();
14754 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14755 cx.assert_state_with_diff(
14756 r#"
14757 + ˇfn main() {
14758 + println!("hello, world!");
14759 + }
14760 "#
14761 .unindent(),
14762 );
14763
14764 cx.update_editor(|editor, window, cx| {
14765 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14766 });
14767 executor.run_until_parked();
14768 cx.assert_index_text(None);
14769}
14770
14771async fn setup_indent_guides_editor(
14772 text: &str,
14773 cx: &mut TestAppContext,
14774) -> (BufferId, EditorTestContext) {
14775 init_test(cx, |_| {});
14776
14777 let mut cx = EditorTestContext::new(cx).await;
14778
14779 let buffer_id = cx.update_editor(|editor, window, cx| {
14780 editor.set_text(text, window, cx);
14781 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14782
14783 buffer_ids[0]
14784 });
14785
14786 (buffer_id, cx)
14787}
14788
14789fn assert_indent_guides(
14790 range: Range<u32>,
14791 expected: Vec<IndentGuide>,
14792 active_indices: Option<Vec<usize>>,
14793 cx: &mut EditorTestContext,
14794) {
14795 let indent_guides = cx.update_editor(|editor, window, cx| {
14796 let snapshot = editor.snapshot(window, cx).display_snapshot;
14797 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14798 editor,
14799 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14800 true,
14801 &snapshot,
14802 cx,
14803 );
14804
14805 indent_guides.sort_by(|a, b| {
14806 a.depth.cmp(&b.depth).then(
14807 a.start_row
14808 .cmp(&b.start_row)
14809 .then(a.end_row.cmp(&b.end_row)),
14810 )
14811 });
14812 indent_guides
14813 });
14814
14815 if let Some(expected) = active_indices {
14816 let active_indices = cx.update_editor(|editor, window, cx| {
14817 let snapshot = editor.snapshot(window, cx).display_snapshot;
14818 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14819 });
14820
14821 assert_eq!(
14822 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14823 expected,
14824 "Active indent guide indices do not match"
14825 );
14826 }
14827
14828 assert_eq!(indent_guides, expected, "Indent guides do not match");
14829}
14830
14831fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14832 IndentGuide {
14833 buffer_id,
14834 start_row: MultiBufferRow(start_row),
14835 end_row: MultiBufferRow(end_row),
14836 depth,
14837 tab_size: 4,
14838 settings: IndentGuideSettings {
14839 enabled: true,
14840 line_width: 1,
14841 active_line_width: 1,
14842 ..Default::default()
14843 },
14844 }
14845}
14846
14847#[gpui::test]
14848async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14849 let (buffer_id, mut cx) = setup_indent_guides_editor(
14850 &"
14851 fn main() {
14852 let a = 1;
14853 }"
14854 .unindent(),
14855 cx,
14856 )
14857 .await;
14858
14859 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14860}
14861
14862#[gpui::test]
14863async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14864 let (buffer_id, mut cx) = setup_indent_guides_editor(
14865 &"
14866 fn main() {
14867 let a = 1;
14868 let b = 2;
14869 }"
14870 .unindent(),
14871 cx,
14872 )
14873 .await;
14874
14875 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14876}
14877
14878#[gpui::test]
14879async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14880 let (buffer_id, mut cx) = setup_indent_guides_editor(
14881 &"
14882 fn main() {
14883 let a = 1;
14884 if a == 3 {
14885 let b = 2;
14886 } else {
14887 let c = 3;
14888 }
14889 }"
14890 .unindent(),
14891 cx,
14892 )
14893 .await;
14894
14895 assert_indent_guides(
14896 0..8,
14897 vec![
14898 indent_guide(buffer_id, 1, 6, 0),
14899 indent_guide(buffer_id, 3, 3, 1),
14900 indent_guide(buffer_id, 5, 5, 1),
14901 ],
14902 None,
14903 &mut cx,
14904 );
14905}
14906
14907#[gpui::test]
14908async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14909 let (buffer_id, mut cx) = setup_indent_guides_editor(
14910 &"
14911 fn main() {
14912 let a = 1;
14913 let b = 2;
14914 let c = 3;
14915 }"
14916 .unindent(),
14917 cx,
14918 )
14919 .await;
14920
14921 assert_indent_guides(
14922 0..5,
14923 vec![
14924 indent_guide(buffer_id, 1, 3, 0),
14925 indent_guide(buffer_id, 2, 2, 1),
14926 ],
14927 None,
14928 &mut cx,
14929 );
14930}
14931
14932#[gpui::test]
14933async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14934 let (buffer_id, mut cx) = setup_indent_guides_editor(
14935 &"
14936 fn main() {
14937 let a = 1;
14938
14939 let c = 3;
14940 }"
14941 .unindent(),
14942 cx,
14943 )
14944 .await;
14945
14946 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14947}
14948
14949#[gpui::test]
14950async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14951 let (buffer_id, mut cx) = setup_indent_guides_editor(
14952 &"
14953 fn main() {
14954 let a = 1;
14955
14956 let c = 3;
14957
14958 if a == 3 {
14959 let b = 2;
14960 } else {
14961 let c = 3;
14962 }
14963 }"
14964 .unindent(),
14965 cx,
14966 )
14967 .await;
14968
14969 assert_indent_guides(
14970 0..11,
14971 vec![
14972 indent_guide(buffer_id, 1, 9, 0),
14973 indent_guide(buffer_id, 6, 6, 1),
14974 indent_guide(buffer_id, 8, 8, 1),
14975 ],
14976 None,
14977 &mut cx,
14978 );
14979}
14980
14981#[gpui::test]
14982async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14983 let (buffer_id, mut cx) = setup_indent_guides_editor(
14984 &"
14985 fn main() {
14986 let a = 1;
14987
14988 let c = 3;
14989
14990 if a == 3 {
14991 let b = 2;
14992 } else {
14993 let c = 3;
14994 }
14995 }"
14996 .unindent(),
14997 cx,
14998 )
14999 .await;
15000
15001 assert_indent_guides(
15002 1..11,
15003 vec![
15004 indent_guide(buffer_id, 1, 9, 0),
15005 indent_guide(buffer_id, 6, 6, 1),
15006 indent_guide(buffer_id, 8, 8, 1),
15007 ],
15008 None,
15009 &mut cx,
15010 );
15011}
15012
15013#[gpui::test]
15014async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15015 let (buffer_id, mut cx) = setup_indent_guides_editor(
15016 &"
15017 fn main() {
15018 let a = 1;
15019
15020 let c = 3;
15021
15022 if a == 3 {
15023 let b = 2;
15024 } else {
15025 let c = 3;
15026 }
15027 }"
15028 .unindent(),
15029 cx,
15030 )
15031 .await;
15032
15033 assert_indent_guides(
15034 1..10,
15035 vec![
15036 indent_guide(buffer_id, 1, 9, 0),
15037 indent_guide(buffer_id, 6, 6, 1),
15038 indent_guide(buffer_id, 8, 8, 1),
15039 ],
15040 None,
15041 &mut cx,
15042 );
15043}
15044
15045#[gpui::test]
15046async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15047 let (buffer_id, mut cx) = setup_indent_guides_editor(
15048 &"
15049 block1
15050 block2
15051 block3
15052 block4
15053 block2
15054 block1
15055 block1"
15056 .unindent(),
15057 cx,
15058 )
15059 .await;
15060
15061 assert_indent_guides(
15062 1..10,
15063 vec![
15064 indent_guide(buffer_id, 1, 4, 0),
15065 indent_guide(buffer_id, 2, 3, 1),
15066 indent_guide(buffer_id, 3, 3, 2),
15067 ],
15068 None,
15069 &mut cx,
15070 );
15071}
15072
15073#[gpui::test]
15074async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15075 let (buffer_id, mut cx) = setup_indent_guides_editor(
15076 &"
15077 block1
15078 block2
15079 block3
15080
15081 block1
15082 block1"
15083 .unindent(),
15084 cx,
15085 )
15086 .await;
15087
15088 assert_indent_guides(
15089 0..6,
15090 vec![
15091 indent_guide(buffer_id, 1, 2, 0),
15092 indent_guide(buffer_id, 2, 2, 1),
15093 ],
15094 None,
15095 &mut cx,
15096 );
15097}
15098
15099#[gpui::test]
15100async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15101 let (buffer_id, mut cx) = setup_indent_guides_editor(
15102 &"
15103 block1
15104
15105
15106
15107 block2
15108 "
15109 .unindent(),
15110 cx,
15111 )
15112 .await;
15113
15114 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15115}
15116
15117#[gpui::test]
15118async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15119 let (buffer_id, mut cx) = setup_indent_guides_editor(
15120 &"
15121 def a:
15122 \tb = 3
15123 \tif True:
15124 \t\tc = 4
15125 \t\td = 5
15126 \tprint(b)
15127 "
15128 .unindent(),
15129 cx,
15130 )
15131 .await;
15132
15133 assert_indent_guides(
15134 0..6,
15135 vec![
15136 indent_guide(buffer_id, 1, 6, 0),
15137 indent_guide(buffer_id, 3, 4, 1),
15138 ],
15139 None,
15140 &mut cx,
15141 );
15142}
15143
15144#[gpui::test]
15145async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15146 let (buffer_id, mut cx) = setup_indent_guides_editor(
15147 &"
15148 fn main() {
15149 let a = 1;
15150 }"
15151 .unindent(),
15152 cx,
15153 )
15154 .await;
15155
15156 cx.update_editor(|editor, window, cx| {
15157 editor.change_selections(None, window, cx, |s| {
15158 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15159 });
15160 });
15161
15162 assert_indent_guides(
15163 0..3,
15164 vec![indent_guide(buffer_id, 1, 1, 0)],
15165 Some(vec![0]),
15166 &mut cx,
15167 );
15168}
15169
15170#[gpui::test]
15171async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15172 let (buffer_id, mut cx) = setup_indent_guides_editor(
15173 &"
15174 fn main() {
15175 if 1 == 2 {
15176 let a = 1;
15177 }
15178 }"
15179 .unindent(),
15180 cx,
15181 )
15182 .await;
15183
15184 cx.update_editor(|editor, window, cx| {
15185 editor.change_selections(None, window, cx, |s| {
15186 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15187 });
15188 });
15189
15190 assert_indent_guides(
15191 0..4,
15192 vec![
15193 indent_guide(buffer_id, 1, 3, 0),
15194 indent_guide(buffer_id, 2, 2, 1),
15195 ],
15196 Some(vec![1]),
15197 &mut cx,
15198 );
15199
15200 cx.update_editor(|editor, window, cx| {
15201 editor.change_selections(None, window, cx, |s| {
15202 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15203 });
15204 });
15205
15206 assert_indent_guides(
15207 0..4,
15208 vec![
15209 indent_guide(buffer_id, 1, 3, 0),
15210 indent_guide(buffer_id, 2, 2, 1),
15211 ],
15212 Some(vec![1]),
15213 &mut cx,
15214 );
15215
15216 cx.update_editor(|editor, window, cx| {
15217 editor.change_selections(None, window, cx, |s| {
15218 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15219 });
15220 });
15221
15222 assert_indent_guides(
15223 0..4,
15224 vec![
15225 indent_guide(buffer_id, 1, 3, 0),
15226 indent_guide(buffer_id, 2, 2, 1),
15227 ],
15228 Some(vec![0]),
15229 &mut cx,
15230 );
15231}
15232
15233#[gpui::test]
15234async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15235 let (buffer_id, mut cx) = setup_indent_guides_editor(
15236 &"
15237 fn main() {
15238 let a = 1;
15239
15240 let b = 2;
15241 }"
15242 .unindent(),
15243 cx,
15244 )
15245 .await;
15246
15247 cx.update_editor(|editor, window, cx| {
15248 editor.change_selections(None, window, cx, |s| {
15249 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15250 });
15251 });
15252
15253 assert_indent_guides(
15254 0..5,
15255 vec![indent_guide(buffer_id, 1, 3, 0)],
15256 Some(vec![0]),
15257 &mut cx,
15258 );
15259}
15260
15261#[gpui::test]
15262async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15263 let (buffer_id, mut cx) = setup_indent_guides_editor(
15264 &"
15265 def m:
15266 a = 1
15267 pass"
15268 .unindent(),
15269 cx,
15270 )
15271 .await;
15272
15273 cx.update_editor(|editor, window, cx| {
15274 editor.change_selections(None, window, cx, |s| {
15275 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15276 });
15277 });
15278
15279 assert_indent_guides(
15280 0..3,
15281 vec![indent_guide(buffer_id, 1, 2, 0)],
15282 Some(vec![0]),
15283 &mut cx,
15284 );
15285}
15286
15287#[gpui::test]
15288async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15289 init_test(cx, |_| {});
15290 let mut cx = EditorTestContext::new(cx).await;
15291 let text = indoc! {
15292 "
15293 impl A {
15294 fn b() {
15295 0;
15296 3;
15297 5;
15298 6;
15299 7;
15300 }
15301 }
15302 "
15303 };
15304 let base_text = indoc! {
15305 "
15306 impl A {
15307 fn b() {
15308 0;
15309 1;
15310 2;
15311 3;
15312 4;
15313 }
15314 fn c() {
15315 5;
15316 6;
15317 7;
15318 }
15319 }
15320 "
15321 };
15322
15323 cx.update_editor(|editor, window, cx| {
15324 editor.set_text(text, window, cx);
15325
15326 editor.buffer().update(cx, |multibuffer, cx| {
15327 let buffer = multibuffer.as_singleton().unwrap();
15328 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15329
15330 multibuffer.set_all_diff_hunks_expanded(cx);
15331 multibuffer.add_diff(diff, cx);
15332
15333 buffer.read(cx).remote_id()
15334 })
15335 });
15336 cx.run_until_parked();
15337
15338 cx.assert_state_with_diff(
15339 indoc! { "
15340 impl A {
15341 fn b() {
15342 0;
15343 - 1;
15344 - 2;
15345 3;
15346 - 4;
15347 - }
15348 - fn c() {
15349 5;
15350 6;
15351 7;
15352 }
15353 }
15354 ˇ"
15355 }
15356 .to_string(),
15357 );
15358
15359 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15360 editor
15361 .snapshot(window, cx)
15362 .buffer_snapshot
15363 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15364 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15365 .collect::<Vec<_>>()
15366 });
15367 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15368 assert_eq!(
15369 actual_guides,
15370 vec![
15371 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15372 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15373 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15374 ]
15375 );
15376}
15377
15378#[gpui::test]
15379async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15380 init_test(cx, |_| {});
15381 let mut cx = EditorTestContext::new(cx).await;
15382
15383 let diff_base = r#"
15384 a
15385 b
15386 c
15387 "#
15388 .unindent();
15389
15390 cx.set_state(
15391 &r#"
15392 ˇA
15393 b
15394 C
15395 "#
15396 .unindent(),
15397 );
15398 cx.set_head_text(&diff_base);
15399 cx.update_editor(|editor, window, cx| {
15400 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15401 });
15402 executor.run_until_parked();
15403
15404 let both_hunks_expanded = r#"
15405 - a
15406 + ˇA
15407 b
15408 - c
15409 + C
15410 "#
15411 .unindent();
15412
15413 cx.assert_state_with_diff(both_hunks_expanded.clone());
15414
15415 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15416 let snapshot = editor.snapshot(window, cx);
15417 let hunks = editor
15418 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15419 .collect::<Vec<_>>();
15420 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15421 let buffer_id = hunks[0].buffer_id;
15422 hunks
15423 .into_iter()
15424 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15425 .collect::<Vec<_>>()
15426 });
15427 assert_eq!(hunk_ranges.len(), 2);
15428
15429 cx.update_editor(|editor, _, cx| {
15430 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15431 });
15432 executor.run_until_parked();
15433
15434 let second_hunk_expanded = r#"
15435 ˇA
15436 b
15437 - c
15438 + C
15439 "#
15440 .unindent();
15441
15442 cx.assert_state_with_diff(second_hunk_expanded);
15443
15444 cx.update_editor(|editor, _, cx| {
15445 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15446 });
15447 executor.run_until_parked();
15448
15449 cx.assert_state_with_diff(both_hunks_expanded.clone());
15450
15451 cx.update_editor(|editor, _, cx| {
15452 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15453 });
15454 executor.run_until_parked();
15455
15456 let first_hunk_expanded = r#"
15457 - a
15458 + ˇA
15459 b
15460 C
15461 "#
15462 .unindent();
15463
15464 cx.assert_state_with_diff(first_hunk_expanded);
15465
15466 cx.update_editor(|editor, _, cx| {
15467 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15468 });
15469 executor.run_until_parked();
15470
15471 cx.assert_state_with_diff(both_hunks_expanded);
15472
15473 cx.set_state(
15474 &r#"
15475 ˇA
15476 b
15477 "#
15478 .unindent(),
15479 );
15480 cx.run_until_parked();
15481
15482 // TODO this cursor position seems bad
15483 cx.assert_state_with_diff(
15484 r#"
15485 - ˇa
15486 + A
15487 b
15488 "#
15489 .unindent(),
15490 );
15491
15492 cx.update_editor(|editor, window, cx| {
15493 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15494 });
15495
15496 cx.assert_state_with_diff(
15497 r#"
15498 - ˇa
15499 + A
15500 b
15501 - c
15502 "#
15503 .unindent(),
15504 );
15505
15506 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15507 let snapshot = editor.snapshot(window, cx);
15508 let hunks = editor
15509 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15510 .collect::<Vec<_>>();
15511 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15512 let buffer_id = hunks[0].buffer_id;
15513 hunks
15514 .into_iter()
15515 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15516 .collect::<Vec<_>>()
15517 });
15518 assert_eq!(hunk_ranges.len(), 2);
15519
15520 cx.update_editor(|editor, _, cx| {
15521 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15522 });
15523 executor.run_until_parked();
15524
15525 cx.assert_state_with_diff(
15526 r#"
15527 - ˇa
15528 + A
15529 b
15530 "#
15531 .unindent(),
15532 );
15533}
15534
15535#[gpui::test]
15536async fn test_toggle_deletion_hunk_at_start_of_file(
15537 executor: BackgroundExecutor,
15538 cx: &mut TestAppContext,
15539) {
15540 init_test(cx, |_| {});
15541 let mut cx = EditorTestContext::new(cx).await;
15542
15543 let diff_base = r#"
15544 a
15545 b
15546 c
15547 "#
15548 .unindent();
15549
15550 cx.set_state(
15551 &r#"
15552 ˇb
15553 c
15554 "#
15555 .unindent(),
15556 );
15557 cx.set_head_text(&diff_base);
15558 cx.update_editor(|editor, window, cx| {
15559 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15560 });
15561 executor.run_until_parked();
15562
15563 let hunk_expanded = r#"
15564 - a
15565 ˇb
15566 c
15567 "#
15568 .unindent();
15569
15570 cx.assert_state_with_diff(hunk_expanded.clone());
15571
15572 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15573 let snapshot = editor.snapshot(window, cx);
15574 let hunks = editor
15575 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15576 .collect::<Vec<_>>();
15577 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15578 let buffer_id = hunks[0].buffer_id;
15579 hunks
15580 .into_iter()
15581 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15582 .collect::<Vec<_>>()
15583 });
15584 assert_eq!(hunk_ranges.len(), 1);
15585
15586 cx.update_editor(|editor, _, cx| {
15587 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15588 });
15589 executor.run_until_parked();
15590
15591 let hunk_collapsed = r#"
15592 ˇb
15593 c
15594 "#
15595 .unindent();
15596
15597 cx.assert_state_with_diff(hunk_collapsed);
15598
15599 cx.update_editor(|editor, _, cx| {
15600 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15601 });
15602 executor.run_until_parked();
15603
15604 cx.assert_state_with_diff(hunk_expanded.clone());
15605}
15606
15607#[gpui::test]
15608async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15609 init_test(cx, |_| {});
15610
15611 let fs = FakeFs::new(cx.executor());
15612 fs.insert_tree(
15613 path!("/test"),
15614 json!({
15615 ".git": {},
15616 "file-1": "ONE\n",
15617 "file-2": "TWO\n",
15618 "file-3": "THREE\n",
15619 }),
15620 )
15621 .await;
15622
15623 fs.set_head_for_repo(
15624 path!("/test/.git").as_ref(),
15625 &[
15626 ("file-1".into(), "one\n".into()),
15627 ("file-2".into(), "two\n".into()),
15628 ("file-3".into(), "three\n".into()),
15629 ],
15630 );
15631
15632 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15633 let mut buffers = vec![];
15634 for i in 1..=3 {
15635 let buffer = project
15636 .update(cx, |project, cx| {
15637 let path = format!(path!("/test/file-{}"), i);
15638 project.open_local_buffer(path, cx)
15639 })
15640 .await
15641 .unwrap();
15642 buffers.push(buffer);
15643 }
15644
15645 let multibuffer = cx.new(|cx| {
15646 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15647 multibuffer.set_all_diff_hunks_expanded(cx);
15648 for buffer in &buffers {
15649 let snapshot = buffer.read(cx).snapshot();
15650 multibuffer.set_excerpts_for_path(
15651 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15652 buffer.clone(),
15653 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15654 DEFAULT_MULTIBUFFER_CONTEXT,
15655 cx,
15656 );
15657 }
15658 multibuffer
15659 });
15660
15661 let editor = cx.add_window(|window, cx| {
15662 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
15663 });
15664 cx.run_until_parked();
15665
15666 let snapshot = editor
15667 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15668 .unwrap();
15669 let hunks = snapshot
15670 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15671 .map(|hunk| match hunk {
15672 DisplayDiffHunk::Unfolded {
15673 display_row_range, ..
15674 } => display_row_range,
15675 DisplayDiffHunk::Folded { .. } => unreachable!(),
15676 })
15677 .collect::<Vec<_>>();
15678 assert_eq!(
15679 hunks,
15680 [
15681 DisplayRow(2)..DisplayRow(4),
15682 DisplayRow(7)..DisplayRow(9),
15683 DisplayRow(12)..DisplayRow(14),
15684 ]
15685 );
15686}
15687
15688#[gpui::test]
15689async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15690 init_test(cx, |_| {});
15691
15692 let mut cx = EditorTestContext::new(cx).await;
15693 cx.set_head_text(indoc! { "
15694 one
15695 two
15696 three
15697 four
15698 five
15699 "
15700 });
15701 cx.set_index_text(indoc! { "
15702 one
15703 two
15704 three
15705 four
15706 five
15707 "
15708 });
15709 cx.set_state(indoc! {"
15710 one
15711 TWO
15712 ˇTHREE
15713 FOUR
15714 five
15715 "});
15716 cx.run_until_parked();
15717 cx.update_editor(|editor, window, cx| {
15718 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15719 });
15720 cx.run_until_parked();
15721 cx.assert_index_text(Some(indoc! {"
15722 one
15723 TWO
15724 THREE
15725 FOUR
15726 five
15727 "}));
15728 cx.set_state(indoc! { "
15729 one
15730 TWO
15731 ˇTHREE-HUNDRED
15732 FOUR
15733 five
15734 "});
15735 cx.run_until_parked();
15736 cx.update_editor(|editor, window, cx| {
15737 let snapshot = editor.snapshot(window, cx);
15738 let hunks = editor
15739 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15740 .collect::<Vec<_>>();
15741 assert_eq!(hunks.len(), 1);
15742 assert_eq!(
15743 hunks[0].status(),
15744 DiffHunkStatus {
15745 kind: DiffHunkStatusKind::Modified,
15746 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15747 }
15748 );
15749
15750 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15751 });
15752 cx.run_until_parked();
15753 cx.assert_index_text(Some(indoc! {"
15754 one
15755 TWO
15756 THREE-HUNDRED
15757 FOUR
15758 five
15759 "}));
15760}
15761
15762#[gpui::test]
15763fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15764 init_test(cx, |_| {});
15765
15766 let editor = cx.add_window(|window, cx| {
15767 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15768 build_editor(buffer, window, cx)
15769 });
15770
15771 let render_args = Arc::new(Mutex::new(None));
15772 let snapshot = editor
15773 .update(cx, |editor, window, cx| {
15774 let snapshot = editor.buffer().read(cx).snapshot(cx);
15775 let range =
15776 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15777
15778 struct RenderArgs {
15779 row: MultiBufferRow,
15780 folded: bool,
15781 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15782 }
15783
15784 let crease = Crease::inline(
15785 range,
15786 FoldPlaceholder::test(),
15787 {
15788 let toggle_callback = render_args.clone();
15789 move |row, folded, callback, _window, _cx| {
15790 *toggle_callback.lock() = Some(RenderArgs {
15791 row,
15792 folded,
15793 callback,
15794 });
15795 div()
15796 }
15797 },
15798 |_row, _folded, _window, _cx| div(),
15799 );
15800
15801 editor.insert_creases(Some(crease), cx);
15802 let snapshot = editor.snapshot(window, cx);
15803 let _div = snapshot.render_crease_toggle(
15804 MultiBufferRow(1),
15805 false,
15806 cx.entity().clone(),
15807 window,
15808 cx,
15809 );
15810 snapshot
15811 })
15812 .unwrap();
15813
15814 let render_args = render_args.lock().take().unwrap();
15815 assert_eq!(render_args.row, MultiBufferRow(1));
15816 assert!(!render_args.folded);
15817 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15818
15819 cx.update_window(*editor, |_, window, cx| {
15820 (render_args.callback)(true, window, cx)
15821 })
15822 .unwrap();
15823 let snapshot = editor
15824 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15825 .unwrap();
15826 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15827
15828 cx.update_window(*editor, |_, window, cx| {
15829 (render_args.callback)(false, window, cx)
15830 })
15831 .unwrap();
15832 let snapshot = editor
15833 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15834 .unwrap();
15835 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15836}
15837
15838#[gpui::test]
15839async fn test_input_text(cx: &mut TestAppContext) {
15840 init_test(cx, |_| {});
15841 let mut cx = EditorTestContext::new(cx).await;
15842
15843 cx.set_state(
15844 &r#"ˇone
15845 two
15846
15847 three
15848 fourˇ
15849 five
15850
15851 siˇx"#
15852 .unindent(),
15853 );
15854
15855 cx.dispatch_action(HandleInput(String::new()));
15856 cx.assert_editor_state(
15857 &r#"ˇone
15858 two
15859
15860 three
15861 fourˇ
15862 five
15863
15864 siˇx"#
15865 .unindent(),
15866 );
15867
15868 cx.dispatch_action(HandleInput("AAAA".to_string()));
15869 cx.assert_editor_state(
15870 &r#"AAAAˇone
15871 two
15872
15873 three
15874 fourAAAAˇ
15875 five
15876
15877 siAAAAˇx"#
15878 .unindent(),
15879 );
15880}
15881
15882#[gpui::test]
15883async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15884 init_test(cx, |_| {});
15885
15886 let mut cx = EditorTestContext::new(cx).await;
15887 cx.set_state(
15888 r#"let foo = 1;
15889let foo = 2;
15890let foo = 3;
15891let fooˇ = 4;
15892let foo = 5;
15893let foo = 6;
15894let foo = 7;
15895let foo = 8;
15896let foo = 9;
15897let foo = 10;
15898let foo = 11;
15899let foo = 12;
15900let foo = 13;
15901let foo = 14;
15902let foo = 15;"#,
15903 );
15904
15905 cx.update_editor(|e, window, cx| {
15906 assert_eq!(
15907 e.next_scroll_position,
15908 NextScrollCursorCenterTopBottom::Center,
15909 "Default next scroll direction is center",
15910 );
15911
15912 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15913 assert_eq!(
15914 e.next_scroll_position,
15915 NextScrollCursorCenterTopBottom::Top,
15916 "After center, next scroll direction should be top",
15917 );
15918
15919 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15920 assert_eq!(
15921 e.next_scroll_position,
15922 NextScrollCursorCenterTopBottom::Bottom,
15923 "After top, next scroll direction should be bottom",
15924 );
15925
15926 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15927 assert_eq!(
15928 e.next_scroll_position,
15929 NextScrollCursorCenterTopBottom::Center,
15930 "After bottom, scrolling should start over",
15931 );
15932
15933 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15934 assert_eq!(
15935 e.next_scroll_position,
15936 NextScrollCursorCenterTopBottom::Top,
15937 "Scrolling continues if retriggered fast enough"
15938 );
15939 });
15940
15941 cx.executor()
15942 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15943 cx.executor().run_until_parked();
15944 cx.update_editor(|e, _, _| {
15945 assert_eq!(
15946 e.next_scroll_position,
15947 NextScrollCursorCenterTopBottom::Center,
15948 "If scrolling is not triggered fast enough, it should reset"
15949 );
15950 });
15951}
15952
15953#[gpui::test]
15954async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15955 init_test(cx, |_| {});
15956 let mut cx = EditorLspTestContext::new_rust(
15957 lsp::ServerCapabilities {
15958 definition_provider: Some(lsp::OneOf::Left(true)),
15959 references_provider: Some(lsp::OneOf::Left(true)),
15960 ..lsp::ServerCapabilities::default()
15961 },
15962 cx,
15963 )
15964 .await;
15965
15966 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15967 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15968 move |params, _| async move {
15969 if empty_go_to_definition {
15970 Ok(None)
15971 } else {
15972 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15973 uri: params.text_document_position_params.text_document.uri,
15974 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15975 })))
15976 }
15977 },
15978 );
15979 let references =
15980 cx.lsp
15981 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15982 Ok(Some(vec![lsp::Location {
15983 uri: params.text_document_position.text_document.uri,
15984 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15985 }]))
15986 });
15987 (go_to_definition, references)
15988 };
15989
15990 cx.set_state(
15991 &r#"fn one() {
15992 let mut a = ˇtwo();
15993 }
15994
15995 fn two() {}"#
15996 .unindent(),
15997 );
15998 set_up_lsp_handlers(false, &mut cx);
15999 let navigated = cx
16000 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16001 .await
16002 .expect("Failed to navigate to definition");
16003 assert_eq!(
16004 navigated,
16005 Navigated::Yes,
16006 "Should have navigated to definition from the GetDefinition response"
16007 );
16008 cx.assert_editor_state(
16009 &r#"fn one() {
16010 let mut a = two();
16011 }
16012
16013 fn «twoˇ»() {}"#
16014 .unindent(),
16015 );
16016
16017 let editors = cx.update_workspace(|workspace, _, cx| {
16018 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16019 });
16020 cx.update_editor(|_, _, test_editor_cx| {
16021 assert_eq!(
16022 editors.len(),
16023 1,
16024 "Initially, only one, test, editor should be open in the workspace"
16025 );
16026 assert_eq!(
16027 test_editor_cx.entity(),
16028 editors.last().expect("Asserted len is 1").clone()
16029 );
16030 });
16031
16032 set_up_lsp_handlers(true, &mut cx);
16033 let navigated = cx
16034 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16035 .await
16036 .expect("Failed to navigate to lookup references");
16037 assert_eq!(
16038 navigated,
16039 Navigated::Yes,
16040 "Should have navigated to references as a fallback after empty GoToDefinition response"
16041 );
16042 // We should not change the selections in the existing file,
16043 // if opening another milti buffer with the references
16044 cx.assert_editor_state(
16045 &r#"fn one() {
16046 let mut a = two();
16047 }
16048
16049 fn «twoˇ»() {}"#
16050 .unindent(),
16051 );
16052 let editors = cx.update_workspace(|workspace, _, cx| {
16053 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16054 });
16055 cx.update_editor(|_, _, test_editor_cx| {
16056 assert_eq!(
16057 editors.len(),
16058 2,
16059 "After falling back to references search, we open a new editor with the results"
16060 );
16061 let references_fallback_text = editors
16062 .into_iter()
16063 .find(|new_editor| *new_editor != test_editor_cx.entity())
16064 .expect("Should have one non-test editor now")
16065 .read(test_editor_cx)
16066 .text(test_editor_cx);
16067 assert_eq!(
16068 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16069 "Should use the range from the references response and not the GoToDefinition one"
16070 );
16071 });
16072}
16073
16074#[gpui::test]
16075async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16076 init_test(cx, |_| {});
16077
16078 let language = Arc::new(Language::new(
16079 LanguageConfig::default(),
16080 Some(tree_sitter_rust::LANGUAGE.into()),
16081 ));
16082
16083 let text = r#"
16084 #[cfg(test)]
16085 mod tests() {
16086 #[test]
16087 fn runnable_1() {
16088 let a = 1;
16089 }
16090
16091 #[test]
16092 fn runnable_2() {
16093 let a = 1;
16094 let b = 2;
16095 }
16096 }
16097 "#
16098 .unindent();
16099
16100 let fs = FakeFs::new(cx.executor());
16101 fs.insert_file("/file.rs", Default::default()).await;
16102
16103 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16104 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16105 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16106 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16107 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16108
16109 let editor = cx.new_window_entity(|window, cx| {
16110 Editor::new(
16111 EditorMode::Full,
16112 multi_buffer,
16113 Some(project.clone()),
16114 window,
16115 cx,
16116 )
16117 });
16118
16119 editor.update_in(cx, |editor, window, cx| {
16120 let snapshot = editor.buffer().read(cx).snapshot(cx);
16121 editor.tasks.insert(
16122 (buffer.read(cx).remote_id(), 3),
16123 RunnableTasks {
16124 templates: vec![],
16125 offset: snapshot.anchor_before(43),
16126 column: 0,
16127 extra_variables: HashMap::default(),
16128 context_range: BufferOffset(43)..BufferOffset(85),
16129 },
16130 );
16131 editor.tasks.insert(
16132 (buffer.read(cx).remote_id(), 8),
16133 RunnableTasks {
16134 templates: vec![],
16135 offset: snapshot.anchor_before(86),
16136 column: 0,
16137 extra_variables: HashMap::default(),
16138 context_range: BufferOffset(86)..BufferOffset(191),
16139 },
16140 );
16141
16142 // Test finding task when cursor is inside function body
16143 editor.change_selections(None, window, cx, |s| {
16144 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16145 });
16146 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16147 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16148
16149 // Test finding task when cursor is on function name
16150 editor.change_selections(None, window, cx, |s| {
16151 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16152 });
16153 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16154 assert_eq!(row, 8, "Should find task when cursor is on function name");
16155 });
16156}
16157
16158#[gpui::test]
16159async fn test_folding_buffers(cx: &mut TestAppContext) {
16160 init_test(cx, |_| {});
16161
16162 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16163 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16164 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16165
16166 let fs = FakeFs::new(cx.executor());
16167 fs.insert_tree(
16168 path!("/a"),
16169 json!({
16170 "first.rs": sample_text_1,
16171 "second.rs": sample_text_2,
16172 "third.rs": sample_text_3,
16173 }),
16174 )
16175 .await;
16176 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16177 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16178 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16179 let worktree = project.update(cx, |project, cx| {
16180 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16181 assert_eq!(worktrees.len(), 1);
16182 worktrees.pop().unwrap()
16183 });
16184 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16185
16186 let buffer_1 = project
16187 .update(cx, |project, cx| {
16188 project.open_buffer((worktree_id, "first.rs"), cx)
16189 })
16190 .await
16191 .unwrap();
16192 let buffer_2 = project
16193 .update(cx, |project, cx| {
16194 project.open_buffer((worktree_id, "second.rs"), cx)
16195 })
16196 .await
16197 .unwrap();
16198 let buffer_3 = project
16199 .update(cx, |project, cx| {
16200 project.open_buffer((worktree_id, "third.rs"), cx)
16201 })
16202 .await
16203 .unwrap();
16204
16205 let multi_buffer = cx.new(|cx| {
16206 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16207 multi_buffer.push_excerpts(
16208 buffer_1.clone(),
16209 [
16210 ExcerptRange {
16211 context: Point::new(0, 0)..Point::new(3, 0),
16212 primary: None,
16213 },
16214 ExcerptRange {
16215 context: Point::new(5, 0)..Point::new(7, 0),
16216 primary: None,
16217 },
16218 ExcerptRange {
16219 context: Point::new(9, 0)..Point::new(10, 4),
16220 primary: None,
16221 },
16222 ],
16223 cx,
16224 );
16225 multi_buffer.push_excerpts(
16226 buffer_2.clone(),
16227 [
16228 ExcerptRange {
16229 context: Point::new(0, 0)..Point::new(3, 0),
16230 primary: None,
16231 },
16232 ExcerptRange {
16233 context: Point::new(5, 0)..Point::new(7, 0),
16234 primary: None,
16235 },
16236 ExcerptRange {
16237 context: Point::new(9, 0)..Point::new(10, 4),
16238 primary: None,
16239 },
16240 ],
16241 cx,
16242 );
16243 multi_buffer.push_excerpts(
16244 buffer_3.clone(),
16245 [
16246 ExcerptRange {
16247 context: Point::new(0, 0)..Point::new(3, 0),
16248 primary: None,
16249 },
16250 ExcerptRange {
16251 context: Point::new(5, 0)..Point::new(7, 0),
16252 primary: None,
16253 },
16254 ExcerptRange {
16255 context: Point::new(9, 0)..Point::new(10, 4),
16256 primary: None,
16257 },
16258 ],
16259 cx,
16260 );
16261 multi_buffer
16262 });
16263 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16264 Editor::new(
16265 EditorMode::Full,
16266 multi_buffer.clone(),
16267 Some(project.clone()),
16268 window,
16269 cx,
16270 )
16271 });
16272
16273 assert_eq!(
16274 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16275 "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16276 );
16277
16278 multi_buffer_editor.update(cx, |editor, cx| {
16279 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16280 });
16281 assert_eq!(
16282 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16283 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16284 "After folding the first buffer, its text should not be displayed"
16285 );
16286
16287 multi_buffer_editor.update(cx, |editor, cx| {
16288 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16289 });
16290 assert_eq!(
16291 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16292 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16293 "After folding the second buffer, its text should not be displayed"
16294 );
16295
16296 multi_buffer_editor.update(cx, |editor, cx| {
16297 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16298 });
16299 assert_eq!(
16300 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16301 "\n\n\n\n\n",
16302 "After folding the third buffer, its text should not be displayed"
16303 );
16304
16305 // Emulate selection inside the fold logic, that should work
16306 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16307 editor
16308 .snapshot(window, cx)
16309 .next_line_boundary(Point::new(0, 4));
16310 });
16311
16312 multi_buffer_editor.update(cx, |editor, cx| {
16313 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16314 });
16315 assert_eq!(
16316 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16317 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16318 "After unfolding the second buffer, its text should be displayed"
16319 );
16320
16321 // Typing inside of buffer 1 causes that buffer to be unfolded.
16322 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16323 assert_eq!(
16324 multi_buffer
16325 .read(cx)
16326 .snapshot(cx)
16327 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16328 .collect::<String>(),
16329 "bbbb"
16330 );
16331 editor.change_selections(None, window, cx, |selections| {
16332 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16333 });
16334 editor.handle_input("B", window, cx);
16335 });
16336
16337 assert_eq!(
16338 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16339 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16340 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16341 );
16342
16343 multi_buffer_editor.update(cx, |editor, cx| {
16344 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16345 });
16346 assert_eq!(
16347 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16348 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16349 "After unfolding the all buffers, all original text should be displayed"
16350 );
16351}
16352
16353#[gpui::test]
16354async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16355 init_test(cx, |_| {});
16356
16357 let sample_text_1 = "1111\n2222\n3333".to_string();
16358 let sample_text_2 = "4444\n5555\n6666".to_string();
16359 let sample_text_3 = "7777\n8888\n9999".to_string();
16360
16361 let fs = FakeFs::new(cx.executor());
16362 fs.insert_tree(
16363 path!("/a"),
16364 json!({
16365 "first.rs": sample_text_1,
16366 "second.rs": sample_text_2,
16367 "third.rs": sample_text_3,
16368 }),
16369 )
16370 .await;
16371 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16372 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16373 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16374 let worktree = project.update(cx, |project, cx| {
16375 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16376 assert_eq!(worktrees.len(), 1);
16377 worktrees.pop().unwrap()
16378 });
16379 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16380
16381 let buffer_1 = project
16382 .update(cx, |project, cx| {
16383 project.open_buffer((worktree_id, "first.rs"), cx)
16384 })
16385 .await
16386 .unwrap();
16387 let buffer_2 = project
16388 .update(cx, |project, cx| {
16389 project.open_buffer((worktree_id, "second.rs"), cx)
16390 })
16391 .await
16392 .unwrap();
16393 let buffer_3 = project
16394 .update(cx, |project, cx| {
16395 project.open_buffer((worktree_id, "third.rs"), cx)
16396 })
16397 .await
16398 .unwrap();
16399
16400 let multi_buffer = cx.new(|cx| {
16401 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16402 multi_buffer.push_excerpts(
16403 buffer_1.clone(),
16404 [ExcerptRange {
16405 context: Point::new(0, 0)..Point::new(3, 0),
16406 primary: None,
16407 }],
16408 cx,
16409 );
16410 multi_buffer.push_excerpts(
16411 buffer_2.clone(),
16412 [ExcerptRange {
16413 context: Point::new(0, 0)..Point::new(3, 0),
16414 primary: None,
16415 }],
16416 cx,
16417 );
16418 multi_buffer.push_excerpts(
16419 buffer_3.clone(),
16420 [ExcerptRange {
16421 context: Point::new(0, 0)..Point::new(3, 0),
16422 primary: None,
16423 }],
16424 cx,
16425 );
16426 multi_buffer
16427 });
16428
16429 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16430 Editor::new(
16431 EditorMode::Full,
16432 multi_buffer,
16433 Some(project.clone()),
16434 window,
16435 cx,
16436 )
16437 });
16438
16439 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16440 assert_eq!(
16441 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16442 full_text,
16443 );
16444
16445 multi_buffer_editor.update(cx, |editor, cx| {
16446 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16447 });
16448 assert_eq!(
16449 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16450 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16451 "After folding the first buffer, its text should not be displayed"
16452 );
16453
16454 multi_buffer_editor.update(cx, |editor, cx| {
16455 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16456 });
16457
16458 assert_eq!(
16459 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16460 "\n\n\n\n\n\n7777\n8888\n9999",
16461 "After folding the second buffer, its text should not be displayed"
16462 );
16463
16464 multi_buffer_editor.update(cx, |editor, cx| {
16465 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16466 });
16467 assert_eq!(
16468 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16469 "\n\n\n\n\n",
16470 "After folding the third buffer, its text should not be displayed"
16471 );
16472
16473 multi_buffer_editor.update(cx, |editor, cx| {
16474 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16475 });
16476 assert_eq!(
16477 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16478 "\n\n\n\n4444\n5555\n6666\n\n",
16479 "After unfolding the second buffer, its text should be displayed"
16480 );
16481
16482 multi_buffer_editor.update(cx, |editor, cx| {
16483 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16484 });
16485 assert_eq!(
16486 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16487 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16488 "After unfolding the first buffer, its text should be displayed"
16489 );
16490
16491 multi_buffer_editor.update(cx, |editor, cx| {
16492 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16493 });
16494 assert_eq!(
16495 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16496 full_text,
16497 "After unfolding all buffers, all original text should be displayed"
16498 );
16499}
16500
16501#[gpui::test]
16502async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16503 init_test(cx, |_| {});
16504
16505 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16506
16507 let fs = FakeFs::new(cx.executor());
16508 fs.insert_tree(
16509 path!("/a"),
16510 json!({
16511 "main.rs": sample_text,
16512 }),
16513 )
16514 .await;
16515 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16516 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16517 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16518 let worktree = project.update(cx, |project, cx| {
16519 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16520 assert_eq!(worktrees.len(), 1);
16521 worktrees.pop().unwrap()
16522 });
16523 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16524
16525 let buffer_1 = project
16526 .update(cx, |project, cx| {
16527 project.open_buffer((worktree_id, "main.rs"), cx)
16528 })
16529 .await
16530 .unwrap();
16531
16532 let multi_buffer = cx.new(|cx| {
16533 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16534 multi_buffer.push_excerpts(
16535 buffer_1.clone(),
16536 [ExcerptRange {
16537 context: Point::new(0, 0)
16538 ..Point::new(
16539 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16540 0,
16541 ),
16542 primary: None,
16543 }],
16544 cx,
16545 );
16546 multi_buffer
16547 });
16548 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16549 Editor::new(
16550 EditorMode::Full,
16551 multi_buffer,
16552 Some(project.clone()),
16553 window,
16554 cx,
16555 )
16556 });
16557
16558 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16559 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16560 enum TestHighlight {}
16561 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16562 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16563 editor.highlight_text::<TestHighlight>(
16564 vec![highlight_range.clone()],
16565 HighlightStyle::color(Hsla::green()),
16566 cx,
16567 );
16568 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16569 });
16570
16571 let full_text = format!("\n\n{sample_text}");
16572 assert_eq!(
16573 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16574 full_text,
16575 );
16576}
16577
16578#[gpui::test]
16579async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16580 init_test(cx, |_| {});
16581 cx.update(|cx| {
16582 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16583 "keymaps/default-linux.json",
16584 cx,
16585 )
16586 .unwrap();
16587 cx.bind_keys(default_key_bindings);
16588 });
16589
16590 let (editor, cx) = cx.add_window_view(|window, cx| {
16591 let multi_buffer = MultiBuffer::build_multi(
16592 [
16593 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16594 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16595 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16596 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16597 ],
16598 cx,
16599 );
16600 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16601
16602 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16603 // fold all but the second buffer, so that we test navigating between two
16604 // adjacent folded buffers, as well as folded buffers at the start and
16605 // end the multibuffer
16606 editor.fold_buffer(buffer_ids[0], cx);
16607 editor.fold_buffer(buffer_ids[2], cx);
16608 editor.fold_buffer(buffer_ids[3], cx);
16609
16610 editor
16611 });
16612 cx.simulate_resize(size(px(1000.), px(1000.)));
16613
16614 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16615 cx.assert_excerpts_with_selections(indoc! {"
16616 [EXCERPT]
16617 ˇ[FOLDED]
16618 [EXCERPT]
16619 a1
16620 b1
16621 [EXCERPT]
16622 [FOLDED]
16623 [EXCERPT]
16624 [FOLDED]
16625 "
16626 });
16627 cx.simulate_keystroke("down");
16628 cx.assert_excerpts_with_selections(indoc! {"
16629 [EXCERPT]
16630 [FOLDED]
16631 [EXCERPT]
16632 ˇa1
16633 b1
16634 [EXCERPT]
16635 [FOLDED]
16636 [EXCERPT]
16637 [FOLDED]
16638 "
16639 });
16640 cx.simulate_keystroke("down");
16641 cx.assert_excerpts_with_selections(indoc! {"
16642 [EXCERPT]
16643 [FOLDED]
16644 [EXCERPT]
16645 a1
16646 ˇb1
16647 [EXCERPT]
16648 [FOLDED]
16649 [EXCERPT]
16650 [FOLDED]
16651 "
16652 });
16653 cx.simulate_keystroke("down");
16654 cx.assert_excerpts_with_selections(indoc! {"
16655 [EXCERPT]
16656 [FOLDED]
16657 [EXCERPT]
16658 a1
16659 b1
16660 ˇ[EXCERPT]
16661 [FOLDED]
16662 [EXCERPT]
16663 [FOLDED]
16664 "
16665 });
16666 cx.simulate_keystroke("down");
16667 cx.assert_excerpts_with_selections(indoc! {"
16668 [EXCERPT]
16669 [FOLDED]
16670 [EXCERPT]
16671 a1
16672 b1
16673 [EXCERPT]
16674 ˇ[FOLDED]
16675 [EXCERPT]
16676 [FOLDED]
16677 "
16678 });
16679 for _ in 0..5 {
16680 cx.simulate_keystroke("down");
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 }
16694
16695 cx.simulate_keystroke("up");
16696 cx.assert_excerpts_with_selections(indoc! {"
16697 [EXCERPT]
16698 [FOLDED]
16699 [EXCERPT]
16700 a1
16701 b1
16702 [EXCERPT]
16703 ˇ[FOLDED]
16704 [EXCERPT]
16705 [FOLDED]
16706 "
16707 });
16708 cx.simulate_keystroke("up");
16709 cx.assert_excerpts_with_selections(indoc! {"
16710 [EXCERPT]
16711 [FOLDED]
16712 [EXCERPT]
16713 a1
16714 b1
16715 ˇ[EXCERPT]
16716 [FOLDED]
16717 [EXCERPT]
16718 [FOLDED]
16719 "
16720 });
16721 cx.simulate_keystroke("up");
16722 cx.assert_excerpts_with_selections(indoc! {"
16723 [EXCERPT]
16724 [FOLDED]
16725 [EXCERPT]
16726 a1
16727 ˇb1
16728 [EXCERPT]
16729 [FOLDED]
16730 [EXCERPT]
16731 [FOLDED]
16732 "
16733 });
16734 cx.simulate_keystroke("up");
16735 cx.assert_excerpts_with_selections(indoc! {"
16736 [EXCERPT]
16737 [FOLDED]
16738 [EXCERPT]
16739 ˇa1
16740 b1
16741 [EXCERPT]
16742 [FOLDED]
16743 [EXCERPT]
16744 [FOLDED]
16745 "
16746 });
16747 for _ in 0..5 {
16748 cx.simulate_keystroke("up");
16749 cx.assert_excerpts_with_selections(indoc! {"
16750 [EXCERPT]
16751 ˇ[FOLDED]
16752 [EXCERPT]
16753 a1
16754 b1
16755 [EXCERPT]
16756 [FOLDED]
16757 [EXCERPT]
16758 [FOLDED]
16759 "
16760 });
16761 }
16762}
16763
16764#[gpui::test]
16765async fn test_inline_completion_text(cx: &mut TestAppContext) {
16766 init_test(cx, |_| {});
16767
16768 // Simple insertion
16769 assert_highlighted_edits(
16770 "Hello, world!",
16771 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16772 true,
16773 cx,
16774 |highlighted_edits, cx| {
16775 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16776 assert_eq!(highlighted_edits.highlights.len(), 1);
16777 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16778 assert_eq!(
16779 highlighted_edits.highlights[0].1.background_color,
16780 Some(cx.theme().status().created_background)
16781 );
16782 },
16783 )
16784 .await;
16785
16786 // Replacement
16787 assert_highlighted_edits(
16788 "This is a test.",
16789 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16790 false,
16791 cx,
16792 |highlighted_edits, cx| {
16793 assert_eq!(highlighted_edits.text, "That is a test.");
16794 assert_eq!(highlighted_edits.highlights.len(), 1);
16795 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16796 assert_eq!(
16797 highlighted_edits.highlights[0].1.background_color,
16798 Some(cx.theme().status().created_background)
16799 );
16800 },
16801 )
16802 .await;
16803
16804 // Multiple edits
16805 assert_highlighted_edits(
16806 "Hello, world!",
16807 vec![
16808 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16809 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16810 ],
16811 false,
16812 cx,
16813 |highlighted_edits, cx| {
16814 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16815 assert_eq!(highlighted_edits.highlights.len(), 2);
16816 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16817 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16818 assert_eq!(
16819 highlighted_edits.highlights[0].1.background_color,
16820 Some(cx.theme().status().created_background)
16821 );
16822 assert_eq!(
16823 highlighted_edits.highlights[1].1.background_color,
16824 Some(cx.theme().status().created_background)
16825 );
16826 },
16827 )
16828 .await;
16829
16830 // Multiple lines with edits
16831 assert_highlighted_edits(
16832 "First line\nSecond line\nThird line\nFourth line",
16833 vec![
16834 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16835 (
16836 Point::new(2, 0)..Point::new(2, 10),
16837 "New third line".to_string(),
16838 ),
16839 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16840 ],
16841 false,
16842 cx,
16843 |highlighted_edits, cx| {
16844 assert_eq!(
16845 highlighted_edits.text,
16846 "Second modified\nNew third line\nFourth updated line"
16847 );
16848 assert_eq!(highlighted_edits.highlights.len(), 3);
16849 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16850 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16851 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16852 for highlight in &highlighted_edits.highlights {
16853 assert_eq!(
16854 highlight.1.background_color,
16855 Some(cx.theme().status().created_background)
16856 );
16857 }
16858 },
16859 )
16860 .await;
16861}
16862
16863#[gpui::test]
16864async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16865 init_test(cx, |_| {});
16866
16867 // Deletion
16868 assert_highlighted_edits(
16869 "Hello, world!",
16870 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16871 true,
16872 cx,
16873 |highlighted_edits, cx| {
16874 assert_eq!(highlighted_edits.text, "Hello, world!");
16875 assert_eq!(highlighted_edits.highlights.len(), 1);
16876 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16877 assert_eq!(
16878 highlighted_edits.highlights[0].1.background_color,
16879 Some(cx.theme().status().deleted_background)
16880 );
16881 },
16882 )
16883 .await;
16884
16885 // Insertion
16886 assert_highlighted_edits(
16887 "Hello, world!",
16888 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16889 true,
16890 cx,
16891 |highlighted_edits, cx| {
16892 assert_eq!(highlighted_edits.highlights.len(), 1);
16893 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16894 assert_eq!(
16895 highlighted_edits.highlights[0].1.background_color,
16896 Some(cx.theme().status().created_background)
16897 );
16898 },
16899 )
16900 .await;
16901}
16902
16903async fn assert_highlighted_edits(
16904 text: &str,
16905 edits: Vec<(Range<Point>, String)>,
16906 include_deletions: bool,
16907 cx: &mut TestAppContext,
16908 assertion_fn: impl Fn(HighlightedText, &App),
16909) {
16910 let window = cx.add_window(|window, cx| {
16911 let buffer = MultiBuffer::build_simple(text, cx);
16912 Editor::new(EditorMode::Full, buffer, None, window, cx)
16913 });
16914 let cx = &mut VisualTestContext::from_window(*window, cx);
16915
16916 let (buffer, snapshot) = window
16917 .update(cx, |editor, _window, cx| {
16918 (
16919 editor.buffer().clone(),
16920 editor.buffer().read(cx).snapshot(cx),
16921 )
16922 })
16923 .unwrap();
16924
16925 let edits = edits
16926 .into_iter()
16927 .map(|(range, edit)| {
16928 (
16929 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16930 edit,
16931 )
16932 })
16933 .collect::<Vec<_>>();
16934
16935 let text_anchor_edits = edits
16936 .clone()
16937 .into_iter()
16938 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16939 .collect::<Vec<_>>();
16940
16941 let edit_preview = window
16942 .update(cx, |_, _window, cx| {
16943 buffer
16944 .read(cx)
16945 .as_singleton()
16946 .unwrap()
16947 .read(cx)
16948 .preview_edits(text_anchor_edits.into(), cx)
16949 })
16950 .unwrap()
16951 .await;
16952
16953 cx.update(|_window, cx| {
16954 let highlighted_edits = inline_completion_edit_text(
16955 &snapshot.as_singleton().unwrap().2,
16956 &edits,
16957 &edit_preview,
16958 include_deletions,
16959 cx,
16960 );
16961 assertion_fn(highlighted_edits, cx)
16962 });
16963}
16964
16965#[gpui::test]
16966async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16967 init_test(cx, |_| {});
16968 let capabilities = lsp::ServerCapabilities {
16969 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16970 prepare_provider: Some(true),
16971 work_done_progress_options: Default::default(),
16972 })),
16973 ..Default::default()
16974 };
16975 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16976
16977 cx.set_state(indoc! {"
16978 struct Fˇoo {}
16979 "});
16980
16981 cx.update_editor(|editor, _, cx| {
16982 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16983 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16984 editor.highlight_background::<DocumentHighlightRead>(
16985 &[highlight_range],
16986 |c| c.editor_document_highlight_read_background,
16987 cx,
16988 );
16989 });
16990
16991 let mut prepare_rename_handler =
16992 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16993 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16994 start: lsp::Position {
16995 line: 0,
16996 character: 7,
16997 },
16998 end: lsp::Position {
16999 line: 0,
17000 character: 10,
17001 },
17002 })))
17003 });
17004 let prepare_rename_task = cx
17005 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17006 .expect("Prepare rename was not started");
17007 prepare_rename_handler.next().await.unwrap();
17008 prepare_rename_task.await.expect("Prepare rename failed");
17009
17010 let mut rename_handler =
17011 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17012 let edit = lsp::TextEdit {
17013 range: lsp::Range {
17014 start: lsp::Position {
17015 line: 0,
17016 character: 7,
17017 },
17018 end: lsp::Position {
17019 line: 0,
17020 character: 10,
17021 },
17022 },
17023 new_text: "FooRenamed".to_string(),
17024 };
17025 Ok(Some(lsp::WorkspaceEdit::new(
17026 // Specify the same edit twice
17027 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17028 )))
17029 });
17030 let rename_task = cx
17031 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17032 .expect("Confirm rename was not started");
17033 rename_handler.next().await.unwrap();
17034 rename_task.await.expect("Confirm rename failed");
17035 cx.run_until_parked();
17036
17037 // Despite two edits, only one is actually applied as those are identical
17038 cx.assert_editor_state(indoc! {"
17039 struct FooRenamedˇ {}
17040 "});
17041}
17042
17043#[gpui::test]
17044async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17045 init_test(cx, |_| {});
17046 // These capabilities indicate that the server does not support prepare rename.
17047 let capabilities = lsp::ServerCapabilities {
17048 rename_provider: Some(lsp::OneOf::Left(true)),
17049 ..Default::default()
17050 };
17051 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17052
17053 cx.set_state(indoc! {"
17054 struct Fˇoo {}
17055 "});
17056
17057 cx.update_editor(|editor, _window, cx| {
17058 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17059 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17060 editor.highlight_background::<DocumentHighlightRead>(
17061 &[highlight_range],
17062 |c| c.editor_document_highlight_read_background,
17063 cx,
17064 );
17065 });
17066
17067 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17068 .expect("Prepare rename was not started")
17069 .await
17070 .expect("Prepare rename failed");
17071
17072 let mut rename_handler =
17073 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17074 let edit = lsp::TextEdit {
17075 range: lsp::Range {
17076 start: lsp::Position {
17077 line: 0,
17078 character: 7,
17079 },
17080 end: lsp::Position {
17081 line: 0,
17082 character: 10,
17083 },
17084 },
17085 new_text: "FooRenamed".to_string(),
17086 };
17087 Ok(Some(lsp::WorkspaceEdit::new(
17088 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17089 )))
17090 });
17091 let rename_task = cx
17092 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17093 .expect("Confirm rename was not started");
17094 rename_handler.next().await.unwrap();
17095 rename_task.await.expect("Confirm rename failed");
17096 cx.run_until_parked();
17097
17098 // Correct range is renamed, as `surrounding_word` is used to find it.
17099 cx.assert_editor_state(indoc! {"
17100 struct FooRenamedˇ {}
17101 "});
17102}
17103
17104#[gpui::test]
17105async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17106 init_test(cx, |_| {});
17107 let mut cx = EditorTestContext::new(cx).await;
17108
17109 let language = Arc::new(
17110 Language::new(
17111 LanguageConfig::default(),
17112 Some(tree_sitter_html::LANGUAGE.into()),
17113 )
17114 .with_brackets_query(
17115 r#"
17116 ("<" @open "/>" @close)
17117 ("</" @open ">" @close)
17118 ("<" @open ">" @close)
17119 ("\"" @open "\"" @close)
17120 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17121 "#,
17122 )
17123 .unwrap(),
17124 );
17125 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17126
17127 cx.set_state(indoc! {"
17128 <span>ˇ</span>
17129 "});
17130 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17131 cx.assert_editor_state(indoc! {"
17132 <span>
17133 ˇ
17134 </span>
17135 "});
17136
17137 cx.set_state(indoc! {"
17138 <span><span></span>ˇ</span>
17139 "});
17140 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17141 cx.assert_editor_state(indoc! {"
17142 <span><span></span>
17143 ˇ</span>
17144 "});
17145
17146 cx.set_state(indoc! {"
17147 <span>ˇ
17148 </span>
17149 "});
17150 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17151 cx.assert_editor_state(indoc! {"
17152 <span>
17153 ˇ
17154 </span>
17155 "});
17156}
17157
17158mod autoclose_tags {
17159 use super::*;
17160 use language::language_settings::JsxTagAutoCloseSettings;
17161 use languages::language;
17162
17163 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17164 init_test(cx, |settings| {
17165 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17166 });
17167
17168 let mut cx = EditorTestContext::new(cx).await;
17169 cx.update_buffer(|buffer, cx| {
17170 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17171
17172 buffer.set_language(Some(language), cx)
17173 });
17174
17175 cx
17176 }
17177
17178 macro_rules! check {
17179 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17180 #[gpui::test]
17181 async fn $name(cx: &mut TestAppContext) {
17182 let mut cx = test_setup(cx).await;
17183 cx.set_state($initial);
17184 cx.run_until_parked();
17185
17186 cx.update_editor(|editor, window, cx| {
17187 editor.handle_input($input, window, cx);
17188 });
17189 cx.run_until_parked();
17190 cx.assert_editor_state($expected);
17191 }
17192 };
17193 }
17194
17195 check!(
17196 test_basic,
17197 "<divˇ" + ">" => "<div>ˇ</div>"
17198 );
17199
17200 check!(
17201 test_basic_nested,
17202 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17203 );
17204
17205 check!(
17206 test_basic_ignore_already_closed,
17207 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17208 );
17209
17210 check!(
17211 test_doesnt_autoclose_closing_tag,
17212 "</divˇ" + ">" => "</div>ˇ"
17213 );
17214
17215 check!(
17216 test_jsx_attr,
17217 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17218 );
17219
17220 check!(
17221 test_ignores_closing_tags_in_expr_block,
17222 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17223 );
17224
17225 check!(
17226 test_doesnt_autoclose_on_gt_in_expr,
17227 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17228 );
17229
17230 check!(
17231 test_ignores_closing_tags_with_different_tag_names,
17232 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17233 );
17234
17235 check!(
17236 test_autocloses_in_jsx_expression,
17237 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17238 );
17239
17240 check!(
17241 test_doesnt_autoclose_already_closed_in_jsx_expression,
17242 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17243 );
17244
17245 check!(
17246 test_autocloses_fragment,
17247 "<ˇ" + ">" => "<>ˇ</>"
17248 );
17249
17250 check!(
17251 test_does_not_include_type_argument_in_autoclose_tag_name,
17252 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17253 );
17254
17255 check!(
17256 test_does_not_autoclose_doctype,
17257 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17258 );
17259
17260 check!(
17261 test_does_not_autoclose_comment,
17262 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17263 );
17264
17265 check!(
17266 test_multi_cursor_autoclose_same_tag,
17267 r#"
17268 <divˇ
17269 <divˇ
17270 "#
17271 + ">" =>
17272 r#"
17273 <div>ˇ</div>
17274 <div>ˇ</div>
17275 "#
17276 );
17277
17278 check!(
17279 test_multi_cursor_autoclose_different_tags,
17280 r#"
17281 <divˇ
17282 <spanˇ
17283 "#
17284 + ">" =>
17285 r#"
17286 <div>ˇ</div>
17287 <span>ˇ</span>
17288 "#
17289 );
17290
17291 check!(
17292 test_multi_cursor_autoclose_some_dont_autoclose_others,
17293 r#"
17294 <divˇ
17295 <div /ˇ
17296 <spanˇ</span>
17297 <!DOCTYPE htmlˇ
17298 </headˇ
17299 <Component<T>ˇ
17300 ˇ
17301 "#
17302 + ">" =>
17303 r#"
17304 <div>ˇ</div>
17305 <div />ˇ
17306 <span>ˇ</span>
17307 <!DOCTYPE html>ˇ
17308 </head>ˇ
17309 <Component<T>>ˇ</Component>
17310 >ˇ
17311 "#
17312 );
17313
17314 check!(
17315 test_doesnt_mess_up_trailing_text,
17316 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17317 );
17318
17319 #[gpui::test]
17320 async fn test_multibuffer(cx: &mut TestAppContext) {
17321 init_test(cx, |settings| {
17322 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17323 });
17324
17325 let buffer_a = cx.new(|cx| {
17326 let mut buf = language::Buffer::local("<div", cx);
17327 buf.set_language(
17328 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17329 cx,
17330 );
17331 buf
17332 });
17333 let buffer_b = cx.new(|cx| {
17334 let mut buf = language::Buffer::local("<pre", cx);
17335 buf.set_language(
17336 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17337 cx,
17338 );
17339 buf
17340 });
17341 let buffer_c = cx.new(|cx| {
17342 let buf = language::Buffer::local("<span", cx);
17343 buf
17344 });
17345 let buffer = cx.new(|cx| {
17346 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17347 buf.push_excerpts(
17348 buffer_a,
17349 [ExcerptRange {
17350 context: text::Anchor::MIN..text::Anchor::MAX,
17351 primary: None,
17352 }],
17353 cx,
17354 );
17355 buf.push_excerpts(
17356 buffer_b,
17357 [ExcerptRange {
17358 context: text::Anchor::MIN..text::Anchor::MAX,
17359 primary: None,
17360 }],
17361 cx,
17362 );
17363 buf.push_excerpts(
17364 buffer_c,
17365 [ExcerptRange {
17366 context: text::Anchor::MIN..text::Anchor::MAX,
17367 primary: None,
17368 }],
17369 cx,
17370 );
17371 buf
17372 });
17373 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17374
17375 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17376
17377 cx.update_editor(|editor, window, cx| {
17378 editor.change_selections(None, window, cx, |selections| {
17379 selections.select(vec![
17380 Selection::from_offset(4),
17381 Selection::from_offset(9),
17382 Selection::from_offset(15),
17383 ])
17384 })
17385 });
17386 cx.run_until_parked();
17387
17388 cx.update_editor(|editor, window, cx| {
17389 editor.handle_input(">", window, cx);
17390 });
17391 cx.run_until_parked();
17392
17393 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17394 }
17395}
17396
17397fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17398 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17399 point..point
17400}
17401
17402fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17403 let (text, ranges) = marked_text_ranges(marked_text, true);
17404 assert_eq!(editor.text(cx), text);
17405 assert_eq!(
17406 editor.selections.ranges(cx),
17407 ranges,
17408 "Assert selections are {}",
17409 marked_text
17410 );
17411}
17412
17413pub fn handle_signature_help_request(
17414 cx: &mut EditorLspTestContext,
17415 mocked_response: lsp::SignatureHelp,
17416) -> impl Future<Output = ()> {
17417 let mut request =
17418 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17419 let mocked_response = mocked_response.clone();
17420 async move { Ok(Some(mocked_response)) }
17421 });
17422
17423 async move {
17424 request.next().await;
17425 }
17426}
17427
17428/// Handle completion request passing a marked string specifying where the completion
17429/// should be triggered from using '|' character, what range should be replaced, and what completions
17430/// should be returned using '<' and '>' to delimit the range
17431pub fn handle_completion_request(
17432 cx: &mut EditorLspTestContext,
17433 marked_string: &str,
17434 completions: Vec<&'static str>,
17435 counter: Arc<AtomicUsize>,
17436) -> impl Future<Output = ()> {
17437 let complete_from_marker: TextRangeMarker = '|'.into();
17438 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17439 let (_, mut marked_ranges) = marked_text_ranges_by(
17440 marked_string,
17441 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17442 );
17443
17444 let complete_from_position =
17445 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17446 let replace_range =
17447 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17448
17449 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17450 let completions = completions.clone();
17451 counter.fetch_add(1, atomic::Ordering::Release);
17452 async move {
17453 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17454 assert_eq!(
17455 params.text_document_position.position,
17456 complete_from_position
17457 );
17458 Ok(Some(lsp::CompletionResponse::Array(
17459 completions
17460 .iter()
17461 .map(|completion_text| lsp::CompletionItem {
17462 label: completion_text.to_string(),
17463 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17464 range: replace_range,
17465 new_text: completion_text.to_string(),
17466 })),
17467 ..Default::default()
17468 })
17469 .collect(),
17470 )))
17471 }
17472 });
17473
17474 async move {
17475 request.next().await;
17476 }
17477}
17478
17479fn handle_resolve_completion_request(
17480 cx: &mut EditorLspTestContext,
17481 edits: Option<Vec<(&'static str, &'static str)>>,
17482) -> impl Future<Output = ()> {
17483 let edits = edits.map(|edits| {
17484 edits
17485 .iter()
17486 .map(|(marked_string, new_text)| {
17487 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17488 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17489 lsp::TextEdit::new(replace_range, new_text.to_string())
17490 })
17491 .collect::<Vec<_>>()
17492 });
17493
17494 let mut request =
17495 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17496 let edits = edits.clone();
17497 async move {
17498 Ok(lsp::CompletionItem {
17499 additional_text_edits: edits,
17500 ..Default::default()
17501 })
17502 }
17503 });
17504
17505 async move {
17506 request.next().await;
17507 }
17508}
17509
17510pub(crate) fn update_test_language_settings(
17511 cx: &mut TestAppContext,
17512 f: impl Fn(&mut AllLanguageSettingsContent),
17513) {
17514 cx.update(|cx| {
17515 SettingsStore::update_global(cx, |store, cx| {
17516 store.update_user_settings::<AllLanguageSettings>(cx, f);
17517 });
17518 });
17519}
17520
17521pub(crate) fn update_test_project_settings(
17522 cx: &mut TestAppContext,
17523 f: impl Fn(&mut ProjectSettings),
17524) {
17525 cx.update(|cx| {
17526 SettingsStore::update_global(cx, |store, cx| {
17527 store.update_user_settings::<ProjectSettings>(cx, f);
17528 });
17529 });
17530}
17531
17532pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17533 cx.update(|cx| {
17534 assets::Assets.load_test_fonts(cx);
17535 let store = SettingsStore::test(cx);
17536 cx.set_global(store);
17537 theme::init(theme::LoadThemes::JustBase, cx);
17538 release_channel::init(SemanticVersion::default(), cx);
17539 client::init_settings(cx);
17540 language::init(cx);
17541 Project::init_settings(cx);
17542 workspace::init_settings(cx);
17543 crate::init(cx);
17544 });
17545
17546 update_test_language_settings(cx, f);
17547}
17548
17549#[track_caller]
17550fn assert_hunk_revert(
17551 not_reverted_text_with_selections: &str,
17552 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17553 expected_reverted_text_with_selections: &str,
17554 base_text: &str,
17555 cx: &mut EditorLspTestContext,
17556) {
17557 cx.set_state(not_reverted_text_with_selections);
17558 cx.set_head_text(base_text);
17559 cx.executor().run_until_parked();
17560
17561 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17562 let snapshot = editor.snapshot(window, cx);
17563 let reverted_hunk_statuses = snapshot
17564 .buffer_snapshot
17565 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17566 .map(|hunk| hunk.status().kind)
17567 .collect::<Vec<_>>();
17568
17569 editor.git_restore(&Default::default(), window, cx);
17570 reverted_hunk_statuses
17571 });
17572 cx.executor().run_until_parked();
17573 cx.assert_editor_state(expected_reverted_text_with_selections);
17574 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17575}