1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
20 LanguageSettingsContent, PrettierSettings,
21 },
22 BracketPairConfig,
23 Capability::ReadWrite,
24 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
25 Override, Point,
26};
27use language_settings::{Formatter, FormatterList, IndentGuideSettings};
28use multi_buffer::{IndentGuide, PathKey};
29use parking_lot::Mutex;
30use pretty_assertions::{assert_eq, assert_ne};
31use project::project_settings::{LspSettings, ProjectSettings};
32use project::FakeFs;
33use serde_json::{self, json};
34use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
35use std::{
36 iter,
37 sync::atomic::{self, AtomicUsize},
38};
39use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
40use text::ToPoint as _;
41use unindent::Unindent;
42use util::{
43 assert_set_eq, path,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45 uri,
46};
47use workspace::{
48 item::{FollowEvent, FollowableItem, Item, ItemHandle},
49 NavigationEntry, ViewId,
50};
51
52#[gpui::test]
53fn test_edit_events(cx: &mut TestAppContext) {
54 init_test(cx, |_| {});
55
56 let buffer = cx.new(|cx| {
57 let mut buffer = language::Buffer::local("123456", cx);
58 buffer.set_group_interval(Duration::from_secs(1));
59 buffer
60 });
61
62 let events = Rc::new(RefCell::new(Vec::new()));
63 let editor1 = cx.add_window({
64 let events = events.clone();
65 |window, cx| {
66 let entity = cx.entity().clone();
67 cx.subscribe_in(
68 &entity,
69 window,
70 move |_, _, event: &EditorEvent, _, _| match event {
71 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
72 EditorEvent::BufferEdited => {
73 events.borrow_mut().push(("editor1", "buffer edited"))
74 }
75 _ => {}
76 },
77 )
78 .detach();
79 Editor::for_buffer(buffer.clone(), None, window, cx)
80 }
81 });
82
83 let editor2 = cx.add_window({
84 let events = events.clone();
85 |window, cx| {
86 cx.subscribe_in(
87 &cx.entity().clone(),
88 window,
89 move |_, _, event: &EditorEvent, _, _| match event {
90 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
91 EditorEvent::BufferEdited => {
92 events.borrow_mut().push(("editor2", "buffer edited"))
93 }
94 _ => {}
95 },
96 )
97 .detach();
98 Editor::for_buffer(buffer.clone(), None, window, cx)
99 }
100 });
101
102 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
103
104 // Mutating editor 1 will emit an `Edited` event only for that editor.
105 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor1", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Mutating editor 2 will emit an `Edited` event only for that editor.
116 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor2", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Undoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Redoing on editor 1 will emit an `Edited` event only for that editor.
138 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor1", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Undoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // Redoing on editor 2 will emit an `Edited` event only for that editor.
160 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
161 assert_eq!(
162 mem::take(&mut *events.borrow_mut()),
163 [
164 ("editor2", "edited"),
165 ("editor1", "buffer edited"),
166 ("editor2", "buffer edited"),
167 ]
168 );
169
170 // No event is emitted when the mutation is a no-op.
171 _ = editor2.update(cx, |editor, window, cx| {
172 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
173
174 editor.backspace(&Backspace, window, cx);
175 });
176 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
177}
178
179#[gpui::test]
180fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
181 init_test(cx, |_| {});
182
183 let mut now = Instant::now();
184 let group_interval = Duration::from_millis(1);
185 let buffer = cx.new(|cx| {
186 let mut buf = language::Buffer::local("123456", cx);
187 buf.set_group_interval(group_interval);
188 buf
189 });
190 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
191 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
192
193 _ = editor.update(cx, |editor, window, cx| {
194 editor.start_transaction_at(now, window, cx);
195 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
196
197 editor.insert("cd", window, cx);
198 editor.end_transaction_at(now, cx);
199 assert_eq!(editor.text(cx), "12cd56");
200 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
201
202 editor.start_transaction_at(now, window, cx);
203 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
204 editor.insert("e", window, cx);
205 editor.end_transaction_at(now, cx);
206 assert_eq!(editor.text(cx), "12cde6");
207 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
208
209 now += group_interval + Duration::from_millis(1);
210 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
211
212 // Simulate an edit in another editor
213 buffer.update(cx, |buffer, cx| {
214 buffer.start_transaction_at(now, cx);
215 buffer.edit([(0..1, "a")], None, cx);
216 buffer.edit([(1..1, "b")], None, cx);
217 buffer.end_transaction_at(now, cx);
218 });
219
220 assert_eq!(editor.text(cx), "ab2cde6");
221 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
222
223 // Last transaction happened past the group interval in a different editor.
224 // Undo it individually and don't restore selections.
225 editor.undo(&Undo, window, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
228
229 // First two transactions happened within the group interval in this editor.
230 // Undo them together and restore selections.
231 editor.undo(&Undo, window, cx);
232 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
233 assert_eq!(editor.text(cx), "123456");
234 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
235
236 // Redo the first two transactions together.
237 editor.redo(&Redo, window, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
240
241 // Redo the last transaction on its own.
242 editor.redo(&Redo, window, cx);
243 assert_eq!(editor.text(cx), "ab2cde6");
244 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
245
246 // Test empty transactions.
247 editor.start_transaction_at(now, window, cx);
248 editor.end_transaction_at(now, cx);
249 editor.undo(&Undo, window, cx);
250 assert_eq!(editor.text(cx), "12cde6");
251 });
252}
253
254#[gpui::test]
255fn test_ime_composition(cx: &mut TestAppContext) {
256 init_test(cx, |_| {});
257
258 let buffer = cx.new(|cx| {
259 let mut buffer = language::Buffer::local("abcde", cx);
260 // Ensure automatic grouping doesn't occur.
261 buffer.set_group_interval(Duration::ZERO);
262 buffer
263 });
264
265 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
266 cx.add_window(|window, cx| {
267 let mut editor = build_editor(buffer.clone(), window, cx);
268
269 // Start a new IME composition.
270 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
272 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
273 assert_eq!(editor.text(cx), "äbcde");
274 assert_eq!(
275 editor.marked_text_ranges(cx),
276 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
277 );
278
279 // Finalize IME composition.
280 editor.replace_text_in_range(None, "ā", window, cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // IME composition edits are grouped and are undone/redone at once.
285 editor.undo(&Default::default(), window, cx);
286 assert_eq!(editor.text(cx), "abcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288 editor.redo(&Default::default(), window, cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition.
293 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
294 assert_eq!(
295 editor.marked_text_ranges(cx),
296 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
297 );
298
299 // Undoing during an IME composition cancels it.
300 editor.undo(&Default::default(), window, cx);
301 assert_eq!(editor.text(cx), "ābcde");
302 assert_eq!(editor.marked_text_ranges(cx), None);
303
304 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
305 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
306 assert_eq!(editor.text(cx), "ābcdè");
307 assert_eq!(
308 editor.marked_text_ranges(cx),
309 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
310 );
311
312 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
313 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
314 assert_eq!(editor.text(cx), "ābcdę");
315 assert_eq!(editor.marked_text_ranges(cx), None);
316
317 // Start a new IME composition with multiple cursors.
318 editor.change_selections(None, window, cx, |s| {
319 s.select_ranges([
320 OffsetUtf16(1)..OffsetUtf16(1),
321 OffsetUtf16(3)..OffsetUtf16(3),
322 OffsetUtf16(5)..OffsetUtf16(5),
323 ])
324 });
325 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
326 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(0)..OffsetUtf16(3),
331 OffsetUtf16(4)..OffsetUtf16(7),
332 OffsetUtf16(8)..OffsetUtf16(11)
333 ])
334 );
335
336 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
337 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
338 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
339 assert_eq!(
340 editor.marked_text_ranges(cx),
341 Some(vec![
342 OffsetUtf16(1)..OffsetUtf16(2),
343 OffsetUtf16(5)..OffsetUtf16(6),
344 OffsetUtf16(9)..OffsetUtf16(10)
345 ])
346 );
347
348 // Finalize IME composition with multiple cursors.
349 editor.replace_text_in_range(Some(9..10), "2", window, cx);
350 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
351 assert_eq!(editor.marked_text_ranges(cx), None);
352
353 editor
354 });
355}
356
357#[gpui::test]
358fn test_selection_with_mouse(cx: &mut TestAppContext) {
359 init_test(cx, |_| {});
360
361 let editor = cx.add_window(|window, cx| {
362 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
363 build_editor(buffer, window, cx)
364 });
365
366 _ = editor.update(cx, |editor, window, cx| {
367 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
368 });
369 assert_eq!(
370 editor
371 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
374 );
375
376 _ = editor.update(cx, |editor, window, cx| {
377 editor.update_selection(
378 DisplayPoint::new(DisplayRow(3), 3),
379 0,
380 gpui::Point::<f32>::default(),
381 window,
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
391 );
392
393 _ = editor.update(cx, |editor, window, cx| {
394 editor.update_selection(
395 DisplayPoint::new(DisplayRow(1), 1),
396 0,
397 gpui::Point::<f32>::default(),
398 window,
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |editor, window, cx| {
411 editor.end_selection(window, cx);
412 editor.update_selection(
413 DisplayPoint::new(DisplayRow(3), 3),
414 0,
415 gpui::Point::<f32>::default(),
416 window,
417 cx,
418 );
419 });
420
421 assert_eq!(
422 editor
423 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
424 .unwrap(),
425 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
426 );
427
428 _ = editor.update(cx, |editor, window, cx| {
429 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
430 editor.update_selection(
431 DisplayPoint::new(DisplayRow(0), 0),
432 0,
433 gpui::Point::<f32>::default(),
434 window,
435 cx,
436 );
437 });
438
439 assert_eq!(
440 editor
441 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
442 .unwrap(),
443 [
444 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
445 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
446 ]
447 );
448
449 _ = editor.update(cx, |editor, window, cx| {
450 editor.end_selection(window, cx);
451 });
452
453 assert_eq!(
454 editor
455 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
456 .unwrap(),
457 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
458 );
459}
460
461#[gpui::test]
462fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
463 init_test(cx, |_| {});
464
465 let editor = cx.add_window(|window, cx| {
466 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
467 build_editor(buffer, window, cx)
468 });
469
470 _ = editor.update(cx, |editor, window, cx| {
471 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
472 });
473
474 _ = editor.update(cx, |editor, window, cx| {
475 editor.end_selection(window, cx);
476 });
477
478 _ = editor.update(cx, |editor, window, cx| {
479 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
480 });
481
482 _ = editor.update(cx, |editor, window, cx| {
483 editor.end_selection(window, cx);
484 });
485
486 assert_eq!(
487 editor
488 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
489 .unwrap(),
490 [
491 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
492 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
493 ]
494 );
495
496 _ = editor.update(cx, |editor, window, cx| {
497 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
498 });
499
500 _ = editor.update(cx, |editor, window, cx| {
501 editor.end_selection(window, cx);
502 });
503
504 assert_eq!(
505 editor
506 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
507 .unwrap(),
508 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
509 );
510}
511
512#[gpui::test]
513fn test_canceling_pending_selection(cx: &mut TestAppContext) {
514 init_test(cx, |_| {});
515
516 let editor = cx.add_window(|window, cx| {
517 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
518 build_editor(buffer, window, cx)
519 });
520
521 _ = editor.update(cx, |editor, window, cx| {
522 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
523 assert_eq!(
524 editor.selections.display_ranges(cx),
525 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
526 );
527 });
528
529 _ = editor.update(cx, |editor, window, cx| {
530 editor.update_selection(
531 DisplayPoint::new(DisplayRow(3), 3),
532 0,
533 gpui::Point::<f32>::default(),
534 window,
535 cx,
536 );
537 assert_eq!(
538 editor.selections.display_ranges(cx),
539 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
540 );
541 });
542
543 _ = editor.update(cx, |editor, window, cx| {
544 editor.cancel(&Cancel, window, cx);
545 editor.update_selection(
546 DisplayPoint::new(DisplayRow(1), 1),
547 0,
548 gpui::Point::<f32>::default(),
549 window,
550 cx,
551 );
552 assert_eq!(
553 editor.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
555 );
556 });
557}
558
559#[gpui::test]
560fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
561 init_test(cx, |_| {});
562
563 let editor = cx.add_window(|window, cx| {
564 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
565 build_editor(buffer, window, cx)
566 });
567
568 _ = editor.update(cx, |editor, window, cx| {
569 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
570 assert_eq!(
571 editor.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
573 );
574
575 editor.move_down(&Default::default(), window, cx);
576 assert_eq!(
577 editor.selections.display_ranges(cx),
578 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
579 );
580
581 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
582 assert_eq!(
583 editor.selections.display_ranges(cx),
584 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
585 );
586
587 editor.move_up(&Default::default(), window, cx);
588 assert_eq!(
589 editor.selections.display_ranges(cx),
590 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
591 );
592 });
593}
594
595#[gpui::test]
596fn test_clone(cx: &mut TestAppContext) {
597 init_test(cx, |_| {});
598
599 let (text, selection_ranges) = marked_text_ranges(
600 indoc! {"
601 one
602 two
603 threeˇ
604 four
605 fiveˇ
606 "},
607 true,
608 );
609
610 let editor = cx.add_window(|window, cx| {
611 let buffer = MultiBuffer::build_simple(&text, cx);
612 build_editor(buffer, window, cx)
613 });
614
615 _ = editor.update(cx, |editor, window, cx| {
616 editor.change_selections(None, window, cx, |s| {
617 s.select_ranges(selection_ranges.clone())
618 });
619 editor.fold_creases(
620 vec![
621 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
622 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
623 ],
624 true,
625 window,
626 cx,
627 );
628 });
629
630 let cloned_editor = editor
631 .update(cx, |editor, _, cx| {
632 cx.open_window(Default::default(), |window, cx| {
633 cx.new(|cx| editor.clone(window, cx))
634 })
635 })
636 .unwrap()
637 .unwrap();
638
639 let snapshot = editor
640 .update(cx, |e, window, cx| e.snapshot(window, cx))
641 .unwrap();
642 let cloned_snapshot = cloned_editor
643 .update(cx, |e, window, cx| e.snapshot(window, cx))
644 .unwrap();
645
646 assert_eq!(
647 cloned_editor
648 .update(cx, |e, _, cx| e.display_text(cx))
649 .unwrap(),
650 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
651 );
652 assert_eq!(
653 cloned_snapshot
654 .folds_in_range(0..text.len())
655 .collect::<Vec<_>>(),
656 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
657 );
658 assert_set_eq!(
659 cloned_editor
660 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
661 .unwrap(),
662 editor
663 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
664 .unwrap()
665 );
666 assert_set_eq!(
667 cloned_editor
668 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
669 .unwrap(),
670 editor
671 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
672 .unwrap()
673 );
674}
675
676#[gpui::test]
677async fn test_navigation_history(cx: &mut TestAppContext) {
678 init_test(cx, |_| {});
679
680 use workspace::item::Item;
681
682 let fs = FakeFs::new(cx.executor());
683 let project = Project::test(fs, [], cx).await;
684 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
685 let pane = workspace
686 .update(cx, |workspace, _, _| workspace.active_pane().clone())
687 .unwrap();
688
689 _ = workspace.update(cx, |_v, window, cx| {
690 cx.new(|cx| {
691 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
692 let mut editor = build_editor(buffer.clone(), window, cx);
693 let handle = cx.entity();
694 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
695
696 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
697 editor.nav_history.as_mut().unwrap().pop_backward(cx)
698 }
699
700 // Move the cursor a small distance.
701 // Nothing is added to the navigation history.
702 editor.change_selections(None, window, cx, |s| {
703 s.select_display_ranges([
704 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
705 ])
706 });
707 editor.change_selections(None, window, cx, |s| {
708 s.select_display_ranges([
709 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
710 ])
711 });
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance.
715 // The history can jump back to the previous position.
716 editor.change_selections(None, window, cx, |s| {
717 s.select_display_ranges([
718 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
719 ])
720 });
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), window, cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Move the cursor a small distance via the mouse.
731 // Nothing is added to the navigation history.
732 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
733 editor.end_selection(window, cx);
734 assert_eq!(
735 editor.selections.display_ranges(cx),
736 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
737 );
738 assert!(pop_history(&mut editor, cx).is_none());
739
740 // Move the cursor a large distance via the mouse.
741 // The history can jump back to the previous position.
742 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
743 editor.end_selection(window, cx);
744 assert_eq!(
745 editor.selections.display_ranges(cx),
746 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
747 );
748 let nav_entry = pop_history(&mut editor, cx).unwrap();
749 editor.navigate(nav_entry.data.unwrap(), window, cx);
750 assert_eq!(nav_entry.item.id(), cx.entity_id());
751 assert_eq!(
752 editor.selections.display_ranges(cx),
753 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
754 );
755 assert!(pop_history(&mut editor, cx).is_none());
756
757 // Set scroll position to check later
758 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
759 let original_scroll_position = editor.scroll_manager.anchor();
760
761 // Jump to the end of the document and adjust scroll
762 editor.move_to_end(&MoveToEnd, window, cx);
763 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
764 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
765
766 let nav_entry = pop_history(&mut editor, cx).unwrap();
767 editor.navigate(nav_entry.data.unwrap(), window, cx);
768 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
769
770 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
771 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
772 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
773 let invalid_point = Point::new(9999, 0);
774 editor.navigate(
775 Box::new(NavigationData {
776 cursor_anchor: invalid_anchor,
777 cursor_position: invalid_point,
778 scroll_anchor: ScrollAnchor {
779 anchor: invalid_anchor,
780 offset: Default::default(),
781 },
782 scroll_top_row: invalid_point.row,
783 }),
784 window,
785 cx,
786 );
787 assert_eq!(
788 editor.selections.display_ranges(cx),
789 &[editor.max_point(cx)..editor.max_point(cx)]
790 );
791 assert_eq!(
792 editor.scroll_position(cx),
793 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
794 );
795
796 editor
797 })
798 });
799}
800
801#[gpui::test]
802fn test_cancel(cx: &mut TestAppContext) {
803 init_test(cx, |_| {});
804
805 let editor = cx.add_window(|window, cx| {
806 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
807 build_editor(buffer, window, cx)
808 });
809
810 _ = editor.update(cx, |editor, window, cx| {
811 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
812 editor.update_selection(
813 DisplayPoint::new(DisplayRow(1), 1),
814 0,
815 gpui::Point::<f32>::default(),
816 window,
817 cx,
818 );
819 editor.end_selection(window, cx);
820
821 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
822 editor.update_selection(
823 DisplayPoint::new(DisplayRow(0), 3),
824 0,
825 gpui::Point::<f32>::default(),
826 window,
827 cx,
828 );
829 editor.end_selection(window, cx);
830 assert_eq!(
831 editor.selections.display_ranges(cx),
832 [
833 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
834 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
835 ]
836 );
837 });
838
839 _ = editor.update(cx, |editor, window, cx| {
840 editor.cancel(&Cancel, window, cx);
841 assert_eq!(
842 editor.selections.display_ranges(cx),
843 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
844 );
845 });
846
847 _ = editor.update(cx, |editor, window, cx| {
848 editor.cancel(&Cancel, window, cx);
849 assert_eq!(
850 editor.selections.display_ranges(cx),
851 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
852 );
853 });
854}
855
856#[gpui::test]
857fn test_fold_action(cx: &mut TestAppContext) {
858 init_test(cx, |_| {});
859
860 let editor = cx.add_window(|window, cx| {
861 let buffer = MultiBuffer::build_simple(
862 &"
863 impl Foo {
864 // Hello!
865
866 fn a() {
867 1
868 }
869
870 fn b() {
871 2
872 }
873
874 fn c() {
875 3
876 }
877 }
878 "
879 .unindent(),
880 cx,
881 );
882 build_editor(buffer.clone(), window, cx)
883 });
884
885 _ = editor.update(cx, |editor, window, cx| {
886 editor.change_selections(None, window, cx, |s| {
887 s.select_display_ranges([
888 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
889 ]);
890 });
891 editor.fold(&Fold, window, cx);
892 assert_eq!(
893 editor.display_text(cx),
894 "
895 impl Foo {
896 // Hello!
897
898 fn a() {
899 1
900 }
901
902 fn b() {⋯
903 }
904
905 fn c() {⋯
906 }
907 }
908 "
909 .unindent(),
910 );
911
912 editor.fold(&Fold, window, cx);
913 assert_eq!(
914 editor.display_text(cx),
915 "
916 impl Foo {⋯
917 }
918 "
919 .unindent(),
920 );
921
922 editor.unfold_lines(&UnfoldLines, window, cx);
923 assert_eq!(
924 editor.display_text(cx),
925 "
926 impl Foo {
927 // Hello!
928
929 fn a() {
930 1
931 }
932
933 fn b() {⋯
934 }
935
936 fn c() {⋯
937 }
938 }
939 "
940 .unindent(),
941 );
942
943 editor.unfold_lines(&UnfoldLines, window, cx);
944 assert_eq!(
945 editor.display_text(cx),
946 editor.buffer.read(cx).read(cx).text()
947 );
948 });
949}
950
951#[gpui::test]
952fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
953 init_test(cx, |_| {});
954
955 let editor = cx.add_window(|window, cx| {
956 let buffer = MultiBuffer::build_simple(
957 &"
958 class Foo:
959 # Hello!
960
961 def a():
962 print(1)
963
964 def b():
965 print(2)
966
967 def c():
968 print(3)
969 "
970 .unindent(),
971 cx,
972 );
973 build_editor(buffer.clone(), window, cx)
974 });
975
976 _ = editor.update(cx, |editor, window, cx| {
977 editor.change_selections(None, window, cx, |s| {
978 s.select_display_ranges([
979 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
980 ]);
981 });
982 editor.fold(&Fold, window, cx);
983 assert_eq!(
984 editor.display_text(cx),
985 "
986 class Foo:
987 # Hello!
988
989 def a():
990 print(1)
991
992 def b():⋯
993
994 def c():⋯
995 "
996 .unindent(),
997 );
998
999 editor.fold(&Fold, window, cx);
1000 assert_eq!(
1001 editor.display_text(cx),
1002 "
1003 class Foo:⋯
1004 "
1005 .unindent(),
1006 );
1007
1008 editor.unfold_lines(&UnfoldLines, window, cx);
1009 assert_eq!(
1010 editor.display_text(cx),
1011 "
1012 class Foo:
1013 # Hello!
1014
1015 def a():
1016 print(1)
1017
1018 def b():⋯
1019
1020 def c():⋯
1021 "
1022 .unindent(),
1023 );
1024
1025 editor.unfold_lines(&UnfoldLines, window, cx);
1026 assert_eq!(
1027 editor.display_text(cx),
1028 editor.buffer.read(cx).read(cx).text()
1029 );
1030 });
1031}
1032
1033#[gpui::test]
1034fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1035 init_test(cx, |_| {});
1036
1037 let editor = cx.add_window(|window, cx| {
1038 let buffer = MultiBuffer::build_simple(
1039 &"
1040 class Foo:
1041 # Hello!
1042
1043 def a():
1044 print(1)
1045
1046 def b():
1047 print(2)
1048
1049
1050 def c():
1051 print(3)
1052
1053
1054 "
1055 .unindent(),
1056 cx,
1057 );
1058 build_editor(buffer.clone(), window, cx)
1059 });
1060
1061 _ = editor.update(cx, |editor, window, cx| {
1062 editor.change_selections(None, window, cx, |s| {
1063 s.select_display_ranges([
1064 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1065 ]);
1066 });
1067 editor.fold(&Fold, window, cx);
1068 assert_eq!(
1069 editor.display_text(cx),
1070 "
1071 class Foo:
1072 # Hello!
1073
1074 def a():
1075 print(1)
1076
1077 def b():⋯
1078
1079
1080 def c():⋯
1081
1082
1083 "
1084 .unindent(),
1085 );
1086
1087 editor.fold(&Fold, window, cx);
1088 assert_eq!(
1089 editor.display_text(cx),
1090 "
1091 class Foo:⋯
1092
1093
1094 "
1095 .unindent(),
1096 );
1097
1098 editor.unfold_lines(&UnfoldLines, window, cx);
1099 assert_eq!(
1100 editor.display_text(cx),
1101 "
1102 class Foo:
1103 # Hello!
1104
1105 def a():
1106 print(1)
1107
1108 def b():⋯
1109
1110
1111 def c():⋯
1112
1113
1114 "
1115 .unindent(),
1116 );
1117
1118 editor.unfold_lines(&UnfoldLines, window, cx);
1119 assert_eq!(
1120 editor.display_text(cx),
1121 editor.buffer.read(cx).read(cx).text()
1122 );
1123 });
1124}
1125
1126#[gpui::test]
1127fn test_fold_at_level(cx: &mut TestAppContext) {
1128 init_test(cx, |_| {});
1129
1130 let editor = cx.add_window(|window, cx| {
1131 let buffer = MultiBuffer::build_simple(
1132 &"
1133 class Foo:
1134 # Hello!
1135
1136 def a():
1137 print(1)
1138
1139 def b():
1140 print(2)
1141
1142
1143 class Bar:
1144 # World!
1145
1146 def a():
1147 print(1)
1148
1149 def b():
1150 print(2)
1151
1152
1153 "
1154 .unindent(),
1155 cx,
1156 );
1157 build_editor(buffer.clone(), window, cx)
1158 });
1159
1160 _ = editor.update(cx, |editor, window, cx| {
1161 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1162 assert_eq!(
1163 editor.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():⋯
1169
1170 def b():⋯
1171
1172
1173 class Bar:
1174 # World!
1175
1176 def a():⋯
1177
1178 def b():⋯
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1186 assert_eq!(
1187 editor.display_text(cx),
1188 "
1189 class Foo:⋯
1190
1191
1192 class Bar:⋯
1193
1194
1195 "
1196 .unindent(),
1197 );
1198
1199 editor.unfold_all(&UnfoldAll, window, cx);
1200 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1201 assert_eq!(
1202 editor.display_text(cx),
1203 "
1204 class Foo:
1205 # Hello!
1206
1207 def a():
1208 print(1)
1209
1210 def b():
1211 print(2)
1212
1213
1214 class Bar:
1215 # World!
1216
1217 def a():
1218 print(1)
1219
1220 def b():
1221 print(2)
1222
1223
1224 "
1225 .unindent(),
1226 );
1227
1228 assert_eq!(
1229 editor.display_text(cx),
1230 editor.buffer.read(cx).read(cx).text()
1231 );
1232 });
1233}
1234
1235#[gpui::test]
1236fn test_move_cursor(cx: &mut TestAppContext) {
1237 init_test(cx, |_| {});
1238
1239 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1240 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1241
1242 buffer.update(cx, |buffer, cx| {
1243 buffer.edit(
1244 vec![
1245 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1246 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1247 ],
1248 None,
1249 cx,
1250 );
1251 });
1252 _ = editor.update(cx, |editor, window, cx| {
1253 assert_eq!(
1254 editor.selections.display_ranges(cx),
1255 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1256 );
1257
1258 editor.move_down(&MoveDown, window, cx);
1259 assert_eq!(
1260 editor.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1262 );
1263
1264 editor.move_right(&MoveRight, window, cx);
1265 assert_eq!(
1266 editor.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1268 );
1269
1270 editor.move_left(&MoveLeft, window, cx);
1271 assert_eq!(
1272 editor.selections.display_ranges(cx),
1273 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1274 );
1275
1276 editor.move_up(&MoveUp, window, cx);
1277 assert_eq!(
1278 editor.selections.display_ranges(cx),
1279 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1280 );
1281
1282 editor.move_to_end(&MoveToEnd, window, cx);
1283 assert_eq!(
1284 editor.selections.display_ranges(cx),
1285 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1286 );
1287
1288 editor.move_to_beginning(&MoveToBeginning, window, cx);
1289 assert_eq!(
1290 editor.selections.display_ranges(cx),
1291 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1292 );
1293
1294 editor.change_selections(None, window, cx, |s| {
1295 s.select_display_ranges([
1296 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1297 ]);
1298 });
1299 editor.select_to_beginning(&SelectToBeginning, window, cx);
1300 assert_eq!(
1301 editor.selections.display_ranges(cx),
1302 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1303 );
1304
1305 editor.select_to_end(&SelectToEnd, window, cx);
1306 assert_eq!(
1307 editor.selections.display_ranges(cx),
1308 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1309 );
1310 });
1311}
1312
1313// TODO: Re-enable this test
1314#[cfg(target_os = "macos")]
1315#[gpui::test]
1316fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1317 init_test(cx, |_| {});
1318
1319 let editor = cx.add_window(|window, cx| {
1320 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1321 build_editor(buffer.clone(), window, cx)
1322 });
1323
1324 assert_eq!('🟥'.len_utf8(), 4);
1325 assert_eq!('α'.len_utf8(), 2);
1326
1327 _ = editor.update(cx, |editor, window, cx| {
1328 editor.fold_creases(
1329 vec![
1330 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1332 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1333 ],
1334 true,
1335 window,
1336 cx,
1337 );
1338 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1339
1340 editor.move_right(&MoveRight, window, cx);
1341 assert_eq!(
1342 editor.selections.display_ranges(cx),
1343 &[empty_range(0, "🟥".len())]
1344 );
1345 editor.move_right(&MoveRight, window, cx);
1346 assert_eq!(
1347 editor.selections.display_ranges(cx),
1348 &[empty_range(0, "🟥🟧".len())]
1349 );
1350 editor.move_right(&MoveRight, window, cx);
1351 assert_eq!(
1352 editor.selections.display_ranges(cx),
1353 &[empty_range(0, "🟥🟧⋯".len())]
1354 );
1355
1356 editor.move_down(&MoveDown, window, cx);
1357 assert_eq!(
1358 editor.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 editor.move_left(&MoveLeft, window, cx);
1362 assert_eq!(
1363 editor.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯".len())]
1365 );
1366 editor.move_left(&MoveLeft, window, cx);
1367 assert_eq!(
1368 editor.selections.display_ranges(cx),
1369 &[empty_range(1, "ab".len())]
1370 );
1371 editor.move_left(&MoveLeft, window, cx);
1372 assert_eq!(
1373 editor.selections.display_ranges(cx),
1374 &[empty_range(1, "a".len())]
1375 );
1376
1377 editor.move_down(&MoveDown, window, cx);
1378 assert_eq!(
1379 editor.selections.display_ranges(cx),
1380 &[empty_range(2, "α".len())]
1381 );
1382 editor.move_right(&MoveRight, window, cx);
1383 assert_eq!(
1384 editor.selections.display_ranges(cx),
1385 &[empty_range(2, "αβ".len())]
1386 );
1387 editor.move_right(&MoveRight, window, cx);
1388 assert_eq!(
1389 editor.selections.display_ranges(cx),
1390 &[empty_range(2, "αβ⋯".len())]
1391 );
1392 editor.move_right(&MoveRight, window, cx);
1393 assert_eq!(
1394 editor.selections.display_ranges(cx),
1395 &[empty_range(2, "αβ⋯ε".len())]
1396 );
1397
1398 editor.move_up(&MoveUp, window, cx);
1399 assert_eq!(
1400 editor.selections.display_ranges(cx),
1401 &[empty_range(1, "ab⋯e".len())]
1402 );
1403 editor.move_down(&MoveDown, window, cx);
1404 assert_eq!(
1405 editor.selections.display_ranges(cx),
1406 &[empty_range(2, "αβ⋯ε".len())]
1407 );
1408 editor.move_up(&MoveUp, window, cx);
1409 assert_eq!(
1410 editor.selections.display_ranges(cx),
1411 &[empty_range(1, "ab⋯e".len())]
1412 );
1413
1414 editor.move_up(&MoveUp, window, cx);
1415 assert_eq!(
1416 editor.selections.display_ranges(cx),
1417 &[empty_range(0, "🟥🟧".len())]
1418 );
1419 editor.move_left(&MoveLeft, window, cx);
1420 assert_eq!(
1421 editor.selections.display_ranges(cx),
1422 &[empty_range(0, "🟥".len())]
1423 );
1424 editor.move_left(&MoveLeft, window, cx);
1425 assert_eq!(
1426 editor.selections.display_ranges(cx),
1427 &[empty_range(0, "".len())]
1428 );
1429 });
1430}
1431
1432#[gpui::test]
1433fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1434 init_test(cx, |_| {});
1435
1436 let editor = cx.add_window(|window, cx| {
1437 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1438 build_editor(buffer.clone(), window, cx)
1439 });
1440 _ = editor.update(cx, |editor, window, cx| {
1441 editor.change_selections(None, window, cx, |s| {
1442 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1443 });
1444
1445 // moving above start of document should move selection to start of document,
1446 // but the next move down should still be at the original goal_x
1447 editor.move_up(&MoveUp, window, cx);
1448 assert_eq!(
1449 editor.selections.display_ranges(cx),
1450 &[empty_range(0, "".len())]
1451 );
1452
1453 editor.move_down(&MoveDown, window, cx);
1454 assert_eq!(
1455 editor.selections.display_ranges(cx),
1456 &[empty_range(1, "abcd".len())]
1457 );
1458
1459 editor.move_down(&MoveDown, window, cx);
1460 assert_eq!(
1461 editor.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464
1465 editor.move_down(&MoveDown, window, cx);
1466 assert_eq!(
1467 editor.selections.display_ranges(cx),
1468 &[empty_range(3, "abcd".len())]
1469 );
1470
1471 editor.move_down(&MoveDown, window, cx);
1472 assert_eq!(
1473 editor.selections.display_ranges(cx),
1474 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1475 );
1476
1477 // moving past end of document should not change goal_x
1478 editor.move_down(&MoveDown, window, cx);
1479 assert_eq!(
1480 editor.selections.display_ranges(cx),
1481 &[empty_range(5, "".len())]
1482 );
1483
1484 editor.move_down(&MoveDown, window, cx);
1485 assert_eq!(
1486 editor.selections.display_ranges(cx),
1487 &[empty_range(5, "".len())]
1488 );
1489
1490 editor.move_up(&MoveUp, window, cx);
1491 assert_eq!(
1492 editor.selections.display_ranges(cx),
1493 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1494 );
1495
1496 editor.move_up(&MoveUp, window, cx);
1497 assert_eq!(
1498 editor.selections.display_ranges(cx),
1499 &[empty_range(3, "abcd".len())]
1500 );
1501
1502 editor.move_up(&MoveUp, window, cx);
1503 assert_eq!(
1504 editor.selections.display_ranges(cx),
1505 &[empty_range(2, "αβγ".len())]
1506 );
1507 });
1508}
1509
1510#[gpui::test]
1511fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1512 init_test(cx, |_| {});
1513 let move_to_beg = MoveToBeginningOfLine {
1514 stop_at_soft_wraps: true,
1515 stop_at_indent: true,
1516 };
1517
1518 let delete_to_beg = DeleteToBeginningOfLine {
1519 stop_at_indent: false,
1520 };
1521
1522 let move_to_end = MoveToEndOfLine {
1523 stop_at_soft_wraps: true,
1524 };
1525
1526 let editor = cx.add_window(|window, cx| {
1527 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1528 build_editor(buffer, window, cx)
1529 });
1530 _ = editor.update(cx, |editor, window, cx| {
1531 editor.change_selections(None, window, cx, |s| {
1532 s.select_display_ranges([
1533 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1534 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1535 ]);
1536 });
1537 });
1538
1539 _ = editor.update(cx, |editor, window, cx| {
1540 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1541 assert_eq!(
1542 editor.selections.display_ranges(cx),
1543 &[
1544 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1545 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1546 ]
1547 );
1548 });
1549
1550 _ = editor.update(cx, |editor, window, cx| {
1551 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1552 assert_eq!(
1553 editor.selections.display_ranges(cx),
1554 &[
1555 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1556 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1557 ]
1558 );
1559 });
1560
1561 _ = editor.update(cx, |editor, window, cx| {
1562 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1563 assert_eq!(
1564 editor.selections.display_ranges(cx),
1565 &[
1566 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1567 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1568 ]
1569 );
1570 });
1571
1572 _ = editor.update(cx, |editor, window, cx| {
1573 editor.move_to_end_of_line(&move_to_end, window, cx);
1574 assert_eq!(
1575 editor.selections.display_ranges(cx),
1576 &[
1577 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1578 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1579 ]
1580 );
1581 });
1582
1583 // Moving to the end of line again is a no-op.
1584 _ = editor.update(cx, |editor, window, cx| {
1585 editor.move_to_end_of_line(&move_to_end, window, cx);
1586 assert_eq!(
1587 editor.selections.display_ranges(cx),
1588 &[
1589 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1590 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1591 ]
1592 );
1593 });
1594
1595 _ = editor.update(cx, |editor, window, cx| {
1596 editor.move_left(&MoveLeft, window, cx);
1597 editor.select_to_beginning_of_line(
1598 &SelectToBeginningOfLine {
1599 stop_at_soft_wraps: true,
1600 stop_at_indent: true,
1601 },
1602 window,
1603 cx,
1604 );
1605 assert_eq!(
1606 editor.selections.display_ranges(cx),
1607 &[
1608 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1609 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1610 ]
1611 );
1612 });
1613
1614 _ = editor.update(cx, |editor, window, cx| {
1615 editor.select_to_beginning_of_line(
1616 &SelectToBeginningOfLine {
1617 stop_at_soft_wraps: true,
1618 stop_at_indent: true,
1619 },
1620 window,
1621 cx,
1622 );
1623 assert_eq!(
1624 editor.selections.display_ranges(cx),
1625 &[
1626 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1627 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1628 ]
1629 );
1630 });
1631
1632 _ = editor.update(cx, |editor, window, cx| {
1633 editor.select_to_beginning_of_line(
1634 &SelectToBeginningOfLine {
1635 stop_at_soft_wraps: true,
1636 stop_at_indent: true,
1637 },
1638 window,
1639 cx,
1640 );
1641 assert_eq!(
1642 editor.selections.display_ranges(cx),
1643 &[
1644 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1645 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1646 ]
1647 );
1648 });
1649
1650 _ = editor.update(cx, |editor, window, cx| {
1651 editor.select_to_end_of_line(
1652 &SelectToEndOfLine {
1653 stop_at_soft_wraps: true,
1654 },
1655 window,
1656 cx,
1657 );
1658 assert_eq!(
1659 editor.selections.display_ranges(cx),
1660 &[
1661 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1662 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1663 ]
1664 );
1665 });
1666
1667 _ = editor.update(cx, |editor, window, cx| {
1668 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1669 assert_eq!(editor.display_text(cx), "ab\n de");
1670 assert_eq!(
1671 editor.selections.display_ranges(cx),
1672 &[
1673 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1674 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1675 ]
1676 );
1677 });
1678
1679 _ = editor.update(cx, |editor, window, cx| {
1680 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1681 assert_eq!(editor.display_text(cx), "\n");
1682 assert_eq!(
1683 editor.selections.display_ranges(cx),
1684 &[
1685 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1686 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1687 ]
1688 );
1689 });
1690}
1691
1692#[gpui::test]
1693fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1694 init_test(cx, |_| {});
1695 let move_to_beg = MoveToBeginningOfLine {
1696 stop_at_soft_wraps: false,
1697 stop_at_indent: false,
1698 };
1699
1700 let move_to_end = MoveToEndOfLine {
1701 stop_at_soft_wraps: false,
1702 };
1703
1704 let editor = cx.add_window(|window, cx| {
1705 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1706 build_editor(buffer, window, cx)
1707 });
1708
1709 _ = editor.update(cx, |editor, window, cx| {
1710 editor.set_wrap_width(Some(140.0.into()), cx);
1711
1712 // We expect the following lines after wrapping
1713 // ```
1714 // thequickbrownfox
1715 // jumpedoverthelazydo
1716 // gs
1717 // ```
1718 // The final `gs` was soft-wrapped onto a new line.
1719 assert_eq!(
1720 "thequickbrownfox\njumpedoverthelaz\nydogs",
1721 editor.display_text(cx),
1722 );
1723
1724 // First, let's assert behavior on the first line, that was not soft-wrapped.
1725 // Start the cursor at the `k` on the first line
1726 editor.change_selections(None, window, cx, |s| {
1727 s.select_display_ranges([
1728 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1729 ]);
1730 });
1731
1732 // Moving to the beginning of the line should put us at the beginning of the line.
1733 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1734 assert_eq!(
1735 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1736 editor.selections.display_ranges(cx)
1737 );
1738
1739 // Moving to the end of the line should put us at the end of the line.
1740 editor.move_to_end_of_line(&move_to_end, window, cx);
1741 assert_eq!(
1742 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1743 editor.selections.display_ranges(cx)
1744 );
1745
1746 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1747 // Start the cursor at the last line (`y` that was wrapped to a new line)
1748 editor.change_selections(None, window, cx, |s| {
1749 s.select_display_ranges([
1750 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1751 ]);
1752 });
1753
1754 // Moving to the beginning of the line should put us at the start of the second line of
1755 // display text, i.e., the `j`.
1756 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1757 assert_eq!(
1758 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1759 editor.selections.display_ranges(cx)
1760 );
1761
1762 // Moving to the beginning of the line again should be a no-op.
1763 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1764 assert_eq!(
1765 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1766 editor.selections.display_ranges(cx)
1767 );
1768
1769 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1770 // next display line.
1771 editor.move_to_end_of_line(&move_to_end, window, cx);
1772 assert_eq!(
1773 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1774 editor.selections.display_ranges(cx)
1775 );
1776
1777 // Moving to the end of the line again should be a no-op.
1778 editor.move_to_end_of_line(&move_to_end, window, cx);
1779 assert_eq!(
1780 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1781 editor.selections.display_ranges(cx)
1782 );
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let move_to_beg = MoveToBeginningOfLine {
1791 stop_at_soft_wraps: true,
1792 stop_at_indent: true,
1793 };
1794
1795 let select_to_beg = SelectToBeginningOfLine {
1796 stop_at_soft_wraps: true,
1797 stop_at_indent: true,
1798 };
1799
1800 let delete_to_beg = DeleteToBeginningOfLine {
1801 stop_at_indent: true,
1802 };
1803
1804 let move_to_end = MoveToEndOfLine {
1805 stop_at_soft_wraps: false,
1806 };
1807
1808 let editor = cx.add_window(|window, cx| {
1809 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1810 build_editor(buffer, window, cx)
1811 });
1812
1813 _ = editor.update(cx, |editor, window, cx| {
1814 editor.change_selections(None, window, cx, |s| {
1815 s.select_display_ranges([
1816 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1817 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1818 ]);
1819 });
1820
1821 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1822 // and the second cursor at the first non-whitespace character in the line.
1823 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1824 assert_eq!(
1825 editor.selections.display_ranges(cx),
1826 &[
1827 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1828 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1829 ]
1830 );
1831
1832 // Moving to the beginning of the line again should be a no-op for the first cursor,
1833 // and should move the second cursor to the beginning of the line.
1834 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1835 assert_eq!(
1836 editor.selections.display_ranges(cx),
1837 &[
1838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1839 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1840 ]
1841 );
1842
1843 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1844 // and should move the second cursor back to the first non-whitespace character in the line.
1845 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1846 assert_eq!(
1847 editor.selections.display_ranges(cx),
1848 &[
1849 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1851 ]
1852 );
1853
1854 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1855 // and to the first non-whitespace character in the line for the second cursor.
1856 editor.move_to_end_of_line(&move_to_end, window, cx);
1857 editor.move_left(&MoveLeft, window, cx);
1858 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1859 assert_eq!(
1860 editor.selections.display_ranges(cx),
1861 &[
1862 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1863 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1864 ]
1865 );
1866
1867 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1868 // and should select to the beginning of the line for the second cursor.
1869 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1870 assert_eq!(
1871 editor.selections.display_ranges(cx),
1872 &[
1873 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1874 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1875 ]
1876 );
1877
1878 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1879 // and should delete to the first non-whitespace character in the line for the second cursor.
1880 editor.move_to_end_of_line(&move_to_end, window, cx);
1881 editor.move_left(&MoveLeft, window, cx);
1882 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1883 assert_eq!(editor.text(cx), "c\n f");
1884 });
1885}
1886
1887#[gpui::test]
1888fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1889 init_test(cx, |_| {});
1890
1891 let editor = cx.add_window(|window, cx| {
1892 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1893 build_editor(buffer, window, cx)
1894 });
1895 _ = editor.update(cx, |editor, window, cx| {
1896 editor.change_selections(None, window, cx, |s| {
1897 s.select_display_ranges([
1898 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1899 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1900 ])
1901 });
1902
1903 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1904 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1905
1906 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1907 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1908
1909 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1910 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1911
1912 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1913 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1914
1915 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1916 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1917
1918 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1919 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1920
1921 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1922 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1923
1924 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1925 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1926
1927 editor.move_right(&MoveRight, window, cx);
1928 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1929 assert_selection_ranges(
1930 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1931 editor,
1932 cx,
1933 );
1934
1935 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1936 assert_selection_ranges(
1937 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1938 editor,
1939 cx,
1940 );
1941
1942 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1943 assert_selection_ranges(
1944 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1945 editor,
1946 cx,
1947 );
1948 });
1949}
1950
1951#[gpui::test]
1952fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1953 init_test(cx, |_| {});
1954
1955 let editor = cx.add_window(|window, cx| {
1956 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1957 build_editor(buffer, window, cx)
1958 });
1959
1960 _ = editor.update(cx, |editor, window, cx| {
1961 editor.set_wrap_width(Some(140.0.into()), cx);
1962 assert_eq!(
1963 editor.display_text(cx),
1964 "use one::{\n two::three::\n four::five\n};"
1965 );
1966
1967 editor.change_selections(None, window, cx, |s| {
1968 s.select_display_ranges([
1969 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1970 ]);
1971 });
1972
1973 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1974 assert_eq!(
1975 editor.selections.display_ranges(cx),
1976 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1977 );
1978
1979 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1980 assert_eq!(
1981 editor.selections.display_ranges(cx),
1982 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1983 );
1984
1985 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1986 assert_eq!(
1987 editor.selections.display_ranges(cx),
1988 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1989 );
1990
1991 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1992 assert_eq!(
1993 editor.selections.display_ranges(cx),
1994 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1995 );
1996
1997 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1998 assert_eq!(
1999 editor.selections.display_ranges(cx),
2000 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2001 );
2002
2003 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2004 assert_eq!(
2005 editor.selections.display_ranges(cx),
2006 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2007 );
2008 });
2009}
2010
2011#[gpui::test]
2012async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2013 init_test(cx, |_| {});
2014 let mut cx = EditorTestContext::new(cx).await;
2015
2016 let line_height = cx.editor(|editor, window, _| {
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(window.rem_size())
2022 });
2023 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2024
2025 cx.set_state(
2026 &r#"ˇone
2027 two
2028
2029 three
2030 fourˇ
2031 five
2032
2033 six"#
2034 .unindent(),
2035 );
2036
2037 cx.update_editor(|editor, window, cx| {
2038 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2039 });
2040 cx.assert_editor_state(
2041 &r#"one
2042 two
2043 ˇ
2044 three
2045 four
2046 five
2047 ˇ
2048 six"#
2049 .unindent(),
2050 );
2051
2052 cx.update_editor(|editor, window, cx| {
2053 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2054 });
2055 cx.assert_editor_state(
2056 &r#"one
2057 two
2058
2059 three
2060 four
2061 five
2062 ˇ
2063 sixˇ"#
2064 .unindent(),
2065 );
2066
2067 cx.update_editor(|editor, window, cx| {
2068 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2069 });
2070 cx.assert_editor_state(
2071 &r#"one
2072 two
2073
2074 three
2075 four
2076 five
2077
2078 sixˇ"#
2079 .unindent(),
2080 );
2081
2082 cx.update_editor(|editor, window, cx| {
2083 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2084 });
2085 cx.assert_editor_state(
2086 &r#"one
2087 two
2088
2089 three
2090 four
2091 five
2092 ˇ
2093 six"#
2094 .unindent(),
2095 );
2096
2097 cx.update_editor(|editor, window, cx| {
2098 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2099 });
2100 cx.assert_editor_state(
2101 &r#"one
2102 two
2103 ˇ
2104 three
2105 four
2106 five
2107
2108 six"#
2109 .unindent(),
2110 );
2111
2112 cx.update_editor(|editor, window, cx| {
2113 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2114 });
2115 cx.assert_editor_state(
2116 &r#"ˇone
2117 two
2118
2119 three
2120 four
2121 five
2122
2123 six"#
2124 .unindent(),
2125 );
2126}
2127
2128#[gpui::test]
2129async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2130 init_test(cx, |_| {});
2131 let mut cx = EditorTestContext::new(cx).await;
2132 let line_height = cx.editor(|editor, window, _| {
2133 editor
2134 .style()
2135 .unwrap()
2136 .text
2137 .line_height_in_pixels(window.rem_size())
2138 });
2139 let window = cx.window;
2140 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2141
2142 cx.set_state(
2143 r#"ˇone
2144 two
2145 three
2146 four
2147 five
2148 six
2149 seven
2150 eight
2151 nine
2152 ten
2153 "#,
2154 );
2155
2156 cx.update_editor(|editor, window, cx| {
2157 assert_eq!(
2158 editor.snapshot(window, cx).scroll_position(),
2159 gpui::Point::new(0., 0.)
2160 );
2161 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2162 assert_eq!(
2163 editor.snapshot(window, cx).scroll_position(),
2164 gpui::Point::new(0., 3.)
2165 );
2166 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2167 assert_eq!(
2168 editor.snapshot(window, cx).scroll_position(),
2169 gpui::Point::new(0., 6.)
2170 );
2171 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2172 assert_eq!(
2173 editor.snapshot(window, cx).scroll_position(),
2174 gpui::Point::new(0., 3.)
2175 );
2176
2177 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2178 assert_eq!(
2179 editor.snapshot(window, cx).scroll_position(),
2180 gpui::Point::new(0., 1.)
2181 );
2182 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2183 assert_eq!(
2184 editor.snapshot(window, cx).scroll_position(),
2185 gpui::Point::new(0., 3.)
2186 );
2187 });
2188}
2189
2190#[gpui::test]
2191async fn test_autoscroll(cx: &mut TestAppContext) {
2192 init_test(cx, |_| {});
2193 let mut cx = EditorTestContext::new(cx).await;
2194
2195 let line_height = cx.update_editor(|editor, window, cx| {
2196 editor.set_vertical_scroll_margin(2, cx);
2197 editor
2198 .style()
2199 .unwrap()
2200 .text
2201 .line_height_in_pixels(window.rem_size())
2202 });
2203 let window = cx.window;
2204 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2205
2206 cx.set_state(
2207 r#"ˇone
2208 two
2209 three
2210 four
2211 five
2212 six
2213 seven
2214 eight
2215 nine
2216 ten
2217 "#,
2218 );
2219 cx.update_editor(|editor, window, cx| {
2220 assert_eq!(
2221 editor.snapshot(window, cx).scroll_position(),
2222 gpui::Point::new(0., 0.0)
2223 );
2224 });
2225
2226 // Add a cursor below the visible area. Since both cursors cannot fit
2227 // on screen, the editor autoscrolls to reveal the newest cursor, and
2228 // allows the vertical scroll margin below that cursor.
2229 cx.update_editor(|editor, window, cx| {
2230 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2231 selections.select_ranges([
2232 Point::new(0, 0)..Point::new(0, 0),
2233 Point::new(6, 0)..Point::new(6, 0),
2234 ]);
2235 })
2236 });
2237 cx.update_editor(|editor, window, cx| {
2238 assert_eq!(
2239 editor.snapshot(window, cx).scroll_position(),
2240 gpui::Point::new(0., 3.0)
2241 );
2242 });
2243
2244 // Move down. The editor cursor scrolls down to track the newest cursor.
2245 cx.update_editor(|editor, window, cx| {
2246 editor.move_down(&Default::default(), window, cx);
2247 });
2248 cx.update_editor(|editor, window, cx| {
2249 assert_eq!(
2250 editor.snapshot(window, cx).scroll_position(),
2251 gpui::Point::new(0., 4.0)
2252 );
2253 });
2254
2255 // Add a cursor above the visible area. Since both cursors fit on screen,
2256 // the editor scrolls to show both.
2257 cx.update_editor(|editor, window, cx| {
2258 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2259 selections.select_ranges([
2260 Point::new(1, 0)..Point::new(1, 0),
2261 Point::new(6, 0)..Point::new(6, 0),
2262 ]);
2263 })
2264 });
2265 cx.update_editor(|editor, window, cx| {
2266 assert_eq!(
2267 editor.snapshot(window, cx).scroll_position(),
2268 gpui::Point::new(0., 1.0)
2269 );
2270 });
2271}
2272
2273#[gpui::test]
2274async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2275 init_test(cx, |_| {});
2276 let mut cx = EditorTestContext::new(cx).await;
2277
2278 let line_height = cx.editor(|editor, window, _cx| {
2279 editor
2280 .style()
2281 .unwrap()
2282 .text
2283 .line_height_in_pixels(window.rem_size())
2284 });
2285 let window = cx.window;
2286 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2287 cx.set_state(
2288 &r#"
2289 ˇone
2290 two
2291 threeˇ
2292 four
2293 five
2294 six
2295 seven
2296 eight
2297 nine
2298 ten
2299 "#
2300 .unindent(),
2301 );
2302
2303 cx.update_editor(|editor, window, cx| {
2304 editor.move_page_down(&MovePageDown::default(), window, cx)
2305 });
2306 cx.assert_editor_state(
2307 &r#"
2308 one
2309 two
2310 three
2311 ˇfour
2312 five
2313 sixˇ
2314 seven
2315 eight
2316 nine
2317 ten
2318 "#
2319 .unindent(),
2320 );
2321
2322 cx.update_editor(|editor, window, cx| {
2323 editor.move_page_down(&MovePageDown::default(), window, cx)
2324 });
2325 cx.assert_editor_state(
2326 &r#"
2327 one
2328 two
2329 three
2330 four
2331 five
2332 six
2333 ˇseven
2334 eight
2335 nineˇ
2336 ten
2337 "#
2338 .unindent(),
2339 );
2340
2341 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2342 cx.assert_editor_state(
2343 &r#"
2344 one
2345 two
2346 three
2347 ˇfour
2348 five
2349 sixˇ
2350 seven
2351 eight
2352 nine
2353 ten
2354 "#
2355 .unindent(),
2356 );
2357
2358 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2359 cx.assert_editor_state(
2360 &r#"
2361 ˇone
2362 two
2363 threeˇ
2364 four
2365 five
2366 six
2367 seven
2368 eight
2369 nine
2370 ten
2371 "#
2372 .unindent(),
2373 );
2374
2375 // Test select collapsing
2376 cx.update_editor(|editor, window, cx| {
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 editor.move_page_down(&MovePageDown::default(), window, cx);
2380 });
2381 cx.assert_editor_state(
2382 &r#"
2383 one
2384 two
2385 three
2386 four
2387 five
2388 six
2389 seven
2390 eight
2391 nine
2392 ˇten
2393 ˇ"#
2394 .unindent(),
2395 );
2396}
2397
2398#[gpui::test]
2399async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2400 init_test(cx, |_| {});
2401 let mut cx = EditorTestContext::new(cx).await;
2402 cx.set_state("one «two threeˇ» four");
2403 cx.update_editor(|editor, window, cx| {
2404 editor.delete_to_beginning_of_line(
2405 &DeleteToBeginningOfLine {
2406 stop_at_indent: false,
2407 },
2408 window,
2409 cx,
2410 );
2411 assert_eq!(editor.text(cx), " four");
2412 });
2413}
2414
2415#[gpui::test]
2416fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2417 init_test(cx, |_| {});
2418
2419 let editor = cx.add_window(|window, cx| {
2420 let buffer = MultiBuffer::build_simple("one two three four", cx);
2421 build_editor(buffer.clone(), window, cx)
2422 });
2423
2424 _ = editor.update(cx, |editor, window, cx| {
2425 editor.change_selections(None, window, cx, |s| {
2426 s.select_display_ranges([
2427 // an empty selection - the preceding word fragment is deleted
2428 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2429 // characters selected - they are deleted
2430 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2431 ])
2432 });
2433 editor.delete_to_previous_word_start(
2434 &DeleteToPreviousWordStart {
2435 ignore_newlines: false,
2436 },
2437 window,
2438 cx,
2439 );
2440 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2441 });
2442
2443 _ = editor.update(cx, |editor, window, cx| {
2444 editor.change_selections(None, window, cx, |s| {
2445 s.select_display_ranges([
2446 // an empty selection - the following word fragment is deleted
2447 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2448 // characters selected - they are deleted
2449 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2450 ])
2451 });
2452 editor.delete_to_next_word_end(
2453 &DeleteToNextWordEnd {
2454 ignore_newlines: false,
2455 },
2456 window,
2457 cx,
2458 );
2459 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2460 });
2461}
2462
2463#[gpui::test]
2464fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2465 init_test(cx, |_| {});
2466
2467 let editor = cx.add_window(|window, cx| {
2468 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2469 build_editor(buffer.clone(), window, cx)
2470 });
2471 let del_to_prev_word_start = DeleteToPreviousWordStart {
2472 ignore_newlines: false,
2473 };
2474 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2475 ignore_newlines: true,
2476 };
2477
2478 _ = editor.update(cx, |editor, window, cx| {
2479 editor.change_selections(None, window, cx, |s| {
2480 s.select_display_ranges([
2481 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2482 ])
2483 });
2484 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2485 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2486 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2487 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2488 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2489 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2490 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2491 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2492 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2493 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2494 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2495 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2496 });
2497}
2498
2499#[gpui::test]
2500fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2501 init_test(cx, |_| {});
2502
2503 let editor = cx.add_window(|window, cx| {
2504 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2505 build_editor(buffer.clone(), window, cx)
2506 });
2507 let del_to_next_word_end = DeleteToNextWordEnd {
2508 ignore_newlines: false,
2509 };
2510 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2511 ignore_newlines: true,
2512 };
2513
2514 _ = editor.update(cx, |editor, window, cx| {
2515 editor.change_selections(None, window, cx, |s| {
2516 s.select_display_ranges([
2517 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2518 ])
2519 });
2520 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2521 assert_eq!(
2522 editor.buffer.read(cx).read(cx).text(),
2523 "one\n two\nthree\n four"
2524 );
2525 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2526 assert_eq!(
2527 editor.buffer.read(cx).read(cx).text(),
2528 "\n two\nthree\n four"
2529 );
2530 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2531 assert_eq!(
2532 editor.buffer.read(cx).read(cx).text(),
2533 "two\nthree\n four"
2534 );
2535 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2536 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2537 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2538 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2539 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2540 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2541 });
2542}
2543
2544#[gpui::test]
2545fn test_newline(cx: &mut TestAppContext) {
2546 init_test(cx, |_| {});
2547
2548 let editor = cx.add_window(|window, cx| {
2549 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2550 build_editor(buffer.clone(), window, cx)
2551 });
2552
2553 _ = editor.update(cx, |editor, window, cx| {
2554 editor.change_selections(None, window, cx, |s| {
2555 s.select_display_ranges([
2556 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2557 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2558 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2559 ])
2560 });
2561
2562 editor.newline(&Newline, window, cx);
2563 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2564 });
2565}
2566
2567#[gpui::test]
2568fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|window, cx| {
2572 let buffer = MultiBuffer::build_simple(
2573 "
2574 a
2575 b(
2576 X
2577 )
2578 c(
2579 X
2580 )
2581 "
2582 .unindent()
2583 .as_str(),
2584 cx,
2585 );
2586 let mut editor = build_editor(buffer.clone(), window, cx);
2587 editor.change_selections(None, window, cx, |s| {
2588 s.select_ranges([
2589 Point::new(2, 4)..Point::new(2, 5),
2590 Point::new(5, 4)..Point::new(5, 5),
2591 ])
2592 });
2593 editor
2594 });
2595
2596 _ = editor.update(cx, |editor, window, cx| {
2597 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2598 editor.buffer.update(cx, |buffer, cx| {
2599 buffer.edit(
2600 [
2601 (Point::new(1, 2)..Point::new(3, 0), ""),
2602 (Point::new(4, 2)..Point::new(6, 0), ""),
2603 ],
2604 None,
2605 cx,
2606 );
2607 assert_eq!(
2608 buffer.read(cx).text(),
2609 "
2610 a
2611 b()
2612 c()
2613 "
2614 .unindent()
2615 );
2616 });
2617 assert_eq!(
2618 editor.selections.ranges(cx),
2619 &[
2620 Point::new(1, 2)..Point::new(1, 2),
2621 Point::new(2, 2)..Point::new(2, 2),
2622 ],
2623 );
2624
2625 editor.newline(&Newline, window, cx);
2626 assert_eq!(
2627 editor.text(cx),
2628 "
2629 a
2630 b(
2631 )
2632 c(
2633 )
2634 "
2635 .unindent()
2636 );
2637
2638 // The selections are moved after the inserted newlines
2639 assert_eq!(
2640 editor.selections.ranges(cx),
2641 &[
2642 Point::new(2, 0)..Point::new(2, 0),
2643 Point::new(4, 0)..Point::new(4, 0),
2644 ],
2645 );
2646 });
2647}
2648
2649#[gpui::test]
2650async fn test_newline_above(cx: &mut TestAppContext) {
2651 init_test(cx, |settings| {
2652 settings.defaults.tab_size = NonZeroU32::new(4)
2653 });
2654
2655 let language = Arc::new(
2656 Language::new(
2657 LanguageConfig::default(),
2658 Some(tree_sitter_rust::LANGUAGE.into()),
2659 )
2660 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2661 .unwrap(),
2662 );
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2666 cx.set_state(indoc! {"
2667 const a: ˇA = (
2668 (ˇ
2669 «const_functionˇ»(ˇ),
2670 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2671 )ˇ
2672 ˇ);ˇ
2673 "});
2674
2675 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2676 cx.assert_editor_state(indoc! {"
2677 ˇ
2678 const a: A = (
2679 ˇ
2680 (
2681 ˇ
2682 ˇ
2683 const_function(),
2684 ˇ
2685 ˇ
2686 ˇ
2687 ˇ
2688 something_else,
2689 ˇ
2690 )
2691 ˇ
2692 ˇ
2693 );
2694 "});
2695}
2696
2697#[gpui::test]
2698async fn test_newline_below(cx: &mut TestAppContext) {
2699 init_test(cx, |settings| {
2700 settings.defaults.tab_size = NonZeroU32::new(4)
2701 });
2702
2703 let language = Arc::new(
2704 Language::new(
2705 LanguageConfig::default(),
2706 Some(tree_sitter_rust::LANGUAGE.into()),
2707 )
2708 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2709 .unwrap(),
2710 );
2711
2712 let mut cx = EditorTestContext::new(cx).await;
2713 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2714 cx.set_state(indoc! {"
2715 const a: ˇA = (
2716 (ˇ
2717 «const_functionˇ»(ˇ),
2718 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2719 )ˇ
2720 ˇ);ˇ
2721 "});
2722
2723 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2724 cx.assert_editor_state(indoc! {"
2725 const a: A = (
2726 ˇ
2727 (
2728 ˇ
2729 const_function(),
2730 ˇ
2731 ˇ
2732 something_else,
2733 ˇ
2734 ˇ
2735 ˇ
2736 ˇ
2737 )
2738 ˇ
2739 );
2740 ˇ
2741 ˇ
2742 "});
2743}
2744
2745#[gpui::test]
2746async fn test_newline_comments(cx: &mut TestAppContext) {
2747 init_test(cx, |settings| {
2748 settings.defaults.tab_size = NonZeroU32::new(4)
2749 });
2750
2751 let language = Arc::new(Language::new(
2752 LanguageConfig {
2753 line_comments: vec!["//".into()],
2754 ..LanguageConfig::default()
2755 },
2756 None,
2757 ));
2758 {
2759 let mut cx = EditorTestContext::new(cx).await;
2760 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2761 cx.set_state(indoc! {"
2762 // Fooˇ
2763 "});
2764
2765 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2766 cx.assert_editor_state(indoc! {"
2767 // Foo
2768 //ˇ
2769 "});
2770 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2771 cx.set_state(indoc! {"
2772 ˇ// Foo
2773 "});
2774 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2775 cx.assert_editor_state(indoc! {"
2776
2777 ˇ// Foo
2778 "});
2779 }
2780 // Ensure that comment continuations can be disabled.
2781 update_test_language_settings(cx, |settings| {
2782 settings.defaults.extend_comment_on_newline = Some(false);
2783 });
2784 let mut cx = EditorTestContext::new(cx).await;
2785 cx.set_state(indoc! {"
2786 // Fooˇ
2787 "});
2788 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2789 cx.assert_editor_state(indoc! {"
2790 // Foo
2791 ˇ
2792 "});
2793}
2794
2795#[gpui::test]
2796fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2797 init_test(cx, |_| {});
2798
2799 let editor = cx.add_window(|window, cx| {
2800 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2801 let mut editor = build_editor(buffer.clone(), window, cx);
2802 editor.change_selections(None, window, cx, |s| {
2803 s.select_ranges([3..4, 11..12, 19..20])
2804 });
2805 editor
2806 });
2807
2808 _ = editor.update(cx, |editor, window, cx| {
2809 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2810 editor.buffer.update(cx, |buffer, cx| {
2811 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2812 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2813 });
2814 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2815
2816 editor.insert("Z", window, cx);
2817 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2818
2819 // The selections are moved after the inserted characters
2820 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2821 });
2822}
2823
2824#[gpui::test]
2825async fn test_tab(cx: &mut TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.tab_size = NonZeroU32::new(3)
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831 cx.set_state(indoc! {"
2832 ˇabˇc
2833 ˇ🏀ˇ🏀ˇefg
2834 dˇ
2835 "});
2836 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2837 cx.assert_editor_state(indoc! {"
2838 ˇab ˇc
2839 ˇ🏀 ˇ🏀 ˇefg
2840 d ˇ
2841 "});
2842
2843 cx.set_state(indoc! {"
2844 a
2845 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2846 "});
2847 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2848 cx.assert_editor_state(indoc! {"
2849 a
2850 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2851 "});
2852}
2853
2854#[gpui::test]
2855async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2856 init_test(cx, |_| {});
2857
2858 let mut cx = EditorTestContext::new(cx).await;
2859 let language = Arc::new(
2860 Language::new(
2861 LanguageConfig::default(),
2862 Some(tree_sitter_rust::LANGUAGE.into()),
2863 )
2864 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2865 .unwrap(),
2866 );
2867 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2868
2869 // cursors that are already at the suggested indent level insert
2870 // a soft tab. cursors that are to the left of the suggested indent
2871 // auto-indent their line.
2872 cx.set_state(indoc! {"
2873 ˇ
2874 const a: B = (
2875 c(
2876 d(
2877 ˇ
2878 )
2879 ˇ
2880 ˇ )
2881 );
2882 "});
2883 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2884 cx.assert_editor_state(indoc! {"
2885 ˇ
2886 const a: B = (
2887 c(
2888 d(
2889 ˇ
2890 )
2891 ˇ
2892 ˇ)
2893 );
2894 "});
2895
2896 // handle auto-indent when there are multiple cursors on the same line
2897 cx.set_state(indoc! {"
2898 const a: B = (
2899 c(
2900 ˇ ˇ
2901 ˇ )
2902 );
2903 "});
2904 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2905 cx.assert_editor_state(indoc! {"
2906 const a: B = (
2907 c(
2908 ˇ
2909 ˇ)
2910 );
2911 "});
2912}
2913
2914#[gpui::test]
2915async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2916 init_test(cx, |settings| {
2917 settings.defaults.tab_size = NonZeroU32::new(4)
2918 });
2919
2920 let language = Arc::new(
2921 Language::new(
2922 LanguageConfig::default(),
2923 Some(tree_sitter_rust::LANGUAGE.into()),
2924 )
2925 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2926 .unwrap(),
2927 );
2928
2929 let mut cx = EditorTestContext::new(cx).await;
2930 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2931 cx.set_state(indoc! {"
2932 fn a() {
2933 if b {
2934 \t ˇc
2935 }
2936 }
2937 "});
2938
2939 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2940 cx.assert_editor_state(indoc! {"
2941 fn a() {
2942 if b {
2943 ˇc
2944 }
2945 }
2946 "});
2947}
2948
2949#[gpui::test]
2950async fn test_indent_outdent(cx: &mut TestAppContext) {
2951 init_test(cx, |settings| {
2952 settings.defaults.tab_size = NonZeroU32::new(4);
2953 });
2954
2955 let mut cx = EditorTestContext::new(cx).await;
2956
2957 cx.set_state(indoc! {"
2958 «oneˇ» «twoˇ»
2959 three
2960 four
2961 "});
2962 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2963 cx.assert_editor_state(indoc! {"
2964 «oneˇ» «twoˇ»
2965 three
2966 four
2967 "});
2968
2969 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2970 cx.assert_editor_state(indoc! {"
2971 «oneˇ» «twoˇ»
2972 three
2973 four
2974 "});
2975
2976 // select across line ending
2977 cx.set_state(indoc! {"
2978 one two
2979 t«hree
2980 ˇ» four
2981 "});
2982 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2983 cx.assert_editor_state(indoc! {"
2984 one two
2985 t«hree
2986 ˇ» four
2987 "});
2988
2989 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2990 cx.assert_editor_state(indoc! {"
2991 one two
2992 t«hree
2993 ˇ» four
2994 "});
2995
2996 // Ensure that indenting/outdenting works when the cursor is at column 0.
2997 cx.set_state(indoc! {"
2998 one two
2999 ˇthree
3000 four
3001 "});
3002 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3003 cx.assert_editor_state(indoc! {"
3004 one two
3005 ˇthree
3006 four
3007 "});
3008
3009 cx.set_state(indoc! {"
3010 one two
3011 ˇ three
3012 four
3013 "});
3014 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3015 cx.assert_editor_state(indoc! {"
3016 one two
3017 ˇthree
3018 four
3019 "});
3020}
3021
3022#[gpui::test]
3023async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3024 init_test(cx, |settings| {
3025 settings.defaults.hard_tabs = Some(true);
3026 });
3027
3028 let mut cx = EditorTestContext::new(cx).await;
3029
3030 // select two ranges on one line
3031 cx.set_state(indoc! {"
3032 «oneˇ» «twoˇ»
3033 three
3034 four
3035 "});
3036 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3037 cx.assert_editor_state(indoc! {"
3038 \t«oneˇ» «twoˇ»
3039 three
3040 four
3041 "});
3042 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3043 cx.assert_editor_state(indoc! {"
3044 \t\t«oneˇ» «twoˇ»
3045 three
3046 four
3047 "});
3048 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3049 cx.assert_editor_state(indoc! {"
3050 \t«oneˇ» «twoˇ»
3051 three
3052 four
3053 "});
3054 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3055 cx.assert_editor_state(indoc! {"
3056 «oneˇ» «twoˇ»
3057 three
3058 four
3059 "});
3060
3061 // select across a line ending
3062 cx.set_state(indoc! {"
3063 one two
3064 t«hree
3065 ˇ»four
3066 "});
3067 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3068 cx.assert_editor_state(indoc! {"
3069 one two
3070 \tt«hree
3071 ˇ»four
3072 "});
3073 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3074 cx.assert_editor_state(indoc! {"
3075 one two
3076 \t\tt«hree
3077 ˇ»four
3078 "});
3079 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3080 cx.assert_editor_state(indoc! {"
3081 one two
3082 \tt«hree
3083 ˇ»four
3084 "});
3085 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3086 cx.assert_editor_state(indoc! {"
3087 one two
3088 t«hree
3089 ˇ»four
3090 "});
3091
3092 // Ensure that indenting/outdenting works when the cursor is at column 0.
3093 cx.set_state(indoc! {"
3094 one two
3095 ˇthree
3096 four
3097 "});
3098 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3099 cx.assert_editor_state(indoc! {"
3100 one two
3101 ˇthree
3102 four
3103 "});
3104 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3105 cx.assert_editor_state(indoc! {"
3106 one two
3107 \tˇthree
3108 four
3109 "});
3110 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3111 cx.assert_editor_state(indoc! {"
3112 one two
3113 ˇthree
3114 four
3115 "});
3116}
3117
3118#[gpui::test]
3119fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3120 init_test(cx, |settings| {
3121 settings.languages.extend([
3122 (
3123 "TOML".into(),
3124 LanguageSettingsContent {
3125 tab_size: NonZeroU32::new(2),
3126 ..Default::default()
3127 },
3128 ),
3129 (
3130 "Rust".into(),
3131 LanguageSettingsContent {
3132 tab_size: NonZeroU32::new(4),
3133 ..Default::default()
3134 },
3135 ),
3136 ]);
3137 });
3138
3139 let toml_language = Arc::new(Language::new(
3140 LanguageConfig {
3141 name: "TOML".into(),
3142 ..Default::default()
3143 },
3144 None,
3145 ));
3146 let rust_language = Arc::new(Language::new(
3147 LanguageConfig {
3148 name: "Rust".into(),
3149 ..Default::default()
3150 },
3151 None,
3152 ));
3153
3154 let toml_buffer =
3155 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3156 let rust_buffer =
3157 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3158 let multibuffer = cx.new(|cx| {
3159 let mut multibuffer = MultiBuffer::new(ReadWrite);
3160 multibuffer.push_excerpts(
3161 toml_buffer.clone(),
3162 [ExcerptRange {
3163 context: Point::new(0, 0)..Point::new(2, 0),
3164 primary: None,
3165 }],
3166 cx,
3167 );
3168 multibuffer.push_excerpts(
3169 rust_buffer.clone(),
3170 [ExcerptRange {
3171 context: Point::new(0, 0)..Point::new(1, 0),
3172 primary: None,
3173 }],
3174 cx,
3175 );
3176 multibuffer
3177 });
3178
3179 cx.add_window(|window, cx| {
3180 let mut editor = build_editor(multibuffer, window, cx);
3181
3182 assert_eq!(
3183 editor.text(cx),
3184 indoc! {"
3185 a = 1
3186 b = 2
3187
3188 const c: usize = 3;
3189 "}
3190 );
3191
3192 select_ranges(
3193 &mut editor,
3194 indoc! {"
3195 «aˇ» = 1
3196 b = 2
3197
3198 «const c:ˇ» usize = 3;
3199 "},
3200 window,
3201 cx,
3202 );
3203
3204 editor.tab(&Tab, window, cx);
3205 assert_text_with_selections(
3206 &mut editor,
3207 indoc! {"
3208 «aˇ» = 1
3209 b = 2
3210
3211 «const c:ˇ» usize = 3;
3212 "},
3213 cx,
3214 );
3215 editor.backtab(&Backtab, window, cx);
3216 assert_text_with_selections(
3217 &mut editor,
3218 indoc! {"
3219 «aˇ» = 1
3220 b = 2
3221
3222 «const c:ˇ» usize = 3;
3223 "},
3224 cx,
3225 );
3226
3227 editor
3228 });
3229}
3230
3231#[gpui::test]
3232async fn test_backspace(cx: &mut TestAppContext) {
3233 init_test(cx, |_| {});
3234
3235 let mut cx = EditorTestContext::new(cx).await;
3236
3237 // Basic backspace
3238 cx.set_state(indoc! {"
3239 onˇe two three
3240 fou«rˇ» five six
3241 seven «ˇeight nine
3242 »ten
3243 "});
3244 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3245 cx.assert_editor_state(indoc! {"
3246 oˇe two three
3247 fouˇ five six
3248 seven ˇten
3249 "});
3250
3251 // Test backspace inside and around indents
3252 cx.set_state(indoc! {"
3253 zero
3254 ˇone
3255 ˇtwo
3256 ˇ ˇ ˇ three
3257 ˇ ˇ four
3258 "});
3259 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3260 cx.assert_editor_state(indoc! {"
3261 zero
3262 ˇone
3263 ˇtwo
3264 ˇ threeˇ four
3265 "});
3266
3267 // Test backspace with line_mode set to true
3268 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3269 cx.set_state(indoc! {"
3270 The ˇquick ˇbrown
3271 fox jumps over
3272 the lazy dog
3273 ˇThe qu«ick bˇ»rown"});
3274 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3275 cx.assert_editor_state(indoc! {"
3276 ˇfox jumps over
3277 the lazy dogˇ"});
3278}
3279
3280#[gpui::test]
3281async fn test_delete(cx: &mut TestAppContext) {
3282 init_test(cx, |_| {});
3283
3284 let mut cx = EditorTestContext::new(cx).await;
3285 cx.set_state(indoc! {"
3286 onˇe two three
3287 fou«rˇ» five six
3288 seven «ˇeight nine
3289 »ten
3290 "});
3291 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3292 cx.assert_editor_state(indoc! {"
3293 onˇ two three
3294 fouˇ five six
3295 seven ˇten
3296 "});
3297
3298 // Test backspace with line_mode set to true
3299 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3300 cx.set_state(indoc! {"
3301 The ˇquick ˇbrown
3302 fox «ˇjum»ps over
3303 the lazy dog
3304 ˇThe qu«ick bˇ»rown"});
3305 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3306 cx.assert_editor_state("ˇthe lazy dogˇ");
3307}
3308
3309#[gpui::test]
3310fn test_delete_line(cx: &mut TestAppContext) {
3311 init_test(cx, |_| {});
3312
3313 let editor = cx.add_window(|window, cx| {
3314 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3315 build_editor(buffer, window, cx)
3316 });
3317 _ = editor.update(cx, |editor, window, cx| {
3318 editor.change_selections(None, window, cx, |s| {
3319 s.select_display_ranges([
3320 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3321 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3322 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3323 ])
3324 });
3325 editor.delete_line(&DeleteLine, window, cx);
3326 assert_eq!(editor.display_text(cx), "ghi");
3327 assert_eq!(
3328 editor.selections.display_ranges(cx),
3329 vec![
3330 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3331 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3332 ]
3333 );
3334 });
3335
3336 let editor = cx.add_window(|window, cx| {
3337 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3338 build_editor(buffer, window, cx)
3339 });
3340 _ = editor.update(cx, |editor, window, cx| {
3341 editor.change_selections(None, window, cx, |s| {
3342 s.select_display_ranges([
3343 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3344 ])
3345 });
3346 editor.delete_line(&DeleteLine, window, cx);
3347 assert_eq!(editor.display_text(cx), "ghi\n");
3348 assert_eq!(
3349 editor.selections.display_ranges(cx),
3350 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3351 );
3352 });
3353}
3354
3355#[gpui::test]
3356fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3357 init_test(cx, |_| {});
3358
3359 cx.add_window(|window, cx| {
3360 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3361 let mut editor = build_editor(buffer.clone(), window, cx);
3362 let buffer = buffer.read(cx).as_singleton().unwrap();
3363
3364 assert_eq!(
3365 editor.selections.ranges::<Point>(cx),
3366 &[Point::new(0, 0)..Point::new(0, 0)]
3367 );
3368
3369 // When on single line, replace newline at end by space
3370 editor.join_lines(&JoinLines, window, cx);
3371 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3372 assert_eq!(
3373 editor.selections.ranges::<Point>(cx),
3374 &[Point::new(0, 3)..Point::new(0, 3)]
3375 );
3376
3377 // When multiple lines are selected, remove newlines that are spanned by the selection
3378 editor.change_selections(None, window, cx, |s| {
3379 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3380 });
3381 editor.join_lines(&JoinLines, window, cx);
3382 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3383 assert_eq!(
3384 editor.selections.ranges::<Point>(cx),
3385 &[Point::new(0, 11)..Point::new(0, 11)]
3386 );
3387
3388 // Undo should be transactional
3389 editor.undo(&Undo, window, cx);
3390 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3391 assert_eq!(
3392 editor.selections.ranges::<Point>(cx),
3393 &[Point::new(0, 5)..Point::new(2, 2)]
3394 );
3395
3396 // When joining an empty line don't insert a space
3397 editor.change_selections(None, window, cx, |s| {
3398 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3399 });
3400 editor.join_lines(&JoinLines, window, cx);
3401 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3402 assert_eq!(
3403 editor.selections.ranges::<Point>(cx),
3404 [Point::new(2, 3)..Point::new(2, 3)]
3405 );
3406
3407 // We can remove trailing newlines
3408 editor.join_lines(&JoinLines, window, cx);
3409 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3410 assert_eq!(
3411 editor.selections.ranges::<Point>(cx),
3412 [Point::new(2, 3)..Point::new(2, 3)]
3413 );
3414
3415 // We don't blow up on the last line
3416 editor.join_lines(&JoinLines, window, cx);
3417 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3418 assert_eq!(
3419 editor.selections.ranges::<Point>(cx),
3420 [Point::new(2, 3)..Point::new(2, 3)]
3421 );
3422
3423 // reset to test indentation
3424 editor.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 [
3427 (Point::new(1, 0)..Point::new(1, 2), " "),
3428 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3429 ],
3430 None,
3431 cx,
3432 )
3433 });
3434
3435 // We remove any leading spaces
3436 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3437 editor.change_selections(None, window, cx, |s| {
3438 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3439 });
3440 editor.join_lines(&JoinLines, window, cx);
3441 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3442
3443 // We don't insert a space for a line containing only spaces
3444 editor.join_lines(&JoinLines, window, cx);
3445 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3446
3447 // We ignore any leading tabs
3448 editor.join_lines(&JoinLines, window, cx);
3449 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3450
3451 editor
3452 });
3453}
3454
3455#[gpui::test]
3456fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 cx.add_window(|window, cx| {
3460 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3461 let mut editor = build_editor(buffer.clone(), window, cx);
3462 let buffer = buffer.read(cx).as_singleton().unwrap();
3463
3464 editor.change_selections(None, window, cx, |s| {
3465 s.select_ranges([
3466 Point::new(0, 2)..Point::new(1, 1),
3467 Point::new(1, 2)..Point::new(1, 2),
3468 Point::new(3, 1)..Point::new(3, 2),
3469 ])
3470 });
3471
3472 editor.join_lines(&JoinLines, window, cx);
3473 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3474
3475 assert_eq!(
3476 editor.selections.ranges::<Point>(cx),
3477 [
3478 Point::new(0, 7)..Point::new(0, 7),
3479 Point::new(1, 3)..Point::new(1, 3)
3480 ]
3481 );
3482 editor
3483 });
3484}
3485
3486#[gpui::test]
3487async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3488 init_test(cx, |_| {});
3489
3490 let mut cx = EditorTestContext::new(cx).await;
3491
3492 let diff_base = r#"
3493 Line 0
3494 Line 1
3495 Line 2
3496 Line 3
3497 "#
3498 .unindent();
3499
3500 cx.set_state(
3501 &r#"
3502 ˇLine 0
3503 Line 1
3504 Line 2
3505 Line 3
3506 "#
3507 .unindent(),
3508 );
3509
3510 cx.set_head_text(&diff_base);
3511 executor.run_until_parked();
3512
3513 // Join lines
3514 cx.update_editor(|editor, window, cx| {
3515 editor.join_lines(&JoinLines, window, cx);
3516 });
3517 executor.run_until_parked();
3518
3519 cx.assert_editor_state(
3520 &r#"
3521 Line 0ˇ Line 1
3522 Line 2
3523 Line 3
3524 "#
3525 .unindent(),
3526 );
3527 // Join again
3528 cx.update_editor(|editor, window, cx| {
3529 editor.join_lines(&JoinLines, window, cx);
3530 });
3531 executor.run_until_parked();
3532
3533 cx.assert_editor_state(
3534 &r#"
3535 Line 0 Line 1ˇ Line 2
3536 Line 3
3537 "#
3538 .unindent(),
3539 );
3540}
3541
3542#[gpui::test]
3543async fn test_custom_newlines_cause_no_false_positive_diffs(
3544 executor: BackgroundExecutor,
3545 cx: &mut TestAppContext,
3546) {
3547 init_test(cx, |_| {});
3548 let mut cx = EditorTestContext::new(cx).await;
3549 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3550 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3551 executor.run_until_parked();
3552
3553 cx.update_editor(|editor, window, cx| {
3554 let snapshot = editor.snapshot(window, cx);
3555 assert_eq!(
3556 snapshot
3557 .buffer_snapshot
3558 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3559 .collect::<Vec<_>>(),
3560 Vec::new(),
3561 "Should not have any diffs for files with custom newlines"
3562 );
3563 });
3564}
3565
3566#[gpui::test]
3567async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let mut cx = EditorTestContext::new(cx).await;
3571
3572 // Test sort_lines_case_insensitive()
3573 cx.set_state(indoc! {"
3574 «z
3575 y
3576 x
3577 Z
3578 Y
3579 Xˇ»
3580 "});
3581 cx.update_editor(|e, window, cx| {
3582 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3583 });
3584 cx.assert_editor_state(indoc! {"
3585 «x
3586 X
3587 y
3588 Y
3589 z
3590 Zˇ»
3591 "});
3592
3593 // Test reverse_lines()
3594 cx.set_state(indoc! {"
3595 «5
3596 4
3597 3
3598 2
3599 1ˇ»
3600 "});
3601 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3602 cx.assert_editor_state(indoc! {"
3603 «1
3604 2
3605 3
3606 4
3607 5ˇ»
3608 "});
3609
3610 // Skip testing shuffle_line()
3611
3612 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3613 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3614
3615 // Don't manipulate when cursor is on single line, but expand the selection
3616 cx.set_state(indoc! {"
3617 ddˇdd
3618 ccc
3619 bb
3620 a
3621 "});
3622 cx.update_editor(|e, window, cx| {
3623 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3624 });
3625 cx.assert_editor_state(indoc! {"
3626 «ddddˇ»
3627 ccc
3628 bb
3629 a
3630 "});
3631
3632 // Basic manipulate case
3633 // Start selection moves to column 0
3634 // End of selection shrinks to fit shorter line
3635 cx.set_state(indoc! {"
3636 dd«d
3637 ccc
3638 bb
3639 aaaaaˇ»
3640 "});
3641 cx.update_editor(|e, window, cx| {
3642 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3643 });
3644 cx.assert_editor_state(indoc! {"
3645 «aaaaa
3646 bb
3647 ccc
3648 dddˇ»
3649 "});
3650
3651 // Manipulate case with newlines
3652 cx.set_state(indoc! {"
3653 dd«d
3654 ccc
3655
3656 bb
3657 aaaaa
3658
3659 ˇ»
3660 "});
3661 cx.update_editor(|e, window, cx| {
3662 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3663 });
3664 cx.assert_editor_state(indoc! {"
3665 «
3666
3667 aaaaa
3668 bb
3669 ccc
3670 dddˇ»
3671
3672 "});
3673
3674 // Adding new line
3675 cx.set_state(indoc! {"
3676 aa«a
3677 bbˇ»b
3678 "});
3679 cx.update_editor(|e, window, cx| {
3680 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3681 });
3682 cx.assert_editor_state(indoc! {"
3683 «aaa
3684 bbb
3685 added_lineˇ»
3686 "});
3687
3688 // Removing line
3689 cx.set_state(indoc! {"
3690 aa«a
3691 bbbˇ»
3692 "});
3693 cx.update_editor(|e, window, cx| {
3694 e.manipulate_lines(window, cx, |lines| {
3695 lines.pop();
3696 })
3697 });
3698 cx.assert_editor_state(indoc! {"
3699 «aaaˇ»
3700 "});
3701
3702 // Removing all lines
3703 cx.set_state(indoc! {"
3704 aa«a
3705 bbbˇ»
3706 "});
3707 cx.update_editor(|e, window, cx| {
3708 e.manipulate_lines(window, cx, |lines| {
3709 lines.drain(..);
3710 })
3711 });
3712 cx.assert_editor_state(indoc! {"
3713 ˇ
3714 "});
3715}
3716
3717#[gpui::test]
3718async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3719 init_test(cx, |_| {});
3720
3721 let mut cx = EditorTestContext::new(cx).await;
3722
3723 // Consider continuous selection as single selection
3724 cx.set_state(indoc! {"
3725 Aaa«aa
3726 cˇ»c«c
3727 bb
3728 aaaˇ»aa
3729 "});
3730 cx.update_editor(|e, window, cx| {
3731 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3732 });
3733 cx.assert_editor_state(indoc! {"
3734 «Aaaaa
3735 ccc
3736 bb
3737 aaaaaˇ»
3738 "});
3739
3740 cx.set_state(indoc! {"
3741 Aaa«aa
3742 cˇ»c«c
3743 bb
3744 aaaˇ»aa
3745 "});
3746 cx.update_editor(|e, window, cx| {
3747 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3748 });
3749 cx.assert_editor_state(indoc! {"
3750 «Aaaaa
3751 ccc
3752 bbˇ»
3753 "});
3754
3755 // Consider non continuous selection as distinct dedup operations
3756 cx.set_state(indoc! {"
3757 «aaaaa
3758 bb
3759 aaaaa
3760 aaaaaˇ»
3761
3762 aaa«aaˇ»
3763 "});
3764 cx.update_editor(|e, window, cx| {
3765 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3766 });
3767 cx.assert_editor_state(indoc! {"
3768 «aaaaa
3769 bbˇ»
3770
3771 «aaaaaˇ»
3772 "});
3773}
3774
3775#[gpui::test]
3776async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3777 init_test(cx, |_| {});
3778
3779 let mut cx = EditorTestContext::new(cx).await;
3780
3781 cx.set_state(indoc! {"
3782 «Aaa
3783 aAa
3784 Aaaˇ»
3785 "});
3786 cx.update_editor(|e, window, cx| {
3787 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3788 });
3789 cx.assert_editor_state(indoc! {"
3790 «Aaa
3791 aAaˇ»
3792 "});
3793
3794 cx.set_state(indoc! {"
3795 «Aaa
3796 aAa
3797 aaAˇ»
3798 "});
3799 cx.update_editor(|e, window, cx| {
3800 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3801 });
3802 cx.assert_editor_state(indoc! {"
3803 «Aaaˇ»
3804 "});
3805}
3806
3807#[gpui::test]
3808async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3809 init_test(cx, |_| {});
3810
3811 let mut cx = EditorTestContext::new(cx).await;
3812
3813 // Manipulate with multiple selections on a single line
3814 cx.set_state(indoc! {"
3815 dd«dd
3816 cˇ»c«c
3817 bb
3818 aaaˇ»aa
3819 "});
3820 cx.update_editor(|e, window, cx| {
3821 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3822 });
3823 cx.assert_editor_state(indoc! {"
3824 «aaaaa
3825 bb
3826 ccc
3827 ddddˇ»
3828 "});
3829
3830 // Manipulate with multiple disjoin selections
3831 cx.set_state(indoc! {"
3832 5«
3833 4
3834 3
3835 2
3836 1ˇ»
3837
3838 dd«dd
3839 ccc
3840 bb
3841 aaaˇ»aa
3842 "});
3843 cx.update_editor(|e, window, cx| {
3844 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3845 });
3846 cx.assert_editor_state(indoc! {"
3847 «1
3848 2
3849 3
3850 4
3851 5ˇ»
3852
3853 «aaaaa
3854 bb
3855 ccc
3856 ddddˇ»
3857 "});
3858
3859 // Adding lines on each selection
3860 cx.set_state(indoc! {"
3861 2«
3862 1ˇ»
3863
3864 bb«bb
3865 aaaˇ»aa
3866 "});
3867 cx.update_editor(|e, window, cx| {
3868 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3869 });
3870 cx.assert_editor_state(indoc! {"
3871 «2
3872 1
3873 added lineˇ»
3874
3875 «bbbb
3876 aaaaa
3877 added lineˇ»
3878 "});
3879
3880 // Removing lines on each selection
3881 cx.set_state(indoc! {"
3882 2«
3883 1ˇ»
3884
3885 bb«bb
3886 aaaˇ»aa
3887 "});
3888 cx.update_editor(|e, window, cx| {
3889 e.manipulate_lines(window, cx, |lines| {
3890 lines.pop();
3891 })
3892 });
3893 cx.assert_editor_state(indoc! {"
3894 «2ˇ»
3895
3896 «bbbbˇ»
3897 "});
3898}
3899
3900#[gpui::test]
3901async fn test_manipulate_text(cx: &mut TestAppContext) {
3902 init_test(cx, |_| {});
3903
3904 let mut cx = EditorTestContext::new(cx).await;
3905
3906 // Test convert_to_upper_case()
3907 cx.set_state(indoc! {"
3908 «hello worldˇ»
3909 "});
3910 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3911 cx.assert_editor_state(indoc! {"
3912 «HELLO WORLDˇ»
3913 "});
3914
3915 // Test convert_to_lower_case()
3916 cx.set_state(indoc! {"
3917 «HELLO WORLDˇ»
3918 "});
3919 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3920 cx.assert_editor_state(indoc! {"
3921 «hello worldˇ»
3922 "});
3923
3924 // Test multiple line, single selection case
3925 cx.set_state(indoc! {"
3926 «The quick brown
3927 fox jumps over
3928 the lazy dogˇ»
3929 "});
3930 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3931 cx.assert_editor_state(indoc! {"
3932 «The Quick Brown
3933 Fox Jumps Over
3934 The Lazy Dogˇ»
3935 "});
3936
3937 // Test multiple line, single selection case
3938 cx.set_state(indoc! {"
3939 «The quick brown
3940 fox jumps over
3941 the lazy dogˇ»
3942 "});
3943 cx.update_editor(|e, window, cx| {
3944 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3945 });
3946 cx.assert_editor_state(indoc! {"
3947 «TheQuickBrown
3948 FoxJumpsOver
3949 TheLazyDogˇ»
3950 "});
3951
3952 // From here on out, test more complex cases of manipulate_text()
3953
3954 // Test no selection case - should affect words cursors are in
3955 // Cursor at beginning, middle, and end of word
3956 cx.set_state(indoc! {"
3957 ˇhello big beauˇtiful worldˇ
3958 "});
3959 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3960 cx.assert_editor_state(indoc! {"
3961 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3962 "});
3963
3964 // Test multiple selections on a single line and across multiple lines
3965 cx.set_state(indoc! {"
3966 «Theˇ» quick «brown
3967 foxˇ» jumps «overˇ»
3968 the «lazyˇ» dog
3969 "});
3970 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3971 cx.assert_editor_state(indoc! {"
3972 «THEˇ» quick «BROWN
3973 FOXˇ» jumps «OVERˇ»
3974 the «LAZYˇ» dog
3975 "});
3976
3977 // Test case where text length grows
3978 cx.set_state(indoc! {"
3979 «tschüߡ»
3980 "});
3981 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3982 cx.assert_editor_state(indoc! {"
3983 «TSCHÜSSˇ»
3984 "});
3985
3986 // Test to make sure we don't crash when text shrinks
3987 cx.set_state(indoc! {"
3988 aaa_bbbˇ
3989 "});
3990 cx.update_editor(|e, window, cx| {
3991 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3992 });
3993 cx.assert_editor_state(indoc! {"
3994 «aaaBbbˇ»
3995 "});
3996
3997 // Test to make sure we all aware of the fact that each word can grow and shrink
3998 // Final selections should be aware of this fact
3999 cx.set_state(indoc! {"
4000 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4001 "});
4002 cx.update_editor(|e, window, cx| {
4003 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4004 });
4005 cx.assert_editor_state(indoc! {"
4006 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4007 "});
4008
4009 cx.set_state(indoc! {"
4010 «hElLo, WoRld!ˇ»
4011 "});
4012 cx.update_editor(|e, window, cx| {
4013 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4014 });
4015 cx.assert_editor_state(indoc! {"
4016 «HeLlO, wOrLD!ˇ»
4017 "});
4018}
4019
4020#[gpui::test]
4021fn test_duplicate_line(cx: &mut TestAppContext) {
4022 init_test(cx, |_| {});
4023
4024 let editor = cx.add_window(|window, cx| {
4025 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4026 build_editor(buffer, window, cx)
4027 });
4028 _ = editor.update(cx, |editor, window, cx| {
4029 editor.change_selections(None, window, cx, |s| {
4030 s.select_display_ranges([
4031 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4032 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4033 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4034 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4035 ])
4036 });
4037 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4038 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4039 assert_eq!(
4040 editor.selections.display_ranges(cx),
4041 vec![
4042 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4043 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4044 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4045 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4046 ]
4047 );
4048 });
4049
4050 let editor = cx.add_window(|window, cx| {
4051 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4052 build_editor(buffer, window, cx)
4053 });
4054 _ = editor.update(cx, |editor, window, cx| {
4055 editor.change_selections(None, window, cx, |s| {
4056 s.select_display_ranges([
4057 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4058 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4059 ])
4060 });
4061 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4062 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4063 assert_eq!(
4064 editor.selections.display_ranges(cx),
4065 vec![
4066 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4067 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4068 ]
4069 );
4070 });
4071
4072 // With `move_upwards` the selections stay in place, except for
4073 // the lines inserted above them
4074 let editor = cx.add_window(|window, cx| {
4075 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4076 build_editor(buffer, window, cx)
4077 });
4078 _ = editor.update(cx, |editor, window, cx| {
4079 editor.change_selections(None, window, cx, |s| {
4080 s.select_display_ranges([
4081 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4082 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4083 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4084 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4085 ])
4086 });
4087 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4088 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4089 assert_eq!(
4090 editor.selections.display_ranges(cx),
4091 vec![
4092 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4093 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4094 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4095 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4096 ]
4097 );
4098 });
4099
4100 let editor = cx.add_window(|window, cx| {
4101 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4102 build_editor(buffer, window, cx)
4103 });
4104 _ = editor.update(cx, |editor, window, cx| {
4105 editor.change_selections(None, window, cx, |s| {
4106 s.select_display_ranges([
4107 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4108 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4109 ])
4110 });
4111 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4112 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4113 assert_eq!(
4114 editor.selections.display_ranges(cx),
4115 vec![
4116 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4117 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4118 ]
4119 );
4120 });
4121
4122 let editor = cx.add_window(|window, cx| {
4123 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4124 build_editor(buffer, window, cx)
4125 });
4126 _ = editor.update(cx, |editor, window, cx| {
4127 editor.change_selections(None, window, cx, |s| {
4128 s.select_display_ranges([
4129 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4130 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4131 ])
4132 });
4133 editor.duplicate_selection(&DuplicateSelection, window, cx);
4134 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4135 assert_eq!(
4136 editor.selections.display_ranges(cx),
4137 vec![
4138 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4139 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4140 ]
4141 );
4142 });
4143}
4144
4145#[gpui::test]
4146fn test_move_line_up_down(cx: &mut TestAppContext) {
4147 init_test(cx, |_| {});
4148
4149 let editor = cx.add_window(|window, cx| {
4150 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4151 build_editor(buffer, window, cx)
4152 });
4153 _ = editor.update(cx, |editor, window, cx| {
4154 editor.fold_creases(
4155 vec![
4156 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4158 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4159 ],
4160 true,
4161 window,
4162 cx,
4163 );
4164 editor.change_selections(None, window, cx, |s| {
4165 s.select_display_ranges([
4166 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4167 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4168 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4169 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4170 ])
4171 });
4172 assert_eq!(
4173 editor.display_text(cx),
4174 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4175 );
4176
4177 editor.move_line_up(&MoveLineUp, window, cx);
4178 assert_eq!(
4179 editor.display_text(cx),
4180 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4181 );
4182 assert_eq!(
4183 editor.selections.display_ranges(cx),
4184 vec![
4185 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4186 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4187 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4188 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4189 ]
4190 );
4191 });
4192
4193 _ = editor.update(cx, |editor, window, cx| {
4194 editor.move_line_down(&MoveLineDown, window, cx);
4195 assert_eq!(
4196 editor.display_text(cx),
4197 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4198 );
4199 assert_eq!(
4200 editor.selections.display_ranges(cx),
4201 vec![
4202 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4203 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4204 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4206 ]
4207 );
4208 });
4209
4210 _ = editor.update(cx, |editor, window, cx| {
4211 editor.move_line_down(&MoveLineDown, window, cx);
4212 assert_eq!(
4213 editor.display_text(cx),
4214 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4215 );
4216 assert_eq!(
4217 editor.selections.display_ranges(cx),
4218 vec![
4219 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4220 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4221 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4222 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4223 ]
4224 );
4225 });
4226
4227 _ = editor.update(cx, |editor, window, cx| {
4228 editor.move_line_up(&MoveLineUp, window, cx);
4229 assert_eq!(
4230 editor.display_text(cx),
4231 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4232 );
4233 assert_eq!(
4234 editor.selections.display_ranges(cx),
4235 vec![
4236 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4237 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4238 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4239 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4240 ]
4241 );
4242 });
4243}
4244
4245#[gpui::test]
4246fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4247 init_test(cx, |_| {});
4248
4249 let editor = cx.add_window(|window, cx| {
4250 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4251 build_editor(buffer, window, cx)
4252 });
4253 _ = editor.update(cx, |editor, window, cx| {
4254 let snapshot = editor.buffer.read(cx).snapshot(cx);
4255 editor.insert_blocks(
4256 [BlockProperties {
4257 style: BlockStyle::Fixed,
4258 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4259 height: 1,
4260 render: Arc::new(|_| div().into_any()),
4261 priority: 0,
4262 }],
4263 Some(Autoscroll::fit()),
4264 cx,
4265 );
4266 editor.change_selections(None, window, cx, |s| {
4267 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4268 });
4269 editor.move_line_down(&MoveLineDown, window, cx);
4270 });
4271}
4272
4273#[gpui::test]
4274async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4275 init_test(cx, |_| {});
4276
4277 let mut cx = EditorTestContext::new(cx).await;
4278 cx.set_state(
4279 &"
4280 ˇzero
4281 one
4282 two
4283 three
4284 four
4285 five
4286 "
4287 .unindent(),
4288 );
4289
4290 // Create a four-line block that replaces three lines of text.
4291 cx.update_editor(|editor, window, cx| {
4292 let snapshot = editor.snapshot(window, cx);
4293 let snapshot = &snapshot.buffer_snapshot;
4294 let placement = BlockPlacement::Replace(
4295 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4296 );
4297 editor.insert_blocks(
4298 [BlockProperties {
4299 placement,
4300 height: 4,
4301 style: BlockStyle::Sticky,
4302 render: Arc::new(|_| gpui::div().into_any_element()),
4303 priority: 0,
4304 }],
4305 None,
4306 cx,
4307 );
4308 });
4309
4310 // Move down so that the cursor touches the block.
4311 cx.update_editor(|editor, window, cx| {
4312 editor.move_down(&Default::default(), window, cx);
4313 });
4314 cx.assert_editor_state(
4315 &"
4316 zero
4317 «one
4318 two
4319 threeˇ»
4320 four
4321 five
4322 "
4323 .unindent(),
4324 );
4325
4326 // Move down past the block.
4327 cx.update_editor(|editor, window, cx| {
4328 editor.move_down(&Default::default(), window, cx);
4329 });
4330 cx.assert_editor_state(
4331 &"
4332 zero
4333 one
4334 two
4335 three
4336 ˇfour
4337 five
4338 "
4339 .unindent(),
4340 );
4341}
4342
4343#[gpui::test]
4344fn test_transpose(cx: &mut TestAppContext) {
4345 init_test(cx, |_| {});
4346
4347 _ = cx.add_window(|window, cx| {
4348 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4349 editor.set_style(EditorStyle::default(), window, cx);
4350 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4351 editor.transpose(&Default::default(), window, cx);
4352 assert_eq!(editor.text(cx), "bac");
4353 assert_eq!(editor.selections.ranges(cx), [2..2]);
4354
4355 editor.transpose(&Default::default(), window, cx);
4356 assert_eq!(editor.text(cx), "bca");
4357 assert_eq!(editor.selections.ranges(cx), [3..3]);
4358
4359 editor.transpose(&Default::default(), window, cx);
4360 assert_eq!(editor.text(cx), "bac");
4361 assert_eq!(editor.selections.ranges(cx), [3..3]);
4362
4363 editor
4364 });
4365
4366 _ = cx.add_window(|window, cx| {
4367 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4368 editor.set_style(EditorStyle::default(), window, cx);
4369 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4370 editor.transpose(&Default::default(), window, cx);
4371 assert_eq!(editor.text(cx), "acb\nde");
4372 assert_eq!(editor.selections.ranges(cx), [3..3]);
4373
4374 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4375 editor.transpose(&Default::default(), window, cx);
4376 assert_eq!(editor.text(cx), "acbd\ne");
4377 assert_eq!(editor.selections.ranges(cx), [5..5]);
4378
4379 editor.transpose(&Default::default(), window, cx);
4380 assert_eq!(editor.text(cx), "acbde\n");
4381 assert_eq!(editor.selections.ranges(cx), [6..6]);
4382
4383 editor.transpose(&Default::default(), window, cx);
4384 assert_eq!(editor.text(cx), "acbd\ne");
4385 assert_eq!(editor.selections.ranges(cx), [6..6]);
4386
4387 editor
4388 });
4389
4390 _ = cx.add_window(|window, cx| {
4391 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4392 editor.set_style(EditorStyle::default(), window, cx);
4393 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4394 editor.transpose(&Default::default(), window, cx);
4395 assert_eq!(editor.text(cx), "bacd\ne");
4396 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4397
4398 editor.transpose(&Default::default(), window, cx);
4399 assert_eq!(editor.text(cx), "bcade\n");
4400 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4401
4402 editor.transpose(&Default::default(), window, cx);
4403 assert_eq!(editor.text(cx), "bcda\ne");
4404 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4405
4406 editor.transpose(&Default::default(), window, cx);
4407 assert_eq!(editor.text(cx), "bcade\n");
4408 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4409
4410 editor.transpose(&Default::default(), window, cx);
4411 assert_eq!(editor.text(cx), "bcaed\n");
4412 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4413
4414 editor
4415 });
4416
4417 _ = cx.add_window(|window, cx| {
4418 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4419 editor.set_style(EditorStyle::default(), window, cx);
4420 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4421 editor.transpose(&Default::default(), window, cx);
4422 assert_eq!(editor.text(cx), "🏀🍐✋");
4423 assert_eq!(editor.selections.ranges(cx), [8..8]);
4424
4425 editor.transpose(&Default::default(), window, cx);
4426 assert_eq!(editor.text(cx), "🏀✋🍐");
4427 assert_eq!(editor.selections.ranges(cx), [11..11]);
4428
4429 editor.transpose(&Default::default(), window, cx);
4430 assert_eq!(editor.text(cx), "🏀🍐✋");
4431 assert_eq!(editor.selections.ranges(cx), [11..11]);
4432
4433 editor
4434 });
4435}
4436
4437#[gpui::test]
4438async fn test_rewrap(cx: &mut TestAppContext) {
4439 init_test(cx, |settings| {
4440 settings.languages.extend([
4441 (
4442 "Markdown".into(),
4443 LanguageSettingsContent {
4444 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4445 ..Default::default()
4446 },
4447 ),
4448 (
4449 "Plain Text".into(),
4450 LanguageSettingsContent {
4451 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4452 ..Default::default()
4453 },
4454 ),
4455 ])
4456 });
4457
4458 let mut cx = EditorTestContext::new(cx).await;
4459
4460 let language_with_c_comments = Arc::new(Language::new(
4461 LanguageConfig {
4462 line_comments: vec!["// ".into()],
4463 ..LanguageConfig::default()
4464 },
4465 None,
4466 ));
4467 let language_with_pound_comments = Arc::new(Language::new(
4468 LanguageConfig {
4469 line_comments: vec!["# ".into()],
4470 ..LanguageConfig::default()
4471 },
4472 None,
4473 ));
4474 let markdown_language = Arc::new(Language::new(
4475 LanguageConfig {
4476 name: "Markdown".into(),
4477 ..LanguageConfig::default()
4478 },
4479 None,
4480 ));
4481 let language_with_doc_comments = Arc::new(Language::new(
4482 LanguageConfig {
4483 line_comments: vec!["// ".into(), "/// ".into()],
4484 ..LanguageConfig::default()
4485 },
4486 Some(tree_sitter_rust::LANGUAGE.into()),
4487 ));
4488
4489 let plaintext_language = Arc::new(Language::new(
4490 LanguageConfig {
4491 name: "Plain Text".into(),
4492 ..LanguageConfig::default()
4493 },
4494 None,
4495 ));
4496
4497 assert_rewrap(
4498 indoc! {"
4499 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4500 "},
4501 indoc! {"
4502 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4503 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4504 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4505 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4506 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4507 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4508 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4509 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4510 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4511 // porttitor id. Aliquam id accumsan eros.
4512 "},
4513 language_with_c_comments.clone(),
4514 &mut cx,
4515 );
4516
4517 // Test that rewrapping works inside of a selection
4518 assert_rewrap(
4519 indoc! {"
4520 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4521 "},
4522 indoc! {"
4523 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4524 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4525 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4526 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4527 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4528 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4529 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4530 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4531 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4532 // porttitor id. Aliquam id accumsan eros.ˇ»
4533 "},
4534 language_with_c_comments.clone(),
4535 &mut cx,
4536 );
4537
4538 // Test that cursors that expand to the same region are collapsed.
4539 assert_rewrap(
4540 indoc! {"
4541 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4542 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4543 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4544 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4545 "},
4546 indoc! {"
4547 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4548 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4549 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4550 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4551 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4552 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4553 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4554 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4555 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4556 // porttitor id. Aliquam id accumsan eros.
4557 "},
4558 language_with_c_comments.clone(),
4559 &mut cx,
4560 );
4561
4562 // Test that non-contiguous selections are treated separately.
4563 assert_rewrap(
4564 indoc! {"
4565 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4566 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4567 //
4568 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4569 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4570 "},
4571 indoc! {"
4572 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4573 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4574 // auctor, eu lacinia sapien scelerisque.
4575 //
4576 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4577 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4578 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4579 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4580 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4581 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4582 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4583 "},
4584 language_with_c_comments.clone(),
4585 &mut cx,
4586 );
4587
4588 // Test that different comment prefixes are supported.
4589 assert_rewrap(
4590 indoc! {"
4591 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4592 "},
4593 indoc! {"
4594 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4595 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4596 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4597 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4598 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4599 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4600 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4601 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4602 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4603 # accumsan eros.
4604 "},
4605 language_with_pound_comments.clone(),
4606 &mut cx,
4607 );
4608
4609 // Test that rewrapping is ignored outside of comments in most languages.
4610 assert_rewrap(
4611 indoc! {"
4612 /// Adds two numbers.
4613 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4614 fn add(a: u32, b: u32) -> u32 {
4615 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4616 }
4617 "},
4618 indoc! {"
4619 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4620 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4621 fn add(a: u32, b: u32) -> u32 {
4622 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4623 }
4624 "},
4625 language_with_doc_comments.clone(),
4626 &mut cx,
4627 );
4628
4629 // Test that rewrapping works in Markdown and Plain Text languages.
4630 assert_rewrap(
4631 indoc! {"
4632 # Hello
4633
4634 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4635 "},
4636 indoc! {"
4637 # Hello
4638
4639 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4640 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4641 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4642 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4643 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4644 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4645 Integer sit amet scelerisque nisi.
4646 "},
4647 markdown_language,
4648 &mut cx,
4649 );
4650
4651 assert_rewrap(
4652 indoc! {"
4653 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4654 "},
4655 indoc! {"
4656 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4657 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4658 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4659 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4660 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4661 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4662 Integer sit amet scelerisque nisi.
4663 "},
4664 plaintext_language,
4665 &mut cx,
4666 );
4667
4668 // Test rewrapping unaligned comments in a selection.
4669 assert_rewrap(
4670 indoc! {"
4671 fn foo() {
4672 if true {
4673 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4674 // Praesent semper egestas tellus id dignissim.ˇ»
4675 do_something();
4676 } else {
4677 //
4678 }
4679 }
4680 "},
4681 indoc! {"
4682 fn foo() {
4683 if true {
4684 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4685 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4686 // egestas tellus id dignissim.ˇ»
4687 do_something();
4688 } else {
4689 //
4690 }
4691 }
4692 "},
4693 language_with_doc_comments.clone(),
4694 &mut cx,
4695 );
4696
4697 assert_rewrap(
4698 indoc! {"
4699 fn foo() {
4700 if true {
4701 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4702 // Praesent semper egestas tellus id dignissim.»
4703 do_something();
4704 } else {
4705 //
4706 }
4707
4708 }
4709 "},
4710 indoc! {"
4711 fn foo() {
4712 if true {
4713 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4714 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4715 // egestas tellus id dignissim.»
4716 do_something();
4717 } else {
4718 //
4719 }
4720
4721 }
4722 "},
4723 language_with_doc_comments.clone(),
4724 &mut cx,
4725 );
4726
4727 #[track_caller]
4728 fn assert_rewrap(
4729 unwrapped_text: &str,
4730 wrapped_text: &str,
4731 language: Arc<Language>,
4732 cx: &mut EditorTestContext,
4733 ) {
4734 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4735 cx.set_state(unwrapped_text);
4736 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4737 cx.assert_editor_state(wrapped_text);
4738 }
4739}
4740
4741#[gpui::test]
4742async fn test_hard_wrap(cx: &mut TestAppContext) {
4743 init_test(cx, |_| {});
4744 let mut cx = EditorTestContext::new(cx).await;
4745
4746 cx.update_editor(|editor, _, cx| {
4747 editor.set_hard_wrap(Some(14), cx);
4748 });
4749
4750 cx.set_state(indoc!(
4751 "
4752 one two three ˇ
4753 "
4754 ));
4755 cx.simulate_input("four");
4756 cx.run_until_parked();
4757
4758 cx.assert_editor_state(indoc!(
4759 "
4760 one two three
4761 fourˇ
4762 "
4763 ));
4764}
4765
4766#[gpui::test]
4767async fn test_clipboard(cx: &mut TestAppContext) {
4768 init_test(cx, |_| {});
4769
4770 let mut cx = EditorTestContext::new(cx).await;
4771
4772 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4773 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4774 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4775
4776 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4777 cx.set_state("two ˇfour ˇsix ˇ");
4778 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4779 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4780
4781 // Paste again but with only two cursors. Since the number of cursors doesn't
4782 // match the number of slices in the clipboard, the entire clipboard text
4783 // is pasted at each cursor.
4784 cx.set_state("ˇtwo one✅ four three six five ˇ");
4785 cx.update_editor(|e, window, cx| {
4786 e.handle_input("( ", window, cx);
4787 e.paste(&Paste, window, cx);
4788 e.handle_input(") ", window, cx);
4789 });
4790 cx.assert_editor_state(
4791 &([
4792 "( one✅ ",
4793 "three ",
4794 "five ) ˇtwo one✅ four three six five ( one✅ ",
4795 "three ",
4796 "five ) ˇ",
4797 ]
4798 .join("\n")),
4799 );
4800
4801 // Cut with three selections, one of which is full-line.
4802 cx.set_state(indoc! {"
4803 1«2ˇ»3
4804 4ˇ567
4805 «8ˇ»9"});
4806 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4807 cx.assert_editor_state(indoc! {"
4808 1ˇ3
4809 ˇ9"});
4810
4811 // Paste with three selections, noticing how the copied selection that was full-line
4812 // gets inserted before the second cursor.
4813 cx.set_state(indoc! {"
4814 1ˇ3
4815 9ˇ
4816 «oˇ»ne"});
4817 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4818 cx.assert_editor_state(indoc! {"
4819 12ˇ3
4820 4567
4821 9ˇ
4822 8ˇne"});
4823
4824 // Copy with a single cursor only, which writes the whole line into the clipboard.
4825 cx.set_state(indoc! {"
4826 The quick brown
4827 fox juˇmps over
4828 the lazy dog"});
4829 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4830 assert_eq!(
4831 cx.read_from_clipboard()
4832 .and_then(|item| item.text().as_deref().map(str::to_string)),
4833 Some("fox jumps over\n".to_string())
4834 );
4835
4836 // Paste with three selections, noticing how the copied full-line selection is inserted
4837 // before the empty selections but replaces the selection that is non-empty.
4838 cx.set_state(indoc! {"
4839 Tˇhe quick brown
4840 «foˇ»x jumps over
4841 tˇhe lazy dog"});
4842 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4843 cx.assert_editor_state(indoc! {"
4844 fox jumps over
4845 Tˇhe quick brown
4846 fox jumps over
4847 ˇx jumps over
4848 fox jumps over
4849 tˇhe lazy dog"});
4850}
4851
4852#[gpui::test]
4853async fn test_paste_multiline(cx: &mut TestAppContext) {
4854 init_test(cx, |_| {});
4855
4856 let mut cx = EditorTestContext::new(cx).await;
4857 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4858
4859 // Cut an indented block, without the leading whitespace.
4860 cx.set_state(indoc! {"
4861 const a: B = (
4862 c(),
4863 «d(
4864 e,
4865 f
4866 )ˇ»
4867 );
4868 "});
4869 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4870 cx.assert_editor_state(indoc! {"
4871 const a: B = (
4872 c(),
4873 ˇ
4874 );
4875 "});
4876
4877 // Paste it at the same position.
4878 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4879 cx.assert_editor_state(indoc! {"
4880 const a: B = (
4881 c(),
4882 d(
4883 e,
4884 f
4885 )ˇ
4886 );
4887 "});
4888
4889 // Paste it at a line with a lower indent level.
4890 cx.set_state(indoc! {"
4891 ˇ
4892 const a: B = (
4893 c(),
4894 );
4895 "});
4896 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4897 cx.assert_editor_state(indoc! {"
4898 d(
4899 e,
4900 f
4901 )ˇ
4902 const a: B = (
4903 c(),
4904 );
4905 "});
4906
4907 // Cut an indented block, with the leading whitespace.
4908 cx.set_state(indoc! {"
4909 const a: B = (
4910 c(),
4911 « d(
4912 e,
4913 f
4914 )
4915 ˇ»);
4916 "});
4917 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4918 cx.assert_editor_state(indoc! {"
4919 const a: B = (
4920 c(),
4921 ˇ);
4922 "});
4923
4924 // Paste it at the same position.
4925 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4926 cx.assert_editor_state(indoc! {"
4927 const a: B = (
4928 c(),
4929 d(
4930 e,
4931 f
4932 )
4933 ˇ);
4934 "});
4935
4936 // Paste it at a line with a higher indent level.
4937 cx.set_state(indoc! {"
4938 const a: B = (
4939 c(),
4940 d(
4941 e,
4942 fˇ
4943 )
4944 );
4945 "});
4946 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4947 cx.assert_editor_state(indoc! {"
4948 const a: B = (
4949 c(),
4950 d(
4951 e,
4952 f d(
4953 e,
4954 f
4955 )
4956 ˇ
4957 )
4958 );
4959 "});
4960
4961 // Copy an indented block, starting mid-line
4962 cx.set_state(indoc! {"
4963 const a: B = (
4964 c(),
4965 somethin«g(
4966 e,
4967 f
4968 )ˇ»
4969 );
4970 "});
4971 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4972
4973 // Paste it on a line with a lower indent level
4974 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4975 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4976 cx.assert_editor_state(indoc! {"
4977 const a: B = (
4978 c(),
4979 something(
4980 e,
4981 f
4982 )
4983 );
4984 g(
4985 e,
4986 f
4987 )ˇ"});
4988}
4989
4990#[gpui::test]
4991async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4992 init_test(cx, |_| {});
4993
4994 cx.write_to_clipboard(ClipboardItem::new_string(
4995 " d(\n e\n );\n".into(),
4996 ));
4997
4998 let mut cx = EditorTestContext::new(cx).await;
4999 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5000
5001 cx.set_state(indoc! {"
5002 fn a() {
5003 b();
5004 if c() {
5005 ˇ
5006 }
5007 }
5008 "});
5009
5010 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5011 cx.assert_editor_state(indoc! {"
5012 fn a() {
5013 b();
5014 if c() {
5015 d(
5016 e
5017 );
5018 ˇ
5019 }
5020 }
5021 "});
5022
5023 cx.set_state(indoc! {"
5024 fn a() {
5025 b();
5026 ˇ
5027 }
5028 "});
5029
5030 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5031 cx.assert_editor_state(indoc! {"
5032 fn a() {
5033 b();
5034 d(
5035 e
5036 );
5037 ˇ
5038 }
5039 "});
5040}
5041
5042#[gpui::test]
5043fn test_select_all(cx: &mut TestAppContext) {
5044 init_test(cx, |_| {});
5045
5046 let editor = cx.add_window(|window, cx| {
5047 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5048 build_editor(buffer, window, cx)
5049 });
5050 _ = editor.update(cx, |editor, window, cx| {
5051 editor.select_all(&SelectAll, window, cx);
5052 assert_eq!(
5053 editor.selections.display_ranges(cx),
5054 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5055 );
5056 });
5057}
5058
5059#[gpui::test]
5060fn test_select_line(cx: &mut TestAppContext) {
5061 init_test(cx, |_| {});
5062
5063 let editor = cx.add_window(|window, cx| {
5064 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5065 build_editor(buffer, window, cx)
5066 });
5067 _ = editor.update(cx, |editor, window, cx| {
5068 editor.change_selections(None, window, cx, |s| {
5069 s.select_display_ranges([
5070 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5071 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5072 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5073 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5074 ])
5075 });
5076 editor.select_line(&SelectLine, window, cx);
5077 assert_eq!(
5078 editor.selections.display_ranges(cx),
5079 vec![
5080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5081 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5082 ]
5083 );
5084 });
5085
5086 _ = editor.update(cx, |editor, window, cx| {
5087 editor.select_line(&SelectLine, window, cx);
5088 assert_eq!(
5089 editor.selections.display_ranges(cx),
5090 vec![
5091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5092 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5093 ]
5094 );
5095 });
5096
5097 _ = editor.update(cx, |editor, window, cx| {
5098 editor.select_line(&SelectLine, window, cx);
5099 assert_eq!(
5100 editor.selections.display_ranges(cx),
5101 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5102 );
5103 });
5104}
5105
5106#[gpui::test]
5107async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5108 init_test(cx, |_| {});
5109 let mut cx = EditorTestContext::new(cx).await;
5110
5111 #[track_caller]
5112 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5113 cx.set_state(initial_state);
5114 cx.update_editor(|e, window, cx| {
5115 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5116 });
5117 cx.assert_editor_state(expected_state);
5118 }
5119
5120 // Selection starts and ends at the middle of lines, left-to-right
5121 test(
5122 &mut cx,
5123 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5124 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5125 );
5126 // Same thing, right-to-left
5127 test(
5128 &mut cx,
5129 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5130 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5131 );
5132
5133 // Whole buffer, left-to-right, last line *doesn't* end with newline
5134 test(
5135 &mut cx,
5136 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5137 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5138 );
5139 // Same thing, right-to-left
5140 test(
5141 &mut cx,
5142 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5143 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5144 );
5145
5146 // Whole buffer, left-to-right, last line ends with newline
5147 test(
5148 &mut cx,
5149 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5150 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5151 );
5152 // Same thing, right-to-left
5153 test(
5154 &mut cx,
5155 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5156 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5157 );
5158
5159 // Starts at the end of a line, ends at the start of another
5160 test(
5161 &mut cx,
5162 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5163 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let editor = cx.add_window(|window, cx| {
5172 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5173 build_editor(buffer, window, cx)
5174 });
5175
5176 // setup
5177 _ = editor.update(cx, |editor, window, cx| {
5178 editor.fold_creases(
5179 vec![
5180 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5181 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5182 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5183 ],
5184 true,
5185 window,
5186 cx,
5187 );
5188 assert_eq!(
5189 editor.display_text(cx),
5190 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5191 );
5192 });
5193
5194 _ = editor.update(cx, |editor, window, cx| {
5195 editor.change_selections(None, window, cx, |s| {
5196 s.select_display_ranges([
5197 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5198 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5199 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5200 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5201 ])
5202 });
5203 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5204 assert_eq!(
5205 editor.display_text(cx),
5206 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5207 );
5208 });
5209 EditorTestContext::for_editor(editor, cx)
5210 .await
5211 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5212
5213 _ = editor.update(cx, |editor, window, cx| {
5214 editor.change_selections(None, window, cx, |s| {
5215 s.select_display_ranges([
5216 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5217 ])
5218 });
5219 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5220 assert_eq!(
5221 editor.display_text(cx),
5222 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5223 );
5224 assert_eq!(
5225 editor.selections.display_ranges(cx),
5226 [
5227 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5228 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5229 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5230 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5231 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5232 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5233 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5234 ]
5235 );
5236 });
5237 EditorTestContext::for_editor(editor, cx)
5238 .await
5239 .assert_editor_state(
5240 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249
5250 cx.set_state(indoc!(
5251 r#"abc
5252 defˇghi
5253
5254 jk
5255 nlmo
5256 "#
5257 ));
5258
5259 cx.update_editor(|editor, window, cx| {
5260 editor.add_selection_above(&Default::default(), window, cx);
5261 });
5262
5263 cx.assert_editor_state(indoc!(
5264 r#"abcˇ
5265 defˇghi
5266
5267 jk
5268 nlmo
5269 "#
5270 ));
5271
5272 cx.update_editor(|editor, window, cx| {
5273 editor.add_selection_above(&Default::default(), window, cx);
5274 });
5275
5276 cx.assert_editor_state(indoc!(
5277 r#"abcˇ
5278 defˇghi
5279
5280 jk
5281 nlmo
5282 "#
5283 ));
5284
5285 cx.update_editor(|editor, window, cx| {
5286 editor.add_selection_below(&Default::default(), window, cx);
5287 });
5288
5289 cx.assert_editor_state(indoc!(
5290 r#"abc
5291 defˇghi
5292
5293 jk
5294 nlmo
5295 "#
5296 ));
5297
5298 cx.update_editor(|editor, window, cx| {
5299 editor.undo_selection(&Default::default(), window, cx);
5300 });
5301
5302 cx.assert_editor_state(indoc!(
5303 r#"abcˇ
5304 defˇghi
5305
5306 jk
5307 nlmo
5308 "#
5309 ));
5310
5311 cx.update_editor(|editor, window, cx| {
5312 editor.redo_selection(&Default::default(), window, cx);
5313 });
5314
5315 cx.assert_editor_state(indoc!(
5316 r#"abc
5317 defˇghi
5318
5319 jk
5320 nlmo
5321 "#
5322 ));
5323
5324 cx.update_editor(|editor, window, cx| {
5325 editor.add_selection_below(&Default::default(), window, cx);
5326 });
5327
5328 cx.assert_editor_state(indoc!(
5329 r#"abc
5330 defˇghi
5331
5332 jk
5333 nlmˇo
5334 "#
5335 ));
5336
5337 cx.update_editor(|editor, window, cx| {
5338 editor.add_selection_below(&Default::default(), window, cx);
5339 });
5340
5341 cx.assert_editor_state(indoc!(
5342 r#"abc
5343 defˇghi
5344
5345 jk
5346 nlmˇo
5347 "#
5348 ));
5349
5350 // change selections
5351 cx.set_state(indoc!(
5352 r#"abc
5353 def«ˇg»hi
5354
5355 jk
5356 nlmo
5357 "#
5358 ));
5359
5360 cx.update_editor(|editor, window, cx| {
5361 editor.add_selection_below(&Default::default(), window, cx);
5362 });
5363
5364 cx.assert_editor_state(indoc!(
5365 r#"abc
5366 def«ˇg»hi
5367
5368 jk
5369 nlm«ˇo»
5370 "#
5371 ));
5372
5373 cx.update_editor(|editor, window, cx| {
5374 editor.add_selection_below(&Default::default(), window, cx);
5375 });
5376
5377 cx.assert_editor_state(indoc!(
5378 r#"abc
5379 def«ˇg»hi
5380
5381 jk
5382 nlm«ˇo»
5383 "#
5384 ));
5385
5386 cx.update_editor(|editor, window, cx| {
5387 editor.add_selection_above(&Default::default(), window, cx);
5388 });
5389
5390 cx.assert_editor_state(indoc!(
5391 r#"abc
5392 def«ˇg»hi
5393
5394 jk
5395 nlmo
5396 "#
5397 ));
5398
5399 cx.update_editor(|editor, window, cx| {
5400 editor.add_selection_above(&Default::default(), window, cx);
5401 });
5402
5403 cx.assert_editor_state(indoc!(
5404 r#"abc
5405 def«ˇg»hi
5406
5407 jk
5408 nlmo
5409 "#
5410 ));
5411
5412 // Change selections again
5413 cx.set_state(indoc!(
5414 r#"a«bc
5415 defgˇ»hi
5416
5417 jk
5418 nlmo
5419 "#
5420 ));
5421
5422 cx.update_editor(|editor, window, cx| {
5423 editor.add_selection_below(&Default::default(), window, cx);
5424 });
5425
5426 cx.assert_editor_state(indoc!(
5427 r#"a«bcˇ»
5428 d«efgˇ»hi
5429
5430 j«kˇ»
5431 nlmo
5432 "#
5433 ));
5434
5435 cx.update_editor(|editor, window, cx| {
5436 editor.add_selection_below(&Default::default(), window, cx);
5437 });
5438 cx.assert_editor_state(indoc!(
5439 r#"a«bcˇ»
5440 d«efgˇ»hi
5441
5442 j«kˇ»
5443 n«lmoˇ»
5444 "#
5445 ));
5446 cx.update_editor(|editor, window, cx| {
5447 editor.add_selection_above(&Default::default(), window, cx);
5448 });
5449
5450 cx.assert_editor_state(indoc!(
5451 r#"a«bcˇ»
5452 d«efgˇ»hi
5453
5454 j«kˇ»
5455 nlmo
5456 "#
5457 ));
5458
5459 // Change selections again
5460 cx.set_state(indoc!(
5461 r#"abc
5462 d«ˇefghi
5463
5464 jk
5465 nlm»o
5466 "#
5467 ));
5468
5469 cx.update_editor(|editor, window, cx| {
5470 editor.add_selection_above(&Default::default(), window, cx);
5471 });
5472
5473 cx.assert_editor_state(indoc!(
5474 r#"a«ˇbc»
5475 d«ˇef»ghi
5476
5477 j«ˇk»
5478 n«ˇlm»o
5479 "#
5480 ));
5481
5482 cx.update_editor(|editor, window, cx| {
5483 editor.add_selection_below(&Default::default(), window, cx);
5484 });
5485
5486 cx.assert_editor_state(indoc!(
5487 r#"abc
5488 d«ˇef»ghi
5489
5490 j«ˇk»
5491 n«ˇlm»o
5492 "#
5493 ));
5494}
5495
5496#[gpui::test]
5497async fn test_select_next(cx: &mut TestAppContext) {
5498 init_test(cx, |_| {});
5499
5500 let mut cx = EditorTestContext::new(cx).await;
5501 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5502
5503 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5504 .unwrap();
5505 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5506
5507 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5508 .unwrap();
5509 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5510
5511 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5512 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5513
5514 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5515 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5516
5517 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5518 .unwrap();
5519 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5520
5521 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5522 .unwrap();
5523 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5524}
5525
5526#[gpui::test]
5527async fn test_select_all_matches(cx: &mut TestAppContext) {
5528 init_test(cx, |_| {});
5529
5530 let mut cx = EditorTestContext::new(cx).await;
5531
5532 // Test caret-only selections
5533 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5534 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5535 .unwrap();
5536 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5537
5538 // Test left-to-right selections
5539 cx.set_state("abc\n«abcˇ»\nabc");
5540 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5541 .unwrap();
5542 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5543
5544 // Test right-to-left selections
5545 cx.set_state("abc\n«ˇabc»\nabc");
5546 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5547 .unwrap();
5548 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5549
5550 // Test selecting whitespace with caret selection
5551 cx.set_state("abc\nˇ abc\nabc");
5552 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5553 .unwrap();
5554 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5555
5556 // Test selecting whitespace with left-to-right selection
5557 cx.set_state("abc\n«ˇ »abc\nabc");
5558 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5559 .unwrap();
5560 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5561
5562 // Test no matches with right-to-left selection
5563 cx.set_state("abc\n« ˇ»abc\nabc");
5564 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5565 .unwrap();
5566 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5567}
5568
5569#[gpui::test]
5570async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5571 init_test(cx, |_| {});
5572
5573 let mut cx = EditorTestContext::new(cx).await;
5574 cx.set_state(
5575 r#"let foo = 2;
5576lˇet foo = 2;
5577let fooˇ = 2;
5578let foo = 2;
5579let foo = ˇ2;"#,
5580 );
5581
5582 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5583 .unwrap();
5584 cx.assert_editor_state(
5585 r#"let foo = 2;
5586«letˇ» foo = 2;
5587let «fooˇ» = 2;
5588let foo = 2;
5589let foo = «2ˇ»;"#,
5590 );
5591
5592 // noop for multiple selections with different contents
5593 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5594 .unwrap();
5595 cx.assert_editor_state(
5596 r#"let foo = 2;
5597«letˇ» foo = 2;
5598let «fooˇ» = 2;
5599let foo = 2;
5600let foo = «2ˇ»;"#,
5601 );
5602}
5603
5604#[gpui::test]
5605async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5606 init_test(cx, |_| {});
5607
5608 let mut cx =
5609 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5610
5611 cx.assert_editor_state(indoc! {"
5612 ˇbbb
5613 ccc
5614
5615 bbb
5616 ccc
5617 "});
5618 cx.dispatch_action(SelectPrevious::default());
5619 cx.assert_editor_state(indoc! {"
5620 «bbbˇ»
5621 ccc
5622
5623 bbb
5624 ccc
5625 "});
5626 cx.dispatch_action(SelectPrevious::default());
5627 cx.assert_editor_state(indoc! {"
5628 «bbbˇ»
5629 ccc
5630
5631 «bbbˇ»
5632 ccc
5633 "});
5634}
5635
5636#[gpui::test]
5637async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5638 init_test(cx, |_| {});
5639
5640 let mut cx = EditorTestContext::new(cx).await;
5641 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5642
5643 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5644 .unwrap();
5645 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5646
5647 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5648 .unwrap();
5649 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5650
5651 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5652 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5653
5654 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5655 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5656
5657 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5658 .unwrap();
5659 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5660
5661 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5662 .unwrap();
5663 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5664
5665 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5666 .unwrap();
5667 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5668}
5669
5670#[gpui::test]
5671async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5672 init_test(cx, |_| {});
5673
5674 let mut cx = EditorTestContext::new(cx).await;
5675 cx.set_state("aˇ");
5676
5677 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5678 .unwrap();
5679 cx.assert_editor_state("«aˇ»");
5680 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5681 .unwrap();
5682 cx.assert_editor_state("«aˇ»");
5683}
5684
5685#[gpui::test]
5686async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5687 init_test(cx, |_| {});
5688
5689 let mut cx = EditorTestContext::new(cx).await;
5690 cx.set_state(
5691 r#"let foo = 2;
5692lˇet foo = 2;
5693let fooˇ = 2;
5694let foo = 2;
5695let foo = ˇ2;"#,
5696 );
5697
5698 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5699 .unwrap();
5700 cx.assert_editor_state(
5701 r#"let foo = 2;
5702«letˇ» foo = 2;
5703let «fooˇ» = 2;
5704let foo = 2;
5705let foo = «2ˇ»;"#,
5706 );
5707
5708 // noop for multiple selections with different contents
5709 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5710 .unwrap();
5711 cx.assert_editor_state(
5712 r#"let foo = 2;
5713«letˇ» foo = 2;
5714let «fooˇ» = 2;
5715let foo = 2;
5716let foo = «2ˇ»;"#,
5717 );
5718}
5719
5720#[gpui::test]
5721async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5722 init_test(cx, |_| {});
5723
5724 let mut cx = EditorTestContext::new(cx).await;
5725 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5726
5727 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5728 .unwrap();
5729 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5730
5731 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5732 .unwrap();
5733 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5734
5735 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5736 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5737
5738 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5739 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5740
5741 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5742 .unwrap();
5743 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5744
5745 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5746 .unwrap();
5747 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5748}
5749
5750#[gpui::test]
5751async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5752 init_test(cx, |_| {});
5753
5754 let language = Arc::new(Language::new(
5755 LanguageConfig::default(),
5756 Some(tree_sitter_rust::LANGUAGE.into()),
5757 ));
5758
5759 let text = r#"
5760 use mod1::mod2::{mod3, mod4};
5761
5762 fn fn_1(param1: bool, param2: &str) {
5763 let var1 = "text";
5764 }
5765 "#
5766 .unindent();
5767
5768 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5769 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5770 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5771
5772 editor
5773 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5774 .await;
5775
5776 editor.update_in(cx, |editor, window, cx| {
5777 editor.change_selections(None, window, cx, |s| {
5778 s.select_display_ranges([
5779 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5780 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5781 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5782 ]);
5783 });
5784 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5785 });
5786 editor.update(cx, |editor, cx| {
5787 assert_text_with_selections(
5788 editor,
5789 indoc! {r#"
5790 use mod1::mod2::{mod3, «mod4ˇ»};
5791
5792 fn fn_1«ˇ(param1: bool, param2: &str)» {
5793 let var1 = "«textˇ»";
5794 }
5795 "#},
5796 cx,
5797 );
5798 });
5799
5800 editor.update_in(cx, |editor, window, cx| {
5801 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5802 });
5803 editor.update(cx, |editor, cx| {
5804 assert_text_with_selections(
5805 editor,
5806 indoc! {r#"
5807 use mod1::mod2::«{mod3, mod4}ˇ»;
5808
5809 «ˇfn fn_1(param1: bool, param2: &str) {
5810 let var1 = "text";
5811 }»
5812 "#},
5813 cx,
5814 );
5815 });
5816
5817 editor.update_in(cx, |editor, window, cx| {
5818 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5819 });
5820 assert_eq!(
5821 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5822 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5823 );
5824
5825 // Trying to expand the selected syntax node one more time has no effect.
5826 editor.update_in(cx, |editor, window, cx| {
5827 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5828 });
5829 assert_eq!(
5830 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5831 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5832 );
5833
5834 editor.update_in(cx, |editor, window, cx| {
5835 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5836 });
5837 editor.update(cx, |editor, cx| {
5838 assert_text_with_selections(
5839 editor,
5840 indoc! {r#"
5841 use mod1::mod2::«{mod3, mod4}ˇ»;
5842
5843 «ˇfn fn_1(param1: bool, param2: &str) {
5844 let var1 = "text";
5845 }»
5846 "#},
5847 cx,
5848 );
5849 });
5850
5851 editor.update_in(cx, |editor, window, cx| {
5852 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5853 });
5854 editor.update(cx, |editor, cx| {
5855 assert_text_with_selections(
5856 editor,
5857 indoc! {r#"
5858 use mod1::mod2::{mod3, «mod4ˇ»};
5859
5860 fn fn_1«ˇ(param1: bool, param2: &str)» {
5861 let var1 = "«textˇ»";
5862 }
5863 "#},
5864 cx,
5865 );
5866 });
5867
5868 editor.update_in(cx, |editor, window, cx| {
5869 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5870 });
5871 editor.update(cx, |editor, cx| {
5872 assert_text_with_selections(
5873 editor,
5874 indoc! {r#"
5875 use mod1::mod2::{mod3, mo«ˇ»d4};
5876
5877 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5878 let var1 = "te«ˇ»xt";
5879 }
5880 "#},
5881 cx,
5882 );
5883 });
5884
5885 // Trying to shrink the selected syntax node one more time has no effect.
5886 editor.update_in(cx, |editor, window, cx| {
5887 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5888 });
5889 editor.update_in(cx, |editor, _, cx| {
5890 assert_text_with_selections(
5891 editor,
5892 indoc! {r#"
5893 use mod1::mod2::{mod3, mo«ˇ»d4};
5894
5895 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5896 let var1 = "te«ˇ»xt";
5897 }
5898 "#},
5899 cx,
5900 );
5901 });
5902
5903 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5904 // a fold.
5905 editor.update_in(cx, |editor, window, cx| {
5906 editor.fold_creases(
5907 vec![
5908 Crease::simple(
5909 Point::new(0, 21)..Point::new(0, 24),
5910 FoldPlaceholder::test(),
5911 ),
5912 Crease::simple(
5913 Point::new(3, 20)..Point::new(3, 22),
5914 FoldPlaceholder::test(),
5915 ),
5916 ],
5917 true,
5918 window,
5919 cx,
5920 );
5921 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5922 });
5923 editor.update(cx, |editor, cx| {
5924 assert_text_with_selections(
5925 editor,
5926 indoc! {r#"
5927 use mod1::mod2::«{mod3, mod4}ˇ»;
5928
5929 fn fn_1«ˇ(param1: bool, param2: &str)» {
5930 «let var1 = "text";ˇ»
5931 }
5932 "#},
5933 cx,
5934 );
5935 });
5936}
5937
5938#[gpui::test]
5939async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5940 init_test(cx, |_| {});
5941
5942 let base_text = r#"
5943 impl A {
5944 // this is an uncommitted comment
5945
5946 fn b() {
5947 c();
5948 }
5949
5950 // this is another uncommitted comment
5951
5952 fn d() {
5953 // e
5954 // f
5955 }
5956 }
5957
5958 fn g() {
5959 // h
5960 }
5961 "#
5962 .unindent();
5963
5964 let text = r#"
5965 ˇimpl A {
5966
5967 fn b() {
5968 c();
5969 }
5970
5971 fn d() {
5972 // e
5973 // f
5974 }
5975 }
5976
5977 fn g() {
5978 // h
5979 }
5980 "#
5981 .unindent();
5982
5983 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5984 cx.set_state(&text);
5985 cx.set_head_text(&base_text);
5986 cx.update_editor(|editor, window, cx| {
5987 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5988 });
5989
5990 cx.assert_state_with_diff(
5991 "
5992 ˇimpl A {
5993 - // this is an uncommitted comment
5994
5995 fn b() {
5996 c();
5997 }
5998
5999 - // this is another uncommitted comment
6000 -
6001 fn d() {
6002 // e
6003 // f
6004 }
6005 }
6006
6007 fn g() {
6008 // h
6009 }
6010 "
6011 .unindent(),
6012 );
6013
6014 let expected_display_text = "
6015 impl A {
6016 // this is an uncommitted comment
6017
6018 fn b() {
6019 ⋯
6020 }
6021
6022 // this is another uncommitted comment
6023
6024 fn d() {
6025 ⋯
6026 }
6027 }
6028
6029 fn g() {
6030 ⋯
6031 }
6032 "
6033 .unindent();
6034
6035 cx.update_editor(|editor, window, cx| {
6036 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6037 assert_eq!(editor.display_text(cx), expected_display_text);
6038 });
6039}
6040
6041#[gpui::test]
6042async fn test_autoindent(cx: &mut TestAppContext) {
6043 init_test(cx, |_| {});
6044
6045 let language = Arc::new(
6046 Language::new(
6047 LanguageConfig {
6048 brackets: BracketPairConfig {
6049 pairs: vec![
6050 BracketPair {
6051 start: "{".to_string(),
6052 end: "}".to_string(),
6053 close: false,
6054 surround: false,
6055 newline: true,
6056 },
6057 BracketPair {
6058 start: "(".to_string(),
6059 end: ")".to_string(),
6060 close: false,
6061 surround: false,
6062 newline: true,
6063 },
6064 ],
6065 ..Default::default()
6066 },
6067 ..Default::default()
6068 },
6069 Some(tree_sitter_rust::LANGUAGE.into()),
6070 )
6071 .with_indents_query(
6072 r#"
6073 (_ "(" ")" @end) @indent
6074 (_ "{" "}" @end) @indent
6075 "#,
6076 )
6077 .unwrap(),
6078 );
6079
6080 let text = "fn a() {}";
6081
6082 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6083 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6084 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6085 editor
6086 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6087 .await;
6088
6089 editor.update_in(cx, |editor, window, cx| {
6090 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6091 editor.newline(&Newline, window, cx);
6092 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6093 assert_eq!(
6094 editor.selections.ranges(cx),
6095 &[
6096 Point::new(1, 4)..Point::new(1, 4),
6097 Point::new(3, 4)..Point::new(3, 4),
6098 Point::new(5, 0)..Point::new(5, 0)
6099 ]
6100 );
6101 });
6102}
6103
6104#[gpui::test]
6105async fn test_autoindent_selections(cx: &mut TestAppContext) {
6106 init_test(cx, |_| {});
6107
6108 {
6109 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6110 cx.set_state(indoc! {"
6111 impl A {
6112
6113 fn b() {}
6114
6115 «fn c() {
6116
6117 }ˇ»
6118 }
6119 "});
6120
6121 cx.update_editor(|editor, window, cx| {
6122 editor.autoindent(&Default::default(), window, cx);
6123 });
6124
6125 cx.assert_editor_state(indoc! {"
6126 impl A {
6127
6128 fn b() {}
6129
6130 «fn c() {
6131
6132 }ˇ»
6133 }
6134 "});
6135 }
6136
6137 {
6138 let mut cx = EditorTestContext::new_multibuffer(
6139 cx,
6140 [indoc! { "
6141 impl A {
6142 «
6143 // a
6144 fn b(){}
6145 »
6146 «
6147 }
6148 fn c(){}
6149 »
6150 "}],
6151 );
6152
6153 let buffer = cx.update_editor(|editor, _, cx| {
6154 let buffer = editor.buffer().update(cx, |buffer, _| {
6155 buffer.all_buffers().iter().next().unwrap().clone()
6156 });
6157 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6158 buffer
6159 });
6160
6161 cx.run_until_parked();
6162 cx.update_editor(|editor, window, cx| {
6163 editor.select_all(&Default::default(), window, cx);
6164 editor.autoindent(&Default::default(), window, cx)
6165 });
6166 cx.run_until_parked();
6167
6168 cx.update(|_, cx| {
6169 pretty_assertions::assert_eq!(
6170 buffer.read(cx).text(),
6171 indoc! { "
6172 impl A {
6173
6174 // a
6175 fn b(){}
6176
6177
6178 }
6179 fn c(){}
6180
6181 " }
6182 )
6183 });
6184 }
6185}
6186
6187#[gpui::test]
6188async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6189 init_test(cx, |_| {});
6190
6191 let mut cx = EditorTestContext::new(cx).await;
6192
6193 let language = Arc::new(Language::new(
6194 LanguageConfig {
6195 brackets: BracketPairConfig {
6196 pairs: vec![
6197 BracketPair {
6198 start: "{".to_string(),
6199 end: "}".to_string(),
6200 close: true,
6201 surround: true,
6202 newline: true,
6203 },
6204 BracketPair {
6205 start: "(".to_string(),
6206 end: ")".to_string(),
6207 close: true,
6208 surround: true,
6209 newline: true,
6210 },
6211 BracketPair {
6212 start: "/*".to_string(),
6213 end: " */".to_string(),
6214 close: true,
6215 surround: true,
6216 newline: true,
6217 },
6218 BracketPair {
6219 start: "[".to_string(),
6220 end: "]".to_string(),
6221 close: false,
6222 surround: false,
6223 newline: true,
6224 },
6225 BracketPair {
6226 start: "\"".to_string(),
6227 end: "\"".to_string(),
6228 close: true,
6229 surround: true,
6230 newline: false,
6231 },
6232 BracketPair {
6233 start: "<".to_string(),
6234 end: ">".to_string(),
6235 close: false,
6236 surround: true,
6237 newline: true,
6238 },
6239 ],
6240 ..Default::default()
6241 },
6242 autoclose_before: "})]".to_string(),
6243 ..Default::default()
6244 },
6245 Some(tree_sitter_rust::LANGUAGE.into()),
6246 ));
6247
6248 cx.language_registry().add(language.clone());
6249 cx.update_buffer(|buffer, cx| {
6250 buffer.set_language(Some(language), cx);
6251 });
6252
6253 cx.set_state(
6254 &r#"
6255 🏀ˇ
6256 εˇ
6257 ❤️ˇ
6258 "#
6259 .unindent(),
6260 );
6261
6262 // autoclose multiple nested brackets at multiple cursors
6263 cx.update_editor(|editor, window, cx| {
6264 editor.handle_input("{", window, cx);
6265 editor.handle_input("{", window, cx);
6266 editor.handle_input("{", window, cx);
6267 });
6268 cx.assert_editor_state(
6269 &"
6270 🏀{{{ˇ}}}
6271 ε{{{ˇ}}}
6272 ❤️{{{ˇ}}}
6273 "
6274 .unindent(),
6275 );
6276
6277 // insert a different closing bracket
6278 cx.update_editor(|editor, window, cx| {
6279 editor.handle_input(")", window, cx);
6280 });
6281 cx.assert_editor_state(
6282 &"
6283 🏀{{{)ˇ}}}
6284 ε{{{)ˇ}}}
6285 ❤️{{{)ˇ}}}
6286 "
6287 .unindent(),
6288 );
6289
6290 // skip over the auto-closed brackets when typing a closing bracket
6291 cx.update_editor(|editor, window, cx| {
6292 editor.move_right(&MoveRight, window, cx);
6293 editor.handle_input("}", window, cx);
6294 editor.handle_input("}", window, cx);
6295 editor.handle_input("}", window, cx);
6296 });
6297 cx.assert_editor_state(
6298 &"
6299 🏀{{{)}}}}ˇ
6300 ε{{{)}}}}ˇ
6301 ❤️{{{)}}}}ˇ
6302 "
6303 .unindent(),
6304 );
6305
6306 // autoclose multi-character pairs
6307 cx.set_state(
6308 &"
6309 ˇ
6310 ˇ
6311 "
6312 .unindent(),
6313 );
6314 cx.update_editor(|editor, window, cx| {
6315 editor.handle_input("/", window, cx);
6316 editor.handle_input("*", window, cx);
6317 });
6318 cx.assert_editor_state(
6319 &"
6320 /*ˇ */
6321 /*ˇ */
6322 "
6323 .unindent(),
6324 );
6325
6326 // one cursor autocloses a multi-character pair, one cursor
6327 // does not autoclose.
6328 cx.set_state(
6329 &"
6330 /ˇ
6331 ˇ
6332 "
6333 .unindent(),
6334 );
6335 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6336 cx.assert_editor_state(
6337 &"
6338 /*ˇ */
6339 *ˇ
6340 "
6341 .unindent(),
6342 );
6343
6344 // Don't autoclose if the next character isn't whitespace and isn't
6345 // listed in the language's "autoclose_before" section.
6346 cx.set_state("ˇa b");
6347 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6348 cx.assert_editor_state("{ˇa b");
6349
6350 // Don't autoclose if `close` is false for the bracket pair
6351 cx.set_state("ˇ");
6352 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6353 cx.assert_editor_state("[ˇ");
6354
6355 // Surround with brackets if text is selected
6356 cx.set_state("«aˇ» b");
6357 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6358 cx.assert_editor_state("{«aˇ»} b");
6359
6360 // Autclose pair where the start and end characters are the same
6361 cx.set_state("aˇ");
6362 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6363 cx.assert_editor_state("a\"ˇ\"");
6364 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6365 cx.assert_editor_state("a\"\"ˇ");
6366
6367 // Don't autoclose pair if autoclose is disabled
6368 cx.set_state("ˇ");
6369 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6370 cx.assert_editor_state("<ˇ");
6371
6372 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6373 cx.set_state("«aˇ» b");
6374 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6375 cx.assert_editor_state("<«aˇ»> b");
6376}
6377
6378#[gpui::test]
6379async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6380 init_test(cx, |settings| {
6381 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6382 });
6383
6384 let mut cx = EditorTestContext::new(cx).await;
6385
6386 let language = Arc::new(Language::new(
6387 LanguageConfig {
6388 brackets: BracketPairConfig {
6389 pairs: vec![
6390 BracketPair {
6391 start: "{".to_string(),
6392 end: "}".to_string(),
6393 close: true,
6394 surround: true,
6395 newline: true,
6396 },
6397 BracketPair {
6398 start: "(".to_string(),
6399 end: ")".to_string(),
6400 close: true,
6401 surround: true,
6402 newline: true,
6403 },
6404 BracketPair {
6405 start: "[".to_string(),
6406 end: "]".to_string(),
6407 close: false,
6408 surround: false,
6409 newline: true,
6410 },
6411 ],
6412 ..Default::default()
6413 },
6414 autoclose_before: "})]".to_string(),
6415 ..Default::default()
6416 },
6417 Some(tree_sitter_rust::LANGUAGE.into()),
6418 ));
6419
6420 cx.language_registry().add(language.clone());
6421 cx.update_buffer(|buffer, cx| {
6422 buffer.set_language(Some(language), cx);
6423 });
6424
6425 cx.set_state(
6426 &"
6427 ˇ
6428 ˇ
6429 ˇ
6430 "
6431 .unindent(),
6432 );
6433
6434 // ensure only matching closing brackets are skipped over
6435 cx.update_editor(|editor, window, cx| {
6436 editor.handle_input("}", window, cx);
6437 editor.move_left(&MoveLeft, window, cx);
6438 editor.handle_input(")", window, cx);
6439 editor.move_left(&MoveLeft, window, cx);
6440 });
6441 cx.assert_editor_state(
6442 &"
6443 ˇ)}
6444 ˇ)}
6445 ˇ)}
6446 "
6447 .unindent(),
6448 );
6449
6450 // skip-over closing brackets at multiple cursors
6451 cx.update_editor(|editor, window, cx| {
6452 editor.handle_input(")", window, cx);
6453 editor.handle_input("}", window, cx);
6454 });
6455 cx.assert_editor_state(
6456 &"
6457 )}ˇ
6458 )}ˇ
6459 )}ˇ
6460 "
6461 .unindent(),
6462 );
6463
6464 // ignore non-close brackets
6465 cx.update_editor(|editor, window, cx| {
6466 editor.handle_input("]", window, cx);
6467 editor.move_left(&MoveLeft, window, cx);
6468 editor.handle_input("]", window, cx);
6469 });
6470 cx.assert_editor_state(
6471 &"
6472 )}]ˇ]
6473 )}]ˇ]
6474 )}]ˇ]
6475 "
6476 .unindent(),
6477 );
6478}
6479
6480#[gpui::test]
6481async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6482 init_test(cx, |_| {});
6483
6484 let mut cx = EditorTestContext::new(cx).await;
6485
6486 let html_language = Arc::new(
6487 Language::new(
6488 LanguageConfig {
6489 name: "HTML".into(),
6490 brackets: BracketPairConfig {
6491 pairs: vec![
6492 BracketPair {
6493 start: "<".into(),
6494 end: ">".into(),
6495 close: true,
6496 ..Default::default()
6497 },
6498 BracketPair {
6499 start: "{".into(),
6500 end: "}".into(),
6501 close: true,
6502 ..Default::default()
6503 },
6504 BracketPair {
6505 start: "(".into(),
6506 end: ")".into(),
6507 close: true,
6508 ..Default::default()
6509 },
6510 ],
6511 ..Default::default()
6512 },
6513 autoclose_before: "})]>".into(),
6514 ..Default::default()
6515 },
6516 Some(tree_sitter_html::LANGUAGE.into()),
6517 )
6518 .with_injection_query(
6519 r#"
6520 (script_element
6521 (raw_text) @injection.content
6522 (#set! injection.language "javascript"))
6523 "#,
6524 )
6525 .unwrap(),
6526 );
6527
6528 let javascript_language = Arc::new(Language::new(
6529 LanguageConfig {
6530 name: "JavaScript".into(),
6531 brackets: BracketPairConfig {
6532 pairs: vec![
6533 BracketPair {
6534 start: "/*".into(),
6535 end: " */".into(),
6536 close: true,
6537 ..Default::default()
6538 },
6539 BracketPair {
6540 start: "{".into(),
6541 end: "}".into(),
6542 close: true,
6543 ..Default::default()
6544 },
6545 BracketPair {
6546 start: "(".into(),
6547 end: ")".into(),
6548 close: true,
6549 ..Default::default()
6550 },
6551 ],
6552 ..Default::default()
6553 },
6554 autoclose_before: "})]>".into(),
6555 ..Default::default()
6556 },
6557 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6558 ));
6559
6560 cx.language_registry().add(html_language.clone());
6561 cx.language_registry().add(javascript_language.clone());
6562
6563 cx.update_buffer(|buffer, cx| {
6564 buffer.set_language(Some(html_language), cx);
6565 });
6566
6567 cx.set_state(
6568 &r#"
6569 <body>ˇ
6570 <script>
6571 var x = 1;ˇ
6572 </script>
6573 </body>ˇ
6574 "#
6575 .unindent(),
6576 );
6577
6578 // Precondition: different languages are active at different locations.
6579 cx.update_editor(|editor, window, cx| {
6580 let snapshot = editor.snapshot(window, cx);
6581 let cursors = editor.selections.ranges::<usize>(cx);
6582 let languages = cursors
6583 .iter()
6584 .map(|c| snapshot.language_at(c.start).unwrap().name())
6585 .collect::<Vec<_>>();
6586 assert_eq!(
6587 languages,
6588 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6589 );
6590 });
6591
6592 // Angle brackets autoclose in HTML, but not JavaScript.
6593 cx.update_editor(|editor, window, cx| {
6594 editor.handle_input("<", window, cx);
6595 editor.handle_input("a", window, cx);
6596 });
6597 cx.assert_editor_state(
6598 &r#"
6599 <body><aˇ>
6600 <script>
6601 var x = 1;<aˇ
6602 </script>
6603 </body><aˇ>
6604 "#
6605 .unindent(),
6606 );
6607
6608 // Curly braces and parens autoclose in both HTML and JavaScript.
6609 cx.update_editor(|editor, window, cx| {
6610 editor.handle_input(" b=", window, cx);
6611 editor.handle_input("{", window, cx);
6612 editor.handle_input("c", window, cx);
6613 editor.handle_input("(", window, cx);
6614 });
6615 cx.assert_editor_state(
6616 &r#"
6617 <body><a b={c(ˇ)}>
6618 <script>
6619 var x = 1;<a b={c(ˇ)}
6620 </script>
6621 </body><a b={c(ˇ)}>
6622 "#
6623 .unindent(),
6624 );
6625
6626 // Brackets that were already autoclosed are skipped.
6627 cx.update_editor(|editor, window, cx| {
6628 editor.handle_input(")", window, cx);
6629 editor.handle_input("d", window, cx);
6630 editor.handle_input("}", window, cx);
6631 });
6632 cx.assert_editor_state(
6633 &r#"
6634 <body><a b={c()d}ˇ>
6635 <script>
6636 var x = 1;<a b={c()d}ˇ
6637 </script>
6638 </body><a b={c()d}ˇ>
6639 "#
6640 .unindent(),
6641 );
6642 cx.update_editor(|editor, window, cx| {
6643 editor.handle_input(">", window, cx);
6644 });
6645 cx.assert_editor_state(
6646 &r#"
6647 <body><a b={c()d}>ˇ
6648 <script>
6649 var x = 1;<a b={c()d}>ˇ
6650 </script>
6651 </body><a b={c()d}>ˇ
6652 "#
6653 .unindent(),
6654 );
6655
6656 // Reset
6657 cx.set_state(
6658 &r#"
6659 <body>ˇ
6660 <script>
6661 var x = 1;ˇ
6662 </script>
6663 </body>ˇ
6664 "#
6665 .unindent(),
6666 );
6667
6668 cx.update_editor(|editor, window, cx| {
6669 editor.handle_input("<", window, cx);
6670 });
6671 cx.assert_editor_state(
6672 &r#"
6673 <body><ˇ>
6674 <script>
6675 var x = 1;<ˇ
6676 </script>
6677 </body><ˇ>
6678 "#
6679 .unindent(),
6680 );
6681
6682 // When backspacing, the closing angle brackets are removed.
6683 cx.update_editor(|editor, window, cx| {
6684 editor.backspace(&Backspace, window, cx);
6685 });
6686 cx.assert_editor_state(
6687 &r#"
6688 <body>ˇ
6689 <script>
6690 var x = 1;ˇ
6691 </script>
6692 </body>ˇ
6693 "#
6694 .unindent(),
6695 );
6696
6697 // Block comments autoclose in JavaScript, but not HTML.
6698 cx.update_editor(|editor, window, cx| {
6699 editor.handle_input("/", window, cx);
6700 editor.handle_input("*", window, cx);
6701 });
6702 cx.assert_editor_state(
6703 &r#"
6704 <body>/*ˇ
6705 <script>
6706 var x = 1;/*ˇ */
6707 </script>
6708 </body>/*ˇ
6709 "#
6710 .unindent(),
6711 );
6712}
6713
6714#[gpui::test]
6715async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6716 init_test(cx, |_| {});
6717
6718 let mut cx = EditorTestContext::new(cx).await;
6719
6720 let rust_language = Arc::new(
6721 Language::new(
6722 LanguageConfig {
6723 name: "Rust".into(),
6724 brackets: serde_json::from_value(json!([
6725 { "start": "{", "end": "}", "close": true, "newline": true },
6726 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6727 ]))
6728 .unwrap(),
6729 autoclose_before: "})]>".into(),
6730 ..Default::default()
6731 },
6732 Some(tree_sitter_rust::LANGUAGE.into()),
6733 )
6734 .with_override_query("(string_literal) @string")
6735 .unwrap(),
6736 );
6737
6738 cx.language_registry().add(rust_language.clone());
6739 cx.update_buffer(|buffer, cx| {
6740 buffer.set_language(Some(rust_language), cx);
6741 });
6742
6743 cx.set_state(
6744 &r#"
6745 let x = ˇ
6746 "#
6747 .unindent(),
6748 );
6749
6750 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6751 cx.update_editor(|editor, window, cx| {
6752 editor.handle_input("\"", window, cx);
6753 });
6754 cx.assert_editor_state(
6755 &r#"
6756 let x = "ˇ"
6757 "#
6758 .unindent(),
6759 );
6760
6761 // Inserting another quotation mark. The cursor moves across the existing
6762 // automatically-inserted quotation mark.
6763 cx.update_editor(|editor, window, cx| {
6764 editor.handle_input("\"", window, cx);
6765 });
6766 cx.assert_editor_state(
6767 &r#"
6768 let x = ""ˇ
6769 "#
6770 .unindent(),
6771 );
6772
6773 // Reset
6774 cx.set_state(
6775 &r#"
6776 let x = ˇ
6777 "#
6778 .unindent(),
6779 );
6780
6781 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6782 cx.update_editor(|editor, window, cx| {
6783 editor.handle_input("\"", window, cx);
6784 editor.handle_input(" ", window, cx);
6785 editor.move_left(&Default::default(), window, cx);
6786 editor.handle_input("\\", window, cx);
6787 editor.handle_input("\"", window, cx);
6788 });
6789 cx.assert_editor_state(
6790 &r#"
6791 let x = "\"ˇ "
6792 "#
6793 .unindent(),
6794 );
6795
6796 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6797 // mark. Nothing is inserted.
6798 cx.update_editor(|editor, window, cx| {
6799 editor.move_right(&Default::default(), window, cx);
6800 editor.handle_input("\"", window, cx);
6801 });
6802 cx.assert_editor_state(
6803 &r#"
6804 let x = "\" "ˇ
6805 "#
6806 .unindent(),
6807 );
6808}
6809
6810#[gpui::test]
6811async fn test_surround_with_pair(cx: &mut TestAppContext) {
6812 init_test(cx, |_| {});
6813
6814 let language = Arc::new(Language::new(
6815 LanguageConfig {
6816 brackets: BracketPairConfig {
6817 pairs: vec![
6818 BracketPair {
6819 start: "{".to_string(),
6820 end: "}".to_string(),
6821 close: true,
6822 surround: true,
6823 newline: true,
6824 },
6825 BracketPair {
6826 start: "/* ".to_string(),
6827 end: "*/".to_string(),
6828 close: true,
6829 surround: true,
6830 ..Default::default()
6831 },
6832 ],
6833 ..Default::default()
6834 },
6835 ..Default::default()
6836 },
6837 Some(tree_sitter_rust::LANGUAGE.into()),
6838 ));
6839
6840 let text = r#"
6841 a
6842 b
6843 c
6844 "#
6845 .unindent();
6846
6847 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6848 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6849 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6850 editor
6851 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6852 .await;
6853
6854 editor.update_in(cx, |editor, window, cx| {
6855 editor.change_selections(None, window, cx, |s| {
6856 s.select_display_ranges([
6857 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6859 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6860 ])
6861 });
6862
6863 editor.handle_input("{", window, cx);
6864 editor.handle_input("{", window, cx);
6865 editor.handle_input("{", window, cx);
6866 assert_eq!(
6867 editor.text(cx),
6868 "
6869 {{{a}}}
6870 {{{b}}}
6871 {{{c}}}
6872 "
6873 .unindent()
6874 );
6875 assert_eq!(
6876 editor.selections.display_ranges(cx),
6877 [
6878 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6879 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6880 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6881 ]
6882 );
6883
6884 editor.undo(&Undo, window, cx);
6885 editor.undo(&Undo, window, cx);
6886 editor.undo(&Undo, window, cx);
6887 assert_eq!(
6888 editor.text(cx),
6889 "
6890 a
6891 b
6892 c
6893 "
6894 .unindent()
6895 );
6896 assert_eq!(
6897 editor.selections.display_ranges(cx),
6898 [
6899 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6900 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6901 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6902 ]
6903 );
6904
6905 // Ensure inserting the first character of a multi-byte bracket pair
6906 // doesn't surround the selections with the bracket.
6907 editor.handle_input("/", window, cx);
6908 assert_eq!(
6909 editor.text(cx),
6910 "
6911 /
6912 /
6913 /
6914 "
6915 .unindent()
6916 );
6917 assert_eq!(
6918 editor.selections.display_ranges(cx),
6919 [
6920 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6922 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6923 ]
6924 );
6925
6926 editor.undo(&Undo, window, cx);
6927 assert_eq!(
6928 editor.text(cx),
6929 "
6930 a
6931 b
6932 c
6933 "
6934 .unindent()
6935 );
6936 assert_eq!(
6937 editor.selections.display_ranges(cx),
6938 [
6939 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6940 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6941 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6942 ]
6943 );
6944
6945 // Ensure inserting the last character of a multi-byte bracket pair
6946 // doesn't surround the selections with the bracket.
6947 editor.handle_input("*", window, cx);
6948 assert_eq!(
6949 editor.text(cx),
6950 "
6951 *
6952 *
6953 *
6954 "
6955 .unindent()
6956 );
6957 assert_eq!(
6958 editor.selections.display_ranges(cx),
6959 [
6960 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6961 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6962 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6963 ]
6964 );
6965 });
6966}
6967
6968#[gpui::test]
6969async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6970 init_test(cx, |_| {});
6971
6972 let language = Arc::new(Language::new(
6973 LanguageConfig {
6974 brackets: BracketPairConfig {
6975 pairs: vec![BracketPair {
6976 start: "{".to_string(),
6977 end: "}".to_string(),
6978 close: true,
6979 surround: true,
6980 newline: true,
6981 }],
6982 ..Default::default()
6983 },
6984 autoclose_before: "}".to_string(),
6985 ..Default::default()
6986 },
6987 Some(tree_sitter_rust::LANGUAGE.into()),
6988 ));
6989
6990 let text = r#"
6991 a
6992 b
6993 c
6994 "#
6995 .unindent();
6996
6997 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6998 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6999 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7000 editor
7001 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7002 .await;
7003
7004 editor.update_in(cx, |editor, window, cx| {
7005 editor.change_selections(None, window, cx, |s| {
7006 s.select_ranges([
7007 Point::new(0, 1)..Point::new(0, 1),
7008 Point::new(1, 1)..Point::new(1, 1),
7009 Point::new(2, 1)..Point::new(2, 1),
7010 ])
7011 });
7012
7013 editor.handle_input("{", window, cx);
7014 editor.handle_input("{", window, cx);
7015 editor.handle_input("_", window, cx);
7016 assert_eq!(
7017 editor.text(cx),
7018 "
7019 a{{_}}
7020 b{{_}}
7021 c{{_}}
7022 "
7023 .unindent()
7024 );
7025 assert_eq!(
7026 editor.selections.ranges::<Point>(cx),
7027 [
7028 Point::new(0, 4)..Point::new(0, 4),
7029 Point::new(1, 4)..Point::new(1, 4),
7030 Point::new(2, 4)..Point::new(2, 4)
7031 ]
7032 );
7033
7034 editor.backspace(&Default::default(), window, cx);
7035 editor.backspace(&Default::default(), window, cx);
7036 assert_eq!(
7037 editor.text(cx),
7038 "
7039 a{}
7040 b{}
7041 c{}
7042 "
7043 .unindent()
7044 );
7045 assert_eq!(
7046 editor.selections.ranges::<Point>(cx),
7047 [
7048 Point::new(0, 2)..Point::new(0, 2),
7049 Point::new(1, 2)..Point::new(1, 2),
7050 Point::new(2, 2)..Point::new(2, 2)
7051 ]
7052 );
7053
7054 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7055 assert_eq!(
7056 editor.text(cx),
7057 "
7058 a
7059 b
7060 c
7061 "
7062 .unindent()
7063 );
7064 assert_eq!(
7065 editor.selections.ranges::<Point>(cx),
7066 [
7067 Point::new(0, 1)..Point::new(0, 1),
7068 Point::new(1, 1)..Point::new(1, 1),
7069 Point::new(2, 1)..Point::new(2, 1)
7070 ]
7071 );
7072 });
7073}
7074
7075#[gpui::test]
7076async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7077 init_test(cx, |settings| {
7078 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7079 });
7080
7081 let mut cx = EditorTestContext::new(cx).await;
7082
7083 let language = Arc::new(Language::new(
7084 LanguageConfig {
7085 brackets: BracketPairConfig {
7086 pairs: vec![
7087 BracketPair {
7088 start: "{".to_string(),
7089 end: "}".to_string(),
7090 close: true,
7091 surround: true,
7092 newline: true,
7093 },
7094 BracketPair {
7095 start: "(".to_string(),
7096 end: ")".to_string(),
7097 close: true,
7098 surround: true,
7099 newline: true,
7100 },
7101 BracketPair {
7102 start: "[".to_string(),
7103 end: "]".to_string(),
7104 close: false,
7105 surround: true,
7106 newline: true,
7107 },
7108 ],
7109 ..Default::default()
7110 },
7111 autoclose_before: "})]".to_string(),
7112 ..Default::default()
7113 },
7114 Some(tree_sitter_rust::LANGUAGE.into()),
7115 ));
7116
7117 cx.language_registry().add(language.clone());
7118 cx.update_buffer(|buffer, cx| {
7119 buffer.set_language(Some(language), cx);
7120 });
7121
7122 cx.set_state(
7123 &"
7124 {(ˇ)}
7125 [[ˇ]]
7126 {(ˇ)}
7127 "
7128 .unindent(),
7129 );
7130
7131 cx.update_editor(|editor, window, cx| {
7132 editor.backspace(&Default::default(), window, cx);
7133 editor.backspace(&Default::default(), window, cx);
7134 });
7135
7136 cx.assert_editor_state(
7137 &"
7138 ˇ
7139 ˇ]]
7140 ˇ
7141 "
7142 .unindent(),
7143 );
7144
7145 cx.update_editor(|editor, window, cx| {
7146 editor.handle_input("{", window, cx);
7147 editor.handle_input("{", window, cx);
7148 editor.move_right(&MoveRight, window, cx);
7149 editor.move_right(&MoveRight, window, cx);
7150 editor.move_left(&MoveLeft, window, cx);
7151 editor.move_left(&MoveLeft, window, cx);
7152 editor.backspace(&Default::default(), window, cx);
7153 });
7154
7155 cx.assert_editor_state(
7156 &"
7157 {ˇ}
7158 {ˇ}]]
7159 {ˇ}
7160 "
7161 .unindent(),
7162 );
7163
7164 cx.update_editor(|editor, window, cx| {
7165 editor.backspace(&Default::default(), window, cx);
7166 });
7167
7168 cx.assert_editor_state(
7169 &"
7170 ˇ
7171 ˇ]]
7172 ˇ
7173 "
7174 .unindent(),
7175 );
7176}
7177
7178#[gpui::test]
7179async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7180 init_test(cx, |_| {});
7181
7182 let language = Arc::new(Language::new(
7183 LanguageConfig::default(),
7184 Some(tree_sitter_rust::LANGUAGE.into()),
7185 ));
7186
7187 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7189 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7190 editor
7191 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7192 .await;
7193
7194 editor.update_in(cx, |editor, window, cx| {
7195 editor.set_auto_replace_emoji_shortcode(true);
7196
7197 editor.handle_input("Hello ", window, cx);
7198 editor.handle_input(":wave", window, cx);
7199 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7200
7201 editor.handle_input(":", window, cx);
7202 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7203
7204 editor.handle_input(" :smile", window, cx);
7205 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7206
7207 editor.handle_input(":", window, cx);
7208 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7209
7210 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7211 editor.handle_input(":wave", window, cx);
7212 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7213
7214 editor.handle_input(":", window, cx);
7215 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7216
7217 editor.handle_input(":1", window, cx);
7218 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7219
7220 editor.handle_input(":", window, cx);
7221 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7222
7223 // Ensure shortcode does not get replaced when it is part of a word
7224 editor.handle_input(" Test:wave", window, cx);
7225 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7226
7227 editor.handle_input(":", window, cx);
7228 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7229
7230 editor.set_auto_replace_emoji_shortcode(false);
7231
7232 // Ensure shortcode does not get replaced when auto replace is off
7233 editor.handle_input(" :wave", window, cx);
7234 assert_eq!(
7235 editor.text(cx),
7236 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7237 );
7238
7239 editor.handle_input(":", window, cx);
7240 assert_eq!(
7241 editor.text(cx),
7242 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7243 );
7244 });
7245}
7246
7247#[gpui::test]
7248async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7249 init_test(cx, |_| {});
7250
7251 let (text, insertion_ranges) = marked_text_ranges(
7252 indoc! {"
7253 ˇ
7254 "},
7255 false,
7256 );
7257
7258 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7259 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7260
7261 _ = editor.update_in(cx, |editor, window, cx| {
7262 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7263
7264 editor
7265 .insert_snippet(&insertion_ranges, snippet, window, cx)
7266 .unwrap();
7267
7268 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7269 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7270 assert_eq!(editor.text(cx), expected_text);
7271 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7272 }
7273
7274 assert(
7275 editor,
7276 cx,
7277 indoc! {"
7278 type «» =•
7279 "},
7280 );
7281
7282 assert!(editor.context_menu_visible(), "There should be a matches");
7283 });
7284}
7285
7286#[gpui::test]
7287async fn test_snippets(cx: &mut TestAppContext) {
7288 init_test(cx, |_| {});
7289
7290 let (text, insertion_ranges) = marked_text_ranges(
7291 indoc! {"
7292 a.ˇ b
7293 a.ˇ b
7294 a.ˇ b
7295 "},
7296 false,
7297 );
7298
7299 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7300 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7301
7302 editor.update_in(cx, |editor, window, cx| {
7303 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7304
7305 editor
7306 .insert_snippet(&insertion_ranges, snippet, window, cx)
7307 .unwrap();
7308
7309 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7310 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7311 assert_eq!(editor.text(cx), expected_text);
7312 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7313 }
7314
7315 assert(
7316 editor,
7317 cx,
7318 indoc! {"
7319 a.f(«one», two, «three») b
7320 a.f(«one», two, «three») b
7321 a.f(«one», two, «three») b
7322 "},
7323 );
7324
7325 // Can't move earlier than the first tab stop
7326 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7327 assert(
7328 editor,
7329 cx,
7330 indoc! {"
7331 a.f(«one», two, «three») b
7332 a.f(«one», two, «three») b
7333 a.f(«one», two, «three») b
7334 "},
7335 );
7336
7337 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7338 assert(
7339 editor,
7340 cx,
7341 indoc! {"
7342 a.f(one, «two», three) b
7343 a.f(one, «two», three) b
7344 a.f(one, «two», three) b
7345 "},
7346 );
7347
7348 editor.move_to_prev_snippet_tabstop(window, cx);
7349 assert(
7350 editor,
7351 cx,
7352 indoc! {"
7353 a.f(«one», two, «three») b
7354 a.f(«one», two, «three») b
7355 a.f(«one», two, «three») b
7356 "},
7357 );
7358
7359 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7360 assert(
7361 editor,
7362 cx,
7363 indoc! {"
7364 a.f(one, «two», three) b
7365 a.f(one, «two», three) b
7366 a.f(one, «two», three) b
7367 "},
7368 );
7369 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7370 assert(
7371 editor,
7372 cx,
7373 indoc! {"
7374 a.f(one, two, three)ˇ b
7375 a.f(one, two, three)ˇ b
7376 a.f(one, two, three)ˇ b
7377 "},
7378 );
7379
7380 // As soon as the last tab stop is reached, snippet state is gone
7381 editor.move_to_prev_snippet_tabstop(window, cx);
7382 assert(
7383 editor,
7384 cx,
7385 indoc! {"
7386 a.f(one, two, three)ˇ b
7387 a.f(one, two, three)ˇ b
7388 a.f(one, two, three)ˇ b
7389 "},
7390 );
7391 });
7392}
7393
7394#[gpui::test]
7395async fn test_document_format_during_save(cx: &mut TestAppContext) {
7396 init_test(cx, |_| {});
7397
7398 let fs = FakeFs::new(cx.executor());
7399 fs.insert_file(path!("/file.rs"), Default::default()).await;
7400
7401 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7402
7403 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7404 language_registry.add(rust_lang());
7405 let mut fake_servers = language_registry.register_fake_lsp(
7406 "Rust",
7407 FakeLspAdapter {
7408 capabilities: lsp::ServerCapabilities {
7409 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7410 ..Default::default()
7411 },
7412 ..Default::default()
7413 },
7414 );
7415
7416 let buffer = project
7417 .update(cx, |project, cx| {
7418 project.open_local_buffer(path!("/file.rs"), cx)
7419 })
7420 .await
7421 .unwrap();
7422
7423 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7424 let (editor, cx) = cx.add_window_view(|window, cx| {
7425 build_editor_with_project(project.clone(), buffer, window, cx)
7426 });
7427 editor.update_in(cx, |editor, window, cx| {
7428 editor.set_text("one\ntwo\nthree\n", window, cx)
7429 });
7430 assert!(cx.read(|cx| editor.is_dirty(cx)));
7431
7432 cx.executor().start_waiting();
7433 let fake_server = fake_servers.next().await.unwrap();
7434
7435 let save = editor
7436 .update_in(cx, |editor, window, cx| {
7437 editor.save(true, project.clone(), window, cx)
7438 })
7439 .unwrap();
7440 fake_server
7441 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7442 assert_eq!(
7443 params.text_document.uri,
7444 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7445 );
7446 assert_eq!(params.options.tab_size, 4);
7447 Ok(Some(vec![lsp::TextEdit::new(
7448 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7449 ", ".to_string(),
7450 )]))
7451 })
7452 .next()
7453 .await;
7454 cx.executor().start_waiting();
7455 save.await;
7456
7457 assert_eq!(
7458 editor.update(cx, |editor, cx| editor.text(cx)),
7459 "one, two\nthree\n"
7460 );
7461 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7462
7463 editor.update_in(cx, |editor, window, cx| {
7464 editor.set_text("one\ntwo\nthree\n", window, cx)
7465 });
7466 assert!(cx.read(|cx| editor.is_dirty(cx)));
7467
7468 // Ensure we can still save even if formatting hangs.
7469 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7470 assert_eq!(
7471 params.text_document.uri,
7472 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7473 );
7474 futures::future::pending::<()>().await;
7475 unreachable!()
7476 });
7477 let save = editor
7478 .update_in(cx, |editor, window, cx| {
7479 editor.save(true, project.clone(), window, cx)
7480 })
7481 .unwrap();
7482 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7483 cx.executor().start_waiting();
7484 save.await;
7485 assert_eq!(
7486 editor.update(cx, |editor, cx| editor.text(cx)),
7487 "one\ntwo\nthree\n"
7488 );
7489 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7490
7491 // For non-dirty buffer, no formatting request should be sent
7492 let save = editor
7493 .update_in(cx, |editor, window, cx| {
7494 editor.save(true, project.clone(), window, cx)
7495 })
7496 .unwrap();
7497 let _pending_format_request = fake_server
7498 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7499 panic!("Should not be invoked on non-dirty buffer");
7500 })
7501 .next();
7502 cx.executor().start_waiting();
7503 save.await;
7504
7505 // Set rust language override and assert overridden tabsize is sent to language server
7506 update_test_language_settings(cx, |settings| {
7507 settings.languages.insert(
7508 "Rust".into(),
7509 LanguageSettingsContent {
7510 tab_size: NonZeroU32::new(8),
7511 ..Default::default()
7512 },
7513 );
7514 });
7515
7516 editor.update_in(cx, |editor, window, cx| {
7517 editor.set_text("somehting_new\n", window, cx)
7518 });
7519 assert!(cx.read(|cx| editor.is_dirty(cx)));
7520 let save = editor
7521 .update_in(cx, |editor, window, cx| {
7522 editor.save(true, project.clone(), window, cx)
7523 })
7524 .unwrap();
7525 fake_server
7526 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7527 assert_eq!(
7528 params.text_document.uri,
7529 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7530 );
7531 assert_eq!(params.options.tab_size, 8);
7532 Ok(Some(vec![]))
7533 })
7534 .next()
7535 .await;
7536 cx.executor().start_waiting();
7537 save.await;
7538}
7539
7540#[gpui::test]
7541async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7542 init_test(cx, |_| {});
7543
7544 let cols = 4;
7545 let rows = 10;
7546 let sample_text_1 = sample_text(rows, cols, 'a');
7547 assert_eq!(
7548 sample_text_1,
7549 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7550 );
7551 let sample_text_2 = sample_text(rows, cols, 'l');
7552 assert_eq!(
7553 sample_text_2,
7554 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7555 );
7556 let sample_text_3 = sample_text(rows, cols, 'v');
7557 assert_eq!(
7558 sample_text_3,
7559 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7560 );
7561
7562 let fs = FakeFs::new(cx.executor());
7563 fs.insert_tree(
7564 path!("/a"),
7565 json!({
7566 "main.rs": sample_text_1,
7567 "other.rs": sample_text_2,
7568 "lib.rs": sample_text_3,
7569 }),
7570 )
7571 .await;
7572
7573 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7574 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7575 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7576
7577 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7578 language_registry.add(rust_lang());
7579 let mut fake_servers = language_registry.register_fake_lsp(
7580 "Rust",
7581 FakeLspAdapter {
7582 capabilities: lsp::ServerCapabilities {
7583 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7584 ..Default::default()
7585 },
7586 ..Default::default()
7587 },
7588 );
7589
7590 let worktree = project.update(cx, |project, cx| {
7591 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7592 assert_eq!(worktrees.len(), 1);
7593 worktrees.pop().unwrap()
7594 });
7595 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7596
7597 let buffer_1 = project
7598 .update(cx, |project, cx| {
7599 project.open_buffer((worktree_id, "main.rs"), cx)
7600 })
7601 .await
7602 .unwrap();
7603 let buffer_2 = project
7604 .update(cx, |project, cx| {
7605 project.open_buffer((worktree_id, "other.rs"), cx)
7606 })
7607 .await
7608 .unwrap();
7609 let buffer_3 = project
7610 .update(cx, |project, cx| {
7611 project.open_buffer((worktree_id, "lib.rs"), cx)
7612 })
7613 .await
7614 .unwrap();
7615
7616 let multi_buffer = cx.new(|cx| {
7617 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7618 multi_buffer.push_excerpts(
7619 buffer_1.clone(),
7620 [
7621 ExcerptRange {
7622 context: Point::new(0, 0)..Point::new(3, 0),
7623 primary: None,
7624 },
7625 ExcerptRange {
7626 context: Point::new(5, 0)..Point::new(7, 0),
7627 primary: None,
7628 },
7629 ExcerptRange {
7630 context: Point::new(9, 0)..Point::new(10, 4),
7631 primary: None,
7632 },
7633 ],
7634 cx,
7635 );
7636 multi_buffer.push_excerpts(
7637 buffer_2.clone(),
7638 [
7639 ExcerptRange {
7640 context: Point::new(0, 0)..Point::new(3, 0),
7641 primary: None,
7642 },
7643 ExcerptRange {
7644 context: Point::new(5, 0)..Point::new(7, 0),
7645 primary: None,
7646 },
7647 ExcerptRange {
7648 context: Point::new(9, 0)..Point::new(10, 4),
7649 primary: None,
7650 },
7651 ],
7652 cx,
7653 );
7654 multi_buffer.push_excerpts(
7655 buffer_3.clone(),
7656 [
7657 ExcerptRange {
7658 context: Point::new(0, 0)..Point::new(3, 0),
7659 primary: None,
7660 },
7661 ExcerptRange {
7662 context: Point::new(5, 0)..Point::new(7, 0),
7663 primary: None,
7664 },
7665 ExcerptRange {
7666 context: Point::new(9, 0)..Point::new(10, 4),
7667 primary: None,
7668 },
7669 ],
7670 cx,
7671 );
7672 multi_buffer
7673 });
7674 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7675 Editor::new(
7676 EditorMode::Full,
7677 multi_buffer,
7678 Some(project.clone()),
7679 true,
7680 window,
7681 cx,
7682 )
7683 });
7684
7685 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7686 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7687 s.select_ranges(Some(1..2))
7688 });
7689 editor.insert("|one|two|three|", window, cx);
7690 });
7691 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7692 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7693 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7694 s.select_ranges(Some(60..70))
7695 });
7696 editor.insert("|four|five|six|", window, cx);
7697 });
7698 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7699
7700 // First two buffers should be edited, but not the third one.
7701 assert_eq!(
7702 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7703 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7704 );
7705 buffer_1.update(cx, |buffer, _| {
7706 assert!(buffer.is_dirty());
7707 assert_eq!(
7708 buffer.text(),
7709 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7710 )
7711 });
7712 buffer_2.update(cx, |buffer, _| {
7713 assert!(buffer.is_dirty());
7714 assert_eq!(
7715 buffer.text(),
7716 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7717 )
7718 });
7719 buffer_3.update(cx, |buffer, _| {
7720 assert!(!buffer.is_dirty());
7721 assert_eq!(buffer.text(), sample_text_3,)
7722 });
7723 cx.executor().run_until_parked();
7724
7725 cx.executor().start_waiting();
7726 let save = multi_buffer_editor
7727 .update_in(cx, |editor, window, cx| {
7728 editor.save(true, project.clone(), window, cx)
7729 })
7730 .unwrap();
7731
7732 let fake_server = fake_servers.next().await.unwrap();
7733 fake_server
7734 .server
7735 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7736 Ok(Some(vec![lsp::TextEdit::new(
7737 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7738 format!("[{} formatted]", params.text_document.uri),
7739 )]))
7740 })
7741 .detach();
7742 save.await;
7743
7744 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7745 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7746 assert_eq!(
7747 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7748 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"),
7749 );
7750 buffer_1.update(cx, |buffer, _| {
7751 assert!(!buffer.is_dirty());
7752 assert_eq!(
7753 buffer.text(),
7754 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7755 )
7756 });
7757 buffer_2.update(cx, |buffer, _| {
7758 assert!(!buffer.is_dirty());
7759 assert_eq!(
7760 buffer.text(),
7761 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7762 )
7763 });
7764 buffer_3.update(cx, |buffer, _| {
7765 assert!(!buffer.is_dirty());
7766 assert_eq!(buffer.text(), sample_text_3,)
7767 });
7768}
7769
7770#[gpui::test]
7771async fn test_range_format_during_save(cx: &mut TestAppContext) {
7772 init_test(cx, |_| {});
7773
7774 let fs = FakeFs::new(cx.executor());
7775 fs.insert_file(path!("/file.rs"), Default::default()).await;
7776
7777 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7778
7779 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7780 language_registry.add(rust_lang());
7781 let mut fake_servers = language_registry.register_fake_lsp(
7782 "Rust",
7783 FakeLspAdapter {
7784 capabilities: lsp::ServerCapabilities {
7785 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7786 ..Default::default()
7787 },
7788 ..Default::default()
7789 },
7790 );
7791
7792 let buffer = project
7793 .update(cx, |project, cx| {
7794 project.open_local_buffer(path!("/file.rs"), cx)
7795 })
7796 .await
7797 .unwrap();
7798
7799 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7800 let (editor, cx) = cx.add_window_view(|window, cx| {
7801 build_editor_with_project(project.clone(), buffer, window, cx)
7802 });
7803 editor.update_in(cx, |editor, window, cx| {
7804 editor.set_text("one\ntwo\nthree\n", window, cx)
7805 });
7806 assert!(cx.read(|cx| editor.is_dirty(cx)));
7807
7808 cx.executor().start_waiting();
7809 let fake_server = fake_servers.next().await.unwrap();
7810
7811 let save = editor
7812 .update_in(cx, |editor, window, cx| {
7813 editor.save(true, project.clone(), window, cx)
7814 })
7815 .unwrap();
7816 fake_server
7817 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7818 assert_eq!(
7819 params.text_document.uri,
7820 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7821 );
7822 assert_eq!(params.options.tab_size, 4);
7823 Ok(Some(vec![lsp::TextEdit::new(
7824 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7825 ", ".to_string(),
7826 )]))
7827 })
7828 .next()
7829 .await;
7830 cx.executor().start_waiting();
7831 save.await;
7832 assert_eq!(
7833 editor.update(cx, |editor, cx| editor.text(cx)),
7834 "one, two\nthree\n"
7835 );
7836 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7837
7838 editor.update_in(cx, |editor, window, cx| {
7839 editor.set_text("one\ntwo\nthree\n", window, cx)
7840 });
7841 assert!(cx.read(|cx| editor.is_dirty(cx)));
7842
7843 // Ensure we can still save even if formatting hangs.
7844 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7845 move |params, _| async move {
7846 assert_eq!(
7847 params.text_document.uri,
7848 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7849 );
7850 futures::future::pending::<()>().await;
7851 unreachable!()
7852 },
7853 );
7854 let save = editor
7855 .update_in(cx, |editor, window, cx| {
7856 editor.save(true, project.clone(), window, cx)
7857 })
7858 .unwrap();
7859 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7860 cx.executor().start_waiting();
7861 save.await;
7862 assert_eq!(
7863 editor.update(cx, |editor, cx| editor.text(cx)),
7864 "one\ntwo\nthree\n"
7865 );
7866 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7867
7868 // For non-dirty buffer, no formatting request should be sent
7869 let save = editor
7870 .update_in(cx, |editor, window, cx| {
7871 editor.save(true, project.clone(), window, cx)
7872 })
7873 .unwrap();
7874 let _pending_format_request = fake_server
7875 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7876 panic!("Should not be invoked on non-dirty buffer");
7877 })
7878 .next();
7879 cx.executor().start_waiting();
7880 save.await;
7881
7882 // Set Rust language override and assert overridden tabsize is sent to language server
7883 update_test_language_settings(cx, |settings| {
7884 settings.languages.insert(
7885 "Rust".into(),
7886 LanguageSettingsContent {
7887 tab_size: NonZeroU32::new(8),
7888 ..Default::default()
7889 },
7890 );
7891 });
7892
7893 editor.update_in(cx, |editor, window, cx| {
7894 editor.set_text("somehting_new\n", window, cx)
7895 });
7896 assert!(cx.read(|cx| editor.is_dirty(cx)));
7897 let save = editor
7898 .update_in(cx, |editor, window, cx| {
7899 editor.save(true, project.clone(), window, cx)
7900 })
7901 .unwrap();
7902 fake_server
7903 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7904 assert_eq!(
7905 params.text_document.uri,
7906 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7907 );
7908 assert_eq!(params.options.tab_size, 8);
7909 Ok(Some(vec![]))
7910 })
7911 .next()
7912 .await;
7913 cx.executor().start_waiting();
7914 save.await;
7915}
7916
7917#[gpui::test]
7918async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7919 init_test(cx, |settings| {
7920 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7921 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7922 ))
7923 });
7924
7925 let fs = FakeFs::new(cx.executor());
7926 fs.insert_file(path!("/file.rs"), Default::default()).await;
7927
7928 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7929
7930 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7931 language_registry.add(Arc::new(Language::new(
7932 LanguageConfig {
7933 name: "Rust".into(),
7934 matcher: LanguageMatcher {
7935 path_suffixes: vec!["rs".to_string()],
7936 ..Default::default()
7937 },
7938 ..LanguageConfig::default()
7939 },
7940 Some(tree_sitter_rust::LANGUAGE.into()),
7941 )));
7942 update_test_language_settings(cx, |settings| {
7943 // Enable Prettier formatting for the same buffer, and ensure
7944 // LSP is called instead of Prettier.
7945 settings.defaults.prettier = Some(PrettierSettings {
7946 allowed: true,
7947 ..PrettierSettings::default()
7948 });
7949 });
7950 let mut fake_servers = language_registry.register_fake_lsp(
7951 "Rust",
7952 FakeLspAdapter {
7953 capabilities: lsp::ServerCapabilities {
7954 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7955 ..Default::default()
7956 },
7957 ..Default::default()
7958 },
7959 );
7960
7961 let buffer = project
7962 .update(cx, |project, cx| {
7963 project.open_local_buffer(path!("/file.rs"), cx)
7964 })
7965 .await
7966 .unwrap();
7967
7968 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7969 let (editor, cx) = cx.add_window_view(|window, cx| {
7970 build_editor_with_project(project.clone(), buffer, window, cx)
7971 });
7972 editor.update_in(cx, |editor, window, cx| {
7973 editor.set_text("one\ntwo\nthree\n", window, cx)
7974 });
7975
7976 cx.executor().start_waiting();
7977 let fake_server = fake_servers.next().await.unwrap();
7978
7979 let format = editor
7980 .update_in(cx, |editor, window, cx| {
7981 editor.perform_format(
7982 project.clone(),
7983 FormatTrigger::Manual,
7984 FormatTarget::Buffers,
7985 window,
7986 cx,
7987 )
7988 })
7989 .unwrap();
7990 fake_server
7991 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7992 assert_eq!(
7993 params.text_document.uri,
7994 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7995 );
7996 assert_eq!(params.options.tab_size, 4);
7997 Ok(Some(vec![lsp::TextEdit::new(
7998 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7999 ", ".to_string(),
8000 )]))
8001 })
8002 .next()
8003 .await;
8004 cx.executor().start_waiting();
8005 format.await;
8006 assert_eq!(
8007 editor.update(cx, |editor, cx| editor.text(cx)),
8008 "one, two\nthree\n"
8009 );
8010
8011 editor.update_in(cx, |editor, window, cx| {
8012 editor.set_text("one\ntwo\nthree\n", window, cx)
8013 });
8014 // Ensure we don't lock if formatting hangs.
8015 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8016 assert_eq!(
8017 params.text_document.uri,
8018 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8019 );
8020 futures::future::pending::<()>().await;
8021 unreachable!()
8022 });
8023 let format = editor
8024 .update_in(cx, |editor, window, cx| {
8025 editor.perform_format(
8026 project,
8027 FormatTrigger::Manual,
8028 FormatTarget::Buffers,
8029 window,
8030 cx,
8031 )
8032 })
8033 .unwrap();
8034 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8035 cx.executor().start_waiting();
8036 format.await;
8037 assert_eq!(
8038 editor.update(cx, |editor, cx| editor.text(cx)),
8039 "one\ntwo\nthree\n"
8040 );
8041}
8042
8043#[gpui::test]
8044async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8045 init_test(cx, |settings| {
8046 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8047 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8048 ))
8049 });
8050
8051 let fs = FakeFs::new(cx.executor());
8052 fs.insert_file(path!("/file.ts"), Default::default()).await;
8053
8054 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8055
8056 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8057 language_registry.add(Arc::new(Language::new(
8058 LanguageConfig {
8059 name: "TypeScript".into(),
8060 matcher: LanguageMatcher {
8061 path_suffixes: vec!["ts".to_string()],
8062 ..Default::default()
8063 },
8064 ..LanguageConfig::default()
8065 },
8066 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8067 )));
8068 update_test_language_settings(cx, |settings| {
8069 settings.defaults.prettier = Some(PrettierSettings {
8070 allowed: true,
8071 ..PrettierSettings::default()
8072 });
8073 });
8074 let mut fake_servers = language_registry.register_fake_lsp(
8075 "TypeScript",
8076 FakeLspAdapter {
8077 capabilities: lsp::ServerCapabilities {
8078 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8079 ..Default::default()
8080 },
8081 ..Default::default()
8082 },
8083 );
8084
8085 let buffer = project
8086 .update(cx, |project, cx| {
8087 project.open_local_buffer(path!("/file.ts"), cx)
8088 })
8089 .await
8090 .unwrap();
8091
8092 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8093 let (editor, cx) = cx.add_window_view(|window, cx| {
8094 build_editor_with_project(project.clone(), buffer, window, cx)
8095 });
8096 editor.update_in(cx, |editor, window, cx| {
8097 editor.set_text(
8098 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8099 window,
8100 cx,
8101 )
8102 });
8103
8104 cx.executor().start_waiting();
8105 let fake_server = fake_servers.next().await.unwrap();
8106
8107 let format = editor
8108 .update_in(cx, |editor, window, cx| {
8109 editor.perform_code_action_kind(
8110 project.clone(),
8111 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8112 window,
8113 cx,
8114 )
8115 })
8116 .unwrap();
8117 fake_server
8118 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8119 assert_eq!(
8120 params.text_document.uri,
8121 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8122 );
8123 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8124 lsp::CodeAction {
8125 title: "Organize Imports".to_string(),
8126 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8127 edit: Some(lsp::WorkspaceEdit {
8128 changes: Some(
8129 [(
8130 params.text_document.uri.clone(),
8131 vec![lsp::TextEdit::new(
8132 lsp::Range::new(
8133 lsp::Position::new(1, 0),
8134 lsp::Position::new(2, 0),
8135 ),
8136 "".to_string(),
8137 )],
8138 )]
8139 .into_iter()
8140 .collect(),
8141 ),
8142 ..Default::default()
8143 }),
8144 ..Default::default()
8145 },
8146 )]))
8147 })
8148 .next()
8149 .await;
8150 cx.executor().start_waiting();
8151 format.await;
8152 assert_eq!(
8153 editor.update(cx, |editor, cx| editor.text(cx)),
8154 "import { a } from 'module';\n\nconst x = a;\n"
8155 );
8156
8157 editor.update_in(cx, |editor, window, cx| {
8158 editor.set_text(
8159 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8160 window,
8161 cx,
8162 )
8163 });
8164 // Ensure we don't lock if code action hangs.
8165 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8166 move |params, _| async move {
8167 assert_eq!(
8168 params.text_document.uri,
8169 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8170 );
8171 futures::future::pending::<()>().await;
8172 unreachable!()
8173 },
8174 );
8175 let format = editor
8176 .update_in(cx, |editor, window, cx| {
8177 editor.perform_code_action_kind(
8178 project,
8179 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8180 window,
8181 cx,
8182 )
8183 })
8184 .unwrap();
8185 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8186 cx.executor().start_waiting();
8187 format.await;
8188 assert_eq!(
8189 editor.update(cx, |editor, cx| editor.text(cx)),
8190 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8191 );
8192}
8193
8194#[gpui::test]
8195async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8196 init_test(cx, |_| {});
8197
8198 let mut cx = EditorLspTestContext::new_rust(
8199 lsp::ServerCapabilities {
8200 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8201 ..Default::default()
8202 },
8203 cx,
8204 )
8205 .await;
8206
8207 cx.set_state(indoc! {"
8208 one.twoˇ
8209 "});
8210
8211 // The format request takes a long time. When it completes, it inserts
8212 // a newline and an indent before the `.`
8213 cx.lsp
8214 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8215 let executor = cx.background_executor().clone();
8216 async move {
8217 executor.timer(Duration::from_millis(100)).await;
8218 Ok(Some(vec![lsp::TextEdit {
8219 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8220 new_text: "\n ".into(),
8221 }]))
8222 }
8223 });
8224
8225 // Submit a format request.
8226 let format_1 = cx
8227 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8228 .unwrap();
8229 cx.executor().run_until_parked();
8230
8231 // Submit a second format request.
8232 let format_2 = cx
8233 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8234 .unwrap();
8235 cx.executor().run_until_parked();
8236
8237 // Wait for both format requests to complete
8238 cx.executor().advance_clock(Duration::from_millis(200));
8239 cx.executor().start_waiting();
8240 format_1.await.unwrap();
8241 cx.executor().start_waiting();
8242 format_2.await.unwrap();
8243
8244 // The formatting edits only happens once.
8245 cx.assert_editor_state(indoc! {"
8246 one
8247 .twoˇ
8248 "});
8249}
8250
8251#[gpui::test]
8252async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8253 init_test(cx, |settings| {
8254 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8255 });
8256
8257 let mut cx = EditorLspTestContext::new_rust(
8258 lsp::ServerCapabilities {
8259 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8260 ..Default::default()
8261 },
8262 cx,
8263 )
8264 .await;
8265
8266 // Set up a buffer white some trailing whitespace and no trailing newline.
8267 cx.set_state(
8268 &[
8269 "one ", //
8270 "twoˇ", //
8271 "three ", //
8272 "four", //
8273 ]
8274 .join("\n"),
8275 );
8276
8277 // Submit a format request.
8278 let format = cx
8279 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8280 .unwrap();
8281
8282 // Record which buffer changes have been sent to the language server
8283 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8284 cx.lsp
8285 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8286 let buffer_changes = buffer_changes.clone();
8287 move |params, _| {
8288 buffer_changes.lock().extend(
8289 params
8290 .content_changes
8291 .into_iter()
8292 .map(|e| (e.range.unwrap(), e.text)),
8293 );
8294 }
8295 });
8296
8297 // Handle formatting requests to the language server.
8298 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8299 let buffer_changes = buffer_changes.clone();
8300 move |_, _| {
8301 // When formatting is requested, trailing whitespace has already been stripped,
8302 // and the trailing newline has already been added.
8303 assert_eq!(
8304 &buffer_changes.lock()[1..],
8305 &[
8306 (
8307 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8308 "".into()
8309 ),
8310 (
8311 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8312 "".into()
8313 ),
8314 (
8315 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8316 "\n".into()
8317 ),
8318 ]
8319 );
8320
8321 // Insert blank lines between each line of the buffer.
8322 async move {
8323 Ok(Some(vec![
8324 lsp::TextEdit {
8325 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8326 new_text: "\n".into(),
8327 },
8328 lsp::TextEdit {
8329 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8330 new_text: "\n".into(),
8331 },
8332 ]))
8333 }
8334 }
8335 });
8336
8337 // After formatting the buffer, the trailing whitespace is stripped,
8338 // a newline is appended, and the edits provided by the language server
8339 // have been applied.
8340 format.await.unwrap();
8341 cx.assert_editor_state(
8342 &[
8343 "one", //
8344 "", //
8345 "twoˇ", //
8346 "", //
8347 "three", //
8348 "four", //
8349 "", //
8350 ]
8351 .join("\n"),
8352 );
8353
8354 // Undoing the formatting undoes the trailing whitespace removal, the
8355 // trailing newline, and the LSP edits.
8356 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8357 cx.assert_editor_state(
8358 &[
8359 "one ", //
8360 "twoˇ", //
8361 "three ", //
8362 "four", //
8363 ]
8364 .join("\n"),
8365 );
8366}
8367
8368#[gpui::test]
8369async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8370 cx: &mut TestAppContext,
8371) {
8372 init_test(cx, |_| {});
8373
8374 cx.update(|cx| {
8375 cx.update_global::<SettingsStore, _>(|settings, cx| {
8376 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8377 settings.auto_signature_help = Some(true);
8378 });
8379 });
8380 });
8381
8382 let mut cx = EditorLspTestContext::new_rust(
8383 lsp::ServerCapabilities {
8384 signature_help_provider: Some(lsp::SignatureHelpOptions {
8385 ..Default::default()
8386 }),
8387 ..Default::default()
8388 },
8389 cx,
8390 )
8391 .await;
8392
8393 let language = Language::new(
8394 LanguageConfig {
8395 name: "Rust".into(),
8396 brackets: BracketPairConfig {
8397 pairs: vec![
8398 BracketPair {
8399 start: "{".to_string(),
8400 end: "}".to_string(),
8401 close: true,
8402 surround: true,
8403 newline: true,
8404 },
8405 BracketPair {
8406 start: "(".to_string(),
8407 end: ")".to_string(),
8408 close: true,
8409 surround: true,
8410 newline: true,
8411 },
8412 BracketPair {
8413 start: "/*".to_string(),
8414 end: " */".to_string(),
8415 close: true,
8416 surround: true,
8417 newline: true,
8418 },
8419 BracketPair {
8420 start: "[".to_string(),
8421 end: "]".to_string(),
8422 close: false,
8423 surround: false,
8424 newline: true,
8425 },
8426 BracketPair {
8427 start: "\"".to_string(),
8428 end: "\"".to_string(),
8429 close: true,
8430 surround: true,
8431 newline: false,
8432 },
8433 BracketPair {
8434 start: "<".to_string(),
8435 end: ">".to_string(),
8436 close: false,
8437 surround: true,
8438 newline: true,
8439 },
8440 ],
8441 ..Default::default()
8442 },
8443 autoclose_before: "})]".to_string(),
8444 ..Default::default()
8445 },
8446 Some(tree_sitter_rust::LANGUAGE.into()),
8447 );
8448 let language = Arc::new(language);
8449
8450 cx.language_registry().add(language.clone());
8451 cx.update_buffer(|buffer, cx| {
8452 buffer.set_language(Some(language), cx);
8453 });
8454
8455 cx.set_state(
8456 &r#"
8457 fn main() {
8458 sampleˇ
8459 }
8460 "#
8461 .unindent(),
8462 );
8463
8464 cx.update_editor(|editor, window, cx| {
8465 editor.handle_input("(", window, cx);
8466 });
8467 cx.assert_editor_state(
8468 &"
8469 fn main() {
8470 sample(ˇ)
8471 }
8472 "
8473 .unindent(),
8474 );
8475
8476 let mocked_response = lsp::SignatureHelp {
8477 signatures: vec![lsp::SignatureInformation {
8478 label: "fn sample(param1: u8, param2: u8)".to_string(),
8479 documentation: None,
8480 parameters: Some(vec![
8481 lsp::ParameterInformation {
8482 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8483 documentation: None,
8484 },
8485 lsp::ParameterInformation {
8486 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8487 documentation: None,
8488 },
8489 ]),
8490 active_parameter: None,
8491 }],
8492 active_signature: Some(0),
8493 active_parameter: Some(0),
8494 };
8495 handle_signature_help_request(&mut cx, mocked_response).await;
8496
8497 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8498 .await;
8499
8500 cx.editor(|editor, _, _| {
8501 let signature_help_state = editor.signature_help_state.popover().cloned();
8502 assert_eq!(
8503 signature_help_state.unwrap().label,
8504 "param1: u8, param2: u8"
8505 );
8506 });
8507}
8508
8509#[gpui::test]
8510async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8511 init_test(cx, |_| {});
8512
8513 cx.update(|cx| {
8514 cx.update_global::<SettingsStore, _>(|settings, cx| {
8515 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8516 settings.auto_signature_help = Some(false);
8517 settings.show_signature_help_after_edits = Some(false);
8518 });
8519 });
8520 });
8521
8522 let mut cx = EditorLspTestContext::new_rust(
8523 lsp::ServerCapabilities {
8524 signature_help_provider: Some(lsp::SignatureHelpOptions {
8525 ..Default::default()
8526 }),
8527 ..Default::default()
8528 },
8529 cx,
8530 )
8531 .await;
8532
8533 let language = Language::new(
8534 LanguageConfig {
8535 name: "Rust".into(),
8536 brackets: BracketPairConfig {
8537 pairs: vec![
8538 BracketPair {
8539 start: "{".to_string(),
8540 end: "}".to_string(),
8541 close: true,
8542 surround: true,
8543 newline: true,
8544 },
8545 BracketPair {
8546 start: "(".to_string(),
8547 end: ")".to_string(),
8548 close: true,
8549 surround: true,
8550 newline: true,
8551 },
8552 BracketPair {
8553 start: "/*".to_string(),
8554 end: " */".to_string(),
8555 close: true,
8556 surround: true,
8557 newline: true,
8558 },
8559 BracketPair {
8560 start: "[".to_string(),
8561 end: "]".to_string(),
8562 close: false,
8563 surround: false,
8564 newline: true,
8565 },
8566 BracketPair {
8567 start: "\"".to_string(),
8568 end: "\"".to_string(),
8569 close: true,
8570 surround: true,
8571 newline: false,
8572 },
8573 BracketPair {
8574 start: "<".to_string(),
8575 end: ">".to_string(),
8576 close: false,
8577 surround: true,
8578 newline: true,
8579 },
8580 ],
8581 ..Default::default()
8582 },
8583 autoclose_before: "})]".to_string(),
8584 ..Default::default()
8585 },
8586 Some(tree_sitter_rust::LANGUAGE.into()),
8587 );
8588 let language = Arc::new(language);
8589
8590 cx.language_registry().add(language.clone());
8591 cx.update_buffer(|buffer, cx| {
8592 buffer.set_language(Some(language), cx);
8593 });
8594
8595 // Ensure that signature_help is not called when no signature help is enabled.
8596 cx.set_state(
8597 &r#"
8598 fn main() {
8599 sampleˇ
8600 }
8601 "#
8602 .unindent(),
8603 );
8604 cx.update_editor(|editor, window, cx| {
8605 editor.handle_input("(", window, cx);
8606 });
8607 cx.assert_editor_state(
8608 &"
8609 fn main() {
8610 sample(ˇ)
8611 }
8612 "
8613 .unindent(),
8614 );
8615 cx.editor(|editor, _, _| {
8616 assert!(editor.signature_help_state.task().is_none());
8617 });
8618
8619 let mocked_response = lsp::SignatureHelp {
8620 signatures: vec![lsp::SignatureInformation {
8621 label: "fn sample(param1: u8, param2: u8)".to_string(),
8622 documentation: None,
8623 parameters: Some(vec![
8624 lsp::ParameterInformation {
8625 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8626 documentation: None,
8627 },
8628 lsp::ParameterInformation {
8629 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8630 documentation: None,
8631 },
8632 ]),
8633 active_parameter: None,
8634 }],
8635 active_signature: Some(0),
8636 active_parameter: Some(0),
8637 };
8638
8639 // Ensure that signature_help is called when enabled afte edits
8640 cx.update(|_, cx| {
8641 cx.update_global::<SettingsStore, _>(|settings, cx| {
8642 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8643 settings.auto_signature_help = Some(false);
8644 settings.show_signature_help_after_edits = Some(true);
8645 });
8646 });
8647 });
8648 cx.set_state(
8649 &r#"
8650 fn main() {
8651 sampleˇ
8652 }
8653 "#
8654 .unindent(),
8655 );
8656 cx.update_editor(|editor, window, cx| {
8657 editor.handle_input("(", window, cx);
8658 });
8659 cx.assert_editor_state(
8660 &"
8661 fn main() {
8662 sample(ˇ)
8663 }
8664 "
8665 .unindent(),
8666 );
8667 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8668 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8669 .await;
8670 cx.update_editor(|editor, _, _| {
8671 let signature_help_state = editor.signature_help_state.popover().cloned();
8672 assert!(signature_help_state.is_some());
8673 assert_eq!(
8674 signature_help_state.unwrap().label,
8675 "param1: u8, param2: u8"
8676 );
8677 editor.signature_help_state = SignatureHelpState::default();
8678 });
8679
8680 // Ensure that signature_help is called when auto signature help override is enabled
8681 cx.update(|_, cx| {
8682 cx.update_global::<SettingsStore, _>(|settings, cx| {
8683 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8684 settings.auto_signature_help = Some(true);
8685 settings.show_signature_help_after_edits = Some(false);
8686 });
8687 });
8688 });
8689 cx.set_state(
8690 &r#"
8691 fn main() {
8692 sampleˇ
8693 }
8694 "#
8695 .unindent(),
8696 );
8697 cx.update_editor(|editor, window, cx| {
8698 editor.handle_input("(", window, cx);
8699 });
8700 cx.assert_editor_state(
8701 &"
8702 fn main() {
8703 sample(ˇ)
8704 }
8705 "
8706 .unindent(),
8707 );
8708 handle_signature_help_request(&mut cx, mocked_response).await;
8709 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8710 .await;
8711 cx.editor(|editor, _, _| {
8712 let signature_help_state = editor.signature_help_state.popover().cloned();
8713 assert!(signature_help_state.is_some());
8714 assert_eq!(
8715 signature_help_state.unwrap().label,
8716 "param1: u8, param2: u8"
8717 );
8718 });
8719}
8720
8721#[gpui::test]
8722async fn test_signature_help(cx: &mut TestAppContext) {
8723 init_test(cx, |_| {});
8724 cx.update(|cx| {
8725 cx.update_global::<SettingsStore, _>(|settings, cx| {
8726 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8727 settings.auto_signature_help = Some(true);
8728 });
8729 });
8730 });
8731
8732 let mut cx = EditorLspTestContext::new_rust(
8733 lsp::ServerCapabilities {
8734 signature_help_provider: Some(lsp::SignatureHelpOptions {
8735 ..Default::default()
8736 }),
8737 ..Default::default()
8738 },
8739 cx,
8740 )
8741 .await;
8742
8743 // A test that directly calls `show_signature_help`
8744 cx.update_editor(|editor, window, cx| {
8745 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8746 });
8747
8748 let mocked_response = lsp::SignatureHelp {
8749 signatures: vec![lsp::SignatureInformation {
8750 label: "fn sample(param1: u8, param2: u8)".to_string(),
8751 documentation: None,
8752 parameters: Some(vec![
8753 lsp::ParameterInformation {
8754 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8755 documentation: None,
8756 },
8757 lsp::ParameterInformation {
8758 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8759 documentation: None,
8760 },
8761 ]),
8762 active_parameter: None,
8763 }],
8764 active_signature: Some(0),
8765 active_parameter: Some(0),
8766 };
8767 handle_signature_help_request(&mut cx, mocked_response).await;
8768
8769 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8770 .await;
8771
8772 cx.editor(|editor, _, _| {
8773 let signature_help_state = editor.signature_help_state.popover().cloned();
8774 assert!(signature_help_state.is_some());
8775 assert_eq!(
8776 signature_help_state.unwrap().label,
8777 "param1: u8, param2: u8"
8778 );
8779 });
8780
8781 // When exiting outside from inside the brackets, `signature_help` is closed.
8782 cx.set_state(indoc! {"
8783 fn main() {
8784 sample(ˇ);
8785 }
8786
8787 fn sample(param1: u8, param2: u8) {}
8788 "});
8789
8790 cx.update_editor(|editor, window, cx| {
8791 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8792 });
8793
8794 let mocked_response = lsp::SignatureHelp {
8795 signatures: Vec::new(),
8796 active_signature: None,
8797 active_parameter: None,
8798 };
8799 handle_signature_help_request(&mut cx, mocked_response).await;
8800
8801 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8802 .await;
8803
8804 cx.editor(|editor, _, _| {
8805 assert!(!editor.signature_help_state.is_shown());
8806 });
8807
8808 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8809 cx.set_state(indoc! {"
8810 fn main() {
8811 sample(ˇ);
8812 }
8813
8814 fn sample(param1: u8, param2: u8) {}
8815 "});
8816
8817 let mocked_response = lsp::SignatureHelp {
8818 signatures: vec![lsp::SignatureInformation {
8819 label: "fn sample(param1: u8, param2: u8)".to_string(),
8820 documentation: None,
8821 parameters: Some(vec![
8822 lsp::ParameterInformation {
8823 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8824 documentation: None,
8825 },
8826 lsp::ParameterInformation {
8827 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8828 documentation: None,
8829 },
8830 ]),
8831 active_parameter: None,
8832 }],
8833 active_signature: Some(0),
8834 active_parameter: Some(0),
8835 };
8836 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8837 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8838 .await;
8839 cx.editor(|editor, _, _| {
8840 assert!(editor.signature_help_state.is_shown());
8841 });
8842
8843 // Restore the popover with more parameter input
8844 cx.set_state(indoc! {"
8845 fn main() {
8846 sample(param1, param2ˇ);
8847 }
8848
8849 fn sample(param1: u8, param2: u8) {}
8850 "});
8851
8852 let mocked_response = lsp::SignatureHelp {
8853 signatures: vec![lsp::SignatureInformation {
8854 label: "fn sample(param1: u8, param2: u8)".to_string(),
8855 documentation: None,
8856 parameters: Some(vec![
8857 lsp::ParameterInformation {
8858 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8859 documentation: None,
8860 },
8861 lsp::ParameterInformation {
8862 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8863 documentation: None,
8864 },
8865 ]),
8866 active_parameter: None,
8867 }],
8868 active_signature: Some(0),
8869 active_parameter: Some(1),
8870 };
8871 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8872 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8873 .await;
8874
8875 // When selecting a range, the popover is gone.
8876 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8877 cx.update_editor(|editor, window, cx| {
8878 editor.change_selections(None, window, cx, |s| {
8879 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8880 })
8881 });
8882 cx.assert_editor_state(indoc! {"
8883 fn main() {
8884 sample(param1, «ˇparam2»);
8885 }
8886
8887 fn sample(param1: u8, param2: u8) {}
8888 "});
8889 cx.editor(|editor, _, _| {
8890 assert!(!editor.signature_help_state.is_shown());
8891 });
8892
8893 // When unselecting again, the popover is back if within the brackets.
8894 cx.update_editor(|editor, window, cx| {
8895 editor.change_selections(None, window, cx, |s| {
8896 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8897 })
8898 });
8899 cx.assert_editor_state(indoc! {"
8900 fn main() {
8901 sample(param1, ˇparam2);
8902 }
8903
8904 fn sample(param1: u8, param2: u8) {}
8905 "});
8906 handle_signature_help_request(&mut cx, mocked_response).await;
8907 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8908 .await;
8909 cx.editor(|editor, _, _| {
8910 assert!(editor.signature_help_state.is_shown());
8911 });
8912
8913 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8914 cx.update_editor(|editor, window, cx| {
8915 editor.change_selections(None, window, cx, |s| {
8916 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8917 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8918 })
8919 });
8920 cx.assert_editor_state(indoc! {"
8921 fn main() {
8922 sample(param1, ˇparam2);
8923 }
8924
8925 fn sample(param1: u8, param2: u8) {}
8926 "});
8927
8928 let mocked_response = lsp::SignatureHelp {
8929 signatures: vec![lsp::SignatureInformation {
8930 label: "fn sample(param1: u8, param2: u8)".to_string(),
8931 documentation: None,
8932 parameters: Some(vec![
8933 lsp::ParameterInformation {
8934 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8935 documentation: None,
8936 },
8937 lsp::ParameterInformation {
8938 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8939 documentation: None,
8940 },
8941 ]),
8942 active_parameter: None,
8943 }],
8944 active_signature: Some(0),
8945 active_parameter: Some(1),
8946 };
8947 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8948 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8949 .await;
8950 cx.update_editor(|editor, _, cx| {
8951 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8952 });
8953 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8954 .await;
8955 cx.update_editor(|editor, window, cx| {
8956 editor.change_selections(None, window, cx, |s| {
8957 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8958 })
8959 });
8960 cx.assert_editor_state(indoc! {"
8961 fn main() {
8962 sample(param1, «ˇparam2»);
8963 }
8964
8965 fn sample(param1: u8, param2: u8) {}
8966 "});
8967 cx.update_editor(|editor, window, cx| {
8968 editor.change_selections(None, window, cx, |s| {
8969 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8970 })
8971 });
8972 cx.assert_editor_state(indoc! {"
8973 fn main() {
8974 sample(param1, ˇparam2);
8975 }
8976
8977 fn sample(param1: u8, param2: u8) {}
8978 "});
8979 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8980 .await;
8981}
8982
8983#[gpui::test]
8984async fn test_completion(cx: &mut TestAppContext) {
8985 init_test(cx, |_| {});
8986
8987 let mut cx = EditorLspTestContext::new_rust(
8988 lsp::ServerCapabilities {
8989 completion_provider: Some(lsp::CompletionOptions {
8990 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8991 resolve_provider: Some(true),
8992 ..Default::default()
8993 }),
8994 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8995 ..Default::default()
8996 },
8997 cx,
8998 )
8999 .await;
9000 let counter = Arc::new(AtomicUsize::new(0));
9001
9002 cx.set_state(indoc! {"
9003 oneˇ
9004 two
9005 three
9006 "});
9007 cx.simulate_keystroke(".");
9008 handle_completion_request(
9009 &mut cx,
9010 indoc! {"
9011 one.|<>
9012 two
9013 three
9014 "},
9015 vec!["first_completion", "second_completion"],
9016 counter.clone(),
9017 )
9018 .await;
9019 cx.condition(|editor, _| editor.context_menu_visible())
9020 .await;
9021 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9022
9023 let _handler = handle_signature_help_request(
9024 &mut cx,
9025 lsp::SignatureHelp {
9026 signatures: vec![lsp::SignatureInformation {
9027 label: "test signature".to_string(),
9028 documentation: None,
9029 parameters: Some(vec![lsp::ParameterInformation {
9030 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9031 documentation: None,
9032 }]),
9033 active_parameter: None,
9034 }],
9035 active_signature: None,
9036 active_parameter: None,
9037 },
9038 );
9039 cx.update_editor(|editor, window, cx| {
9040 assert!(
9041 !editor.signature_help_state.is_shown(),
9042 "No signature help was called for"
9043 );
9044 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9045 });
9046 cx.run_until_parked();
9047 cx.update_editor(|editor, _, _| {
9048 assert!(
9049 !editor.signature_help_state.is_shown(),
9050 "No signature help should be shown when completions menu is open"
9051 );
9052 });
9053
9054 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9055 editor.context_menu_next(&Default::default(), window, cx);
9056 editor
9057 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9058 .unwrap()
9059 });
9060 cx.assert_editor_state(indoc! {"
9061 one.second_completionˇ
9062 two
9063 three
9064 "});
9065
9066 handle_resolve_completion_request(
9067 &mut cx,
9068 Some(vec![
9069 (
9070 //This overlaps with the primary completion edit which is
9071 //misbehavior from the LSP spec, test that we filter it out
9072 indoc! {"
9073 one.second_ˇcompletion
9074 two
9075 threeˇ
9076 "},
9077 "overlapping additional edit",
9078 ),
9079 (
9080 indoc! {"
9081 one.second_completion
9082 two
9083 threeˇ
9084 "},
9085 "\nadditional edit",
9086 ),
9087 ]),
9088 )
9089 .await;
9090 apply_additional_edits.await.unwrap();
9091 cx.assert_editor_state(indoc! {"
9092 one.second_completionˇ
9093 two
9094 three
9095 additional edit
9096 "});
9097
9098 cx.set_state(indoc! {"
9099 one.second_completion
9100 twoˇ
9101 threeˇ
9102 additional edit
9103 "});
9104 cx.simulate_keystroke(" ");
9105 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9106 cx.simulate_keystroke("s");
9107 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9108
9109 cx.assert_editor_state(indoc! {"
9110 one.second_completion
9111 two sˇ
9112 three sˇ
9113 additional edit
9114 "});
9115 handle_completion_request(
9116 &mut cx,
9117 indoc! {"
9118 one.second_completion
9119 two s
9120 three <s|>
9121 additional edit
9122 "},
9123 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9124 counter.clone(),
9125 )
9126 .await;
9127 cx.condition(|editor, _| editor.context_menu_visible())
9128 .await;
9129 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9130
9131 cx.simulate_keystroke("i");
9132
9133 handle_completion_request(
9134 &mut cx,
9135 indoc! {"
9136 one.second_completion
9137 two si
9138 three <si|>
9139 additional edit
9140 "},
9141 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9142 counter.clone(),
9143 )
9144 .await;
9145 cx.condition(|editor, _| editor.context_menu_visible())
9146 .await;
9147 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9148
9149 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9150 editor
9151 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9152 .unwrap()
9153 });
9154 cx.assert_editor_state(indoc! {"
9155 one.second_completion
9156 two sixth_completionˇ
9157 three sixth_completionˇ
9158 additional edit
9159 "});
9160
9161 apply_additional_edits.await.unwrap();
9162
9163 update_test_language_settings(&mut cx, |settings| {
9164 settings.defaults.show_completions_on_input = Some(false);
9165 });
9166 cx.set_state("editorˇ");
9167 cx.simulate_keystroke(".");
9168 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9169 cx.simulate_keystroke("c");
9170 cx.simulate_keystroke("l");
9171 cx.simulate_keystroke("o");
9172 cx.assert_editor_state("editor.cloˇ");
9173 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9174 cx.update_editor(|editor, window, cx| {
9175 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9176 });
9177 handle_completion_request(
9178 &mut cx,
9179 "editor.<clo|>",
9180 vec!["close", "clobber"],
9181 counter.clone(),
9182 )
9183 .await;
9184 cx.condition(|editor, _| editor.context_menu_visible())
9185 .await;
9186 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9187
9188 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9189 editor
9190 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9191 .unwrap()
9192 });
9193 cx.assert_editor_state("editor.closeˇ");
9194 handle_resolve_completion_request(&mut cx, None).await;
9195 apply_additional_edits.await.unwrap();
9196}
9197
9198#[gpui::test]
9199async fn test_words_completion(cx: &mut TestAppContext) {
9200 let lsp_fetch_timeout_ms = 10;
9201 init_test(cx, |language_settings| {
9202 language_settings.defaults.completions = Some(CompletionSettings {
9203 words: WordsCompletionMode::Fallback,
9204 lsp: true,
9205 lsp_fetch_timeout_ms: 10,
9206 });
9207 });
9208
9209 let mut cx = EditorLspTestContext::new_rust(
9210 lsp::ServerCapabilities {
9211 completion_provider: Some(lsp::CompletionOptions {
9212 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9213 ..lsp::CompletionOptions::default()
9214 }),
9215 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9216 ..lsp::ServerCapabilities::default()
9217 },
9218 cx,
9219 )
9220 .await;
9221
9222 let throttle_completions = Arc::new(AtomicBool::new(false));
9223
9224 let lsp_throttle_completions = throttle_completions.clone();
9225 let _completion_requests_handler =
9226 cx.lsp
9227 .server
9228 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9229 let lsp_throttle_completions = lsp_throttle_completions.clone();
9230 async move {
9231 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9232 cx.background_executor()
9233 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9234 .await;
9235 }
9236 Ok(Some(lsp::CompletionResponse::Array(vec![
9237 lsp::CompletionItem {
9238 label: "first".into(),
9239 ..lsp::CompletionItem::default()
9240 },
9241 lsp::CompletionItem {
9242 label: "last".into(),
9243 ..lsp::CompletionItem::default()
9244 },
9245 ])))
9246 }
9247 });
9248
9249 cx.set_state(indoc! {"
9250 oneˇ
9251 two
9252 three
9253 "});
9254 cx.simulate_keystroke(".");
9255 cx.executor().run_until_parked();
9256 cx.condition(|editor, _| editor.context_menu_visible())
9257 .await;
9258 cx.update_editor(|editor, window, cx| {
9259 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9260 {
9261 assert_eq!(
9262 completion_menu_entries(&menu),
9263 &["first", "last"],
9264 "When LSP server is fast to reply, no fallback word completions are used"
9265 );
9266 } else {
9267 panic!("expected completion menu to be open");
9268 }
9269 editor.cancel(&Cancel, window, cx);
9270 });
9271 cx.executor().run_until_parked();
9272 cx.condition(|editor, _| !editor.context_menu_visible())
9273 .await;
9274
9275 throttle_completions.store(true, atomic::Ordering::Release);
9276 cx.simulate_keystroke(".");
9277 cx.executor()
9278 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9279 cx.executor().run_until_parked();
9280 cx.condition(|editor, _| editor.context_menu_visible())
9281 .await;
9282 cx.update_editor(|editor, _, _| {
9283 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9284 {
9285 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9286 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9287 } else {
9288 panic!("expected completion menu to be open");
9289 }
9290 });
9291}
9292
9293#[gpui::test]
9294async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9295 init_test(cx, |language_settings| {
9296 language_settings.defaults.completions = Some(CompletionSettings {
9297 words: WordsCompletionMode::Enabled,
9298 lsp: true,
9299 lsp_fetch_timeout_ms: 0,
9300 });
9301 });
9302
9303 let mut cx = EditorLspTestContext::new_rust(
9304 lsp::ServerCapabilities {
9305 completion_provider: Some(lsp::CompletionOptions {
9306 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9307 ..lsp::CompletionOptions::default()
9308 }),
9309 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9310 ..lsp::ServerCapabilities::default()
9311 },
9312 cx,
9313 )
9314 .await;
9315
9316 let _completion_requests_handler =
9317 cx.lsp
9318 .server
9319 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9320 Ok(Some(lsp::CompletionResponse::Array(vec![
9321 lsp::CompletionItem {
9322 label: "first".into(),
9323 ..lsp::CompletionItem::default()
9324 },
9325 lsp::CompletionItem {
9326 label: "last".into(),
9327 ..lsp::CompletionItem::default()
9328 },
9329 ])))
9330 });
9331
9332 cx.set_state(indoc! {"ˇ
9333 first
9334 last
9335 second
9336 "});
9337 cx.simulate_keystroke(".");
9338 cx.executor().run_until_parked();
9339 cx.condition(|editor, _| editor.context_menu_visible())
9340 .await;
9341 cx.update_editor(|editor, window, cx| {
9342 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9343 {
9344 assert_eq!(
9345 completion_menu_entries(&menu),
9346 &["first", "last", "second"],
9347 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9348 );
9349 } else {
9350 panic!("expected completion menu to be open");
9351 }
9352 editor.cancel(&Cancel, window, cx);
9353 });
9354}
9355
9356#[gpui::test]
9357async fn test_multiline_completion(cx: &mut TestAppContext) {
9358 init_test(cx, |_| {});
9359
9360 let fs = FakeFs::new(cx.executor());
9361 fs.insert_tree(
9362 path!("/a"),
9363 json!({
9364 "main.ts": "a",
9365 }),
9366 )
9367 .await;
9368
9369 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9370 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9371 let typescript_language = Arc::new(Language::new(
9372 LanguageConfig {
9373 name: "TypeScript".into(),
9374 matcher: LanguageMatcher {
9375 path_suffixes: vec!["ts".to_string()],
9376 ..LanguageMatcher::default()
9377 },
9378 line_comments: vec!["// ".into()],
9379 ..LanguageConfig::default()
9380 },
9381 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9382 ));
9383 language_registry.add(typescript_language.clone());
9384 let mut fake_servers = language_registry.register_fake_lsp(
9385 "TypeScript",
9386 FakeLspAdapter {
9387 capabilities: lsp::ServerCapabilities {
9388 completion_provider: Some(lsp::CompletionOptions {
9389 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9390 ..lsp::CompletionOptions::default()
9391 }),
9392 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9393 ..lsp::ServerCapabilities::default()
9394 },
9395 // Emulate vtsls label generation
9396 label_for_completion: Some(Box::new(|item, _| {
9397 let text = if let Some(description) = item
9398 .label_details
9399 .as_ref()
9400 .and_then(|label_details| label_details.description.as_ref())
9401 {
9402 format!("{} {}", item.label, description)
9403 } else if let Some(detail) = &item.detail {
9404 format!("{} {}", item.label, detail)
9405 } else {
9406 item.label.clone()
9407 };
9408 let len = text.len();
9409 Some(language::CodeLabel {
9410 text,
9411 runs: Vec::new(),
9412 filter_range: 0..len,
9413 })
9414 })),
9415 ..FakeLspAdapter::default()
9416 },
9417 );
9418 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9419 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9420 let worktree_id = workspace
9421 .update(cx, |workspace, _window, cx| {
9422 workspace.project().update(cx, |project, cx| {
9423 project.worktrees(cx).next().unwrap().read(cx).id()
9424 })
9425 })
9426 .unwrap();
9427 let _buffer = project
9428 .update(cx, |project, cx| {
9429 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9430 })
9431 .await
9432 .unwrap();
9433 let editor = workspace
9434 .update(cx, |workspace, window, cx| {
9435 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9436 })
9437 .unwrap()
9438 .await
9439 .unwrap()
9440 .downcast::<Editor>()
9441 .unwrap();
9442 let fake_server = fake_servers.next().await.unwrap();
9443
9444 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9445 let multiline_label_2 = "a\nb\nc\n";
9446 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9447 let multiline_description = "d\ne\nf\n";
9448 let multiline_detail_2 = "g\nh\ni\n";
9449
9450 let mut completion_handle =
9451 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9452 Ok(Some(lsp::CompletionResponse::Array(vec![
9453 lsp::CompletionItem {
9454 label: multiline_label.to_string(),
9455 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9456 range: lsp::Range {
9457 start: lsp::Position {
9458 line: params.text_document_position.position.line,
9459 character: params.text_document_position.position.character,
9460 },
9461 end: lsp::Position {
9462 line: params.text_document_position.position.line,
9463 character: params.text_document_position.position.character,
9464 },
9465 },
9466 new_text: "new_text_1".to_string(),
9467 })),
9468 ..lsp::CompletionItem::default()
9469 },
9470 lsp::CompletionItem {
9471 label: "single line label 1".to_string(),
9472 detail: Some(multiline_detail.to_string()),
9473 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9474 range: lsp::Range {
9475 start: lsp::Position {
9476 line: params.text_document_position.position.line,
9477 character: params.text_document_position.position.character,
9478 },
9479 end: lsp::Position {
9480 line: params.text_document_position.position.line,
9481 character: params.text_document_position.position.character,
9482 },
9483 },
9484 new_text: "new_text_2".to_string(),
9485 })),
9486 ..lsp::CompletionItem::default()
9487 },
9488 lsp::CompletionItem {
9489 label: "single line label 2".to_string(),
9490 label_details: Some(lsp::CompletionItemLabelDetails {
9491 description: Some(multiline_description.to_string()),
9492 detail: None,
9493 }),
9494 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9495 range: lsp::Range {
9496 start: lsp::Position {
9497 line: params.text_document_position.position.line,
9498 character: params.text_document_position.position.character,
9499 },
9500 end: lsp::Position {
9501 line: params.text_document_position.position.line,
9502 character: params.text_document_position.position.character,
9503 },
9504 },
9505 new_text: "new_text_2".to_string(),
9506 })),
9507 ..lsp::CompletionItem::default()
9508 },
9509 lsp::CompletionItem {
9510 label: multiline_label_2.to_string(),
9511 detail: Some(multiline_detail_2.to_string()),
9512 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9513 range: lsp::Range {
9514 start: lsp::Position {
9515 line: params.text_document_position.position.line,
9516 character: params.text_document_position.position.character,
9517 },
9518 end: lsp::Position {
9519 line: params.text_document_position.position.line,
9520 character: params.text_document_position.position.character,
9521 },
9522 },
9523 new_text: "new_text_3".to_string(),
9524 })),
9525 ..lsp::CompletionItem::default()
9526 },
9527 lsp::CompletionItem {
9528 label: "Label with many spaces and \t but without newlines".to_string(),
9529 detail: Some(
9530 "Details with many spaces and \t but without newlines".to_string(),
9531 ),
9532 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9533 range: lsp::Range {
9534 start: lsp::Position {
9535 line: params.text_document_position.position.line,
9536 character: params.text_document_position.position.character,
9537 },
9538 end: lsp::Position {
9539 line: params.text_document_position.position.line,
9540 character: params.text_document_position.position.character,
9541 },
9542 },
9543 new_text: "new_text_4".to_string(),
9544 })),
9545 ..lsp::CompletionItem::default()
9546 },
9547 ])))
9548 });
9549
9550 editor.update_in(cx, |editor, window, cx| {
9551 cx.focus_self(window);
9552 editor.move_to_end(&MoveToEnd, window, cx);
9553 editor.handle_input(".", window, cx);
9554 });
9555 cx.run_until_parked();
9556 completion_handle.next().await.unwrap();
9557
9558 editor.update(cx, |editor, _| {
9559 assert!(editor.context_menu_visible());
9560 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9561 {
9562 let completion_labels = menu
9563 .completions
9564 .borrow()
9565 .iter()
9566 .map(|c| c.label.text.clone())
9567 .collect::<Vec<_>>();
9568 assert_eq!(
9569 completion_labels,
9570 &[
9571 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9572 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9573 "single line label 2 d e f ",
9574 "a b c g h i ",
9575 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9576 ],
9577 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9578 );
9579
9580 for completion in menu
9581 .completions
9582 .borrow()
9583 .iter() {
9584 assert_eq!(
9585 completion.label.filter_range,
9586 0..completion.label.text.len(),
9587 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9588 );
9589 }
9590
9591 } else {
9592 panic!("expected completion menu to be open");
9593 }
9594 });
9595}
9596
9597#[gpui::test]
9598async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9599 init_test(cx, |_| {});
9600 let mut cx = EditorLspTestContext::new_rust(
9601 lsp::ServerCapabilities {
9602 completion_provider: Some(lsp::CompletionOptions {
9603 trigger_characters: Some(vec![".".to_string()]),
9604 ..Default::default()
9605 }),
9606 ..Default::default()
9607 },
9608 cx,
9609 )
9610 .await;
9611 cx.lsp
9612 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9613 Ok(Some(lsp::CompletionResponse::Array(vec![
9614 lsp::CompletionItem {
9615 label: "first".into(),
9616 ..Default::default()
9617 },
9618 lsp::CompletionItem {
9619 label: "last".into(),
9620 ..Default::default()
9621 },
9622 ])))
9623 });
9624 cx.set_state("variableˇ");
9625 cx.simulate_keystroke(".");
9626 cx.executor().run_until_parked();
9627
9628 cx.update_editor(|editor, _, _| {
9629 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9630 {
9631 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9632 } else {
9633 panic!("expected completion menu to be open");
9634 }
9635 });
9636
9637 cx.update_editor(|editor, window, cx| {
9638 editor.move_page_down(&MovePageDown::default(), window, cx);
9639 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9640 {
9641 assert!(
9642 menu.selected_item == 1,
9643 "expected PageDown to select the last item from the context menu"
9644 );
9645 } else {
9646 panic!("expected completion menu to stay open after PageDown");
9647 }
9648 });
9649
9650 cx.update_editor(|editor, window, cx| {
9651 editor.move_page_up(&MovePageUp::default(), window, cx);
9652 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9653 {
9654 assert!(
9655 menu.selected_item == 0,
9656 "expected PageUp to select the first item from the context menu"
9657 );
9658 } else {
9659 panic!("expected completion menu to stay open after PageUp");
9660 }
9661 });
9662}
9663
9664#[gpui::test]
9665async fn test_completion_sort(cx: &mut TestAppContext) {
9666 init_test(cx, |_| {});
9667 let mut cx = EditorLspTestContext::new_rust(
9668 lsp::ServerCapabilities {
9669 completion_provider: Some(lsp::CompletionOptions {
9670 trigger_characters: Some(vec![".".to_string()]),
9671 ..Default::default()
9672 }),
9673 ..Default::default()
9674 },
9675 cx,
9676 )
9677 .await;
9678 cx.lsp
9679 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9680 Ok(Some(lsp::CompletionResponse::Array(vec![
9681 lsp::CompletionItem {
9682 label: "Range".into(),
9683 sort_text: Some("a".into()),
9684 ..Default::default()
9685 },
9686 lsp::CompletionItem {
9687 label: "r".into(),
9688 sort_text: Some("b".into()),
9689 ..Default::default()
9690 },
9691 lsp::CompletionItem {
9692 label: "ret".into(),
9693 sort_text: Some("c".into()),
9694 ..Default::default()
9695 },
9696 lsp::CompletionItem {
9697 label: "return".into(),
9698 sort_text: Some("d".into()),
9699 ..Default::default()
9700 },
9701 lsp::CompletionItem {
9702 label: "slice".into(),
9703 sort_text: Some("d".into()),
9704 ..Default::default()
9705 },
9706 ])))
9707 });
9708 cx.set_state("rˇ");
9709 cx.executor().run_until_parked();
9710 cx.update_editor(|editor, window, cx| {
9711 editor.show_completions(
9712 &ShowCompletions {
9713 trigger: Some("r".into()),
9714 },
9715 window,
9716 cx,
9717 );
9718 });
9719 cx.executor().run_until_parked();
9720
9721 cx.update_editor(|editor, _, _| {
9722 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9723 {
9724 assert_eq!(
9725 completion_menu_entries(&menu),
9726 &["r", "ret", "Range", "return"]
9727 );
9728 } else {
9729 panic!("expected completion menu to be open");
9730 }
9731 });
9732}
9733
9734#[gpui::test]
9735async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9736 init_test(cx, |_| {});
9737
9738 let mut cx = EditorLspTestContext::new_rust(
9739 lsp::ServerCapabilities {
9740 completion_provider: Some(lsp::CompletionOptions {
9741 trigger_characters: Some(vec![".".to_string()]),
9742 resolve_provider: Some(true),
9743 ..Default::default()
9744 }),
9745 ..Default::default()
9746 },
9747 cx,
9748 )
9749 .await;
9750
9751 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9752 cx.simulate_keystroke(".");
9753 let completion_item = lsp::CompletionItem {
9754 label: "Some".into(),
9755 kind: Some(lsp::CompletionItemKind::SNIPPET),
9756 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9757 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9758 kind: lsp::MarkupKind::Markdown,
9759 value: "```rust\nSome(2)\n```".to_string(),
9760 })),
9761 deprecated: Some(false),
9762 sort_text: Some("Some".to_string()),
9763 filter_text: Some("Some".to_string()),
9764 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9765 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9766 range: lsp::Range {
9767 start: lsp::Position {
9768 line: 0,
9769 character: 22,
9770 },
9771 end: lsp::Position {
9772 line: 0,
9773 character: 22,
9774 },
9775 },
9776 new_text: "Some(2)".to_string(),
9777 })),
9778 additional_text_edits: Some(vec![lsp::TextEdit {
9779 range: lsp::Range {
9780 start: lsp::Position {
9781 line: 0,
9782 character: 20,
9783 },
9784 end: lsp::Position {
9785 line: 0,
9786 character: 22,
9787 },
9788 },
9789 new_text: "".to_string(),
9790 }]),
9791 ..Default::default()
9792 };
9793
9794 let closure_completion_item = completion_item.clone();
9795 let counter = Arc::new(AtomicUsize::new(0));
9796 let counter_clone = counter.clone();
9797 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9798 let task_completion_item = closure_completion_item.clone();
9799 counter_clone.fetch_add(1, atomic::Ordering::Release);
9800 async move {
9801 Ok(Some(lsp::CompletionResponse::Array(vec![
9802 task_completion_item,
9803 ])))
9804 }
9805 });
9806
9807 cx.condition(|editor, _| editor.context_menu_visible())
9808 .await;
9809 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9810 assert!(request.next().await.is_some());
9811 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9812
9813 cx.simulate_keystroke("S");
9814 cx.simulate_keystroke("o");
9815 cx.simulate_keystroke("m");
9816 cx.condition(|editor, _| editor.context_menu_visible())
9817 .await;
9818 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9819 assert!(request.next().await.is_some());
9820 assert!(request.next().await.is_some());
9821 assert!(request.next().await.is_some());
9822 request.close();
9823 assert!(request.next().await.is_none());
9824 assert_eq!(
9825 counter.load(atomic::Ordering::Acquire),
9826 4,
9827 "With the completions menu open, only one LSP request should happen per input"
9828 );
9829}
9830
9831#[gpui::test]
9832async fn test_toggle_comment(cx: &mut TestAppContext) {
9833 init_test(cx, |_| {});
9834 let mut cx = EditorTestContext::new(cx).await;
9835 let language = Arc::new(Language::new(
9836 LanguageConfig {
9837 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9838 ..Default::default()
9839 },
9840 Some(tree_sitter_rust::LANGUAGE.into()),
9841 ));
9842 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9843
9844 // If multiple selections intersect a line, the line is only toggled once.
9845 cx.set_state(indoc! {"
9846 fn a() {
9847 «//b();
9848 ˇ»// «c();
9849 //ˇ» d();
9850 }
9851 "});
9852
9853 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9854
9855 cx.assert_editor_state(indoc! {"
9856 fn a() {
9857 «b();
9858 c();
9859 ˇ» d();
9860 }
9861 "});
9862
9863 // The comment prefix is inserted at the same column for every line in a
9864 // selection.
9865 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9866
9867 cx.assert_editor_state(indoc! {"
9868 fn a() {
9869 // «b();
9870 // c();
9871 ˇ»// d();
9872 }
9873 "});
9874
9875 // If a selection ends at the beginning of a line, that line is not toggled.
9876 cx.set_selections_state(indoc! {"
9877 fn a() {
9878 // b();
9879 «// c();
9880 ˇ» // d();
9881 }
9882 "});
9883
9884 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9885
9886 cx.assert_editor_state(indoc! {"
9887 fn a() {
9888 // b();
9889 «c();
9890 ˇ» // d();
9891 }
9892 "});
9893
9894 // If a selection span a single line and is empty, the line is toggled.
9895 cx.set_state(indoc! {"
9896 fn a() {
9897 a();
9898 b();
9899 ˇ
9900 }
9901 "});
9902
9903 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9904
9905 cx.assert_editor_state(indoc! {"
9906 fn a() {
9907 a();
9908 b();
9909 //•ˇ
9910 }
9911 "});
9912
9913 // If a selection span multiple lines, empty lines are not toggled.
9914 cx.set_state(indoc! {"
9915 fn a() {
9916 «a();
9917
9918 c();ˇ»
9919 }
9920 "});
9921
9922 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9923
9924 cx.assert_editor_state(indoc! {"
9925 fn a() {
9926 // «a();
9927
9928 // c();ˇ»
9929 }
9930 "});
9931
9932 // If a selection includes multiple comment prefixes, all lines are uncommented.
9933 cx.set_state(indoc! {"
9934 fn a() {
9935 «// a();
9936 /// b();
9937 //! c();ˇ»
9938 }
9939 "});
9940
9941 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9942
9943 cx.assert_editor_state(indoc! {"
9944 fn a() {
9945 «a();
9946 b();
9947 c();ˇ»
9948 }
9949 "});
9950}
9951
9952#[gpui::test]
9953async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9954 init_test(cx, |_| {});
9955 let mut cx = EditorTestContext::new(cx).await;
9956 let language = Arc::new(Language::new(
9957 LanguageConfig {
9958 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9959 ..Default::default()
9960 },
9961 Some(tree_sitter_rust::LANGUAGE.into()),
9962 ));
9963 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9964
9965 let toggle_comments = &ToggleComments {
9966 advance_downwards: false,
9967 ignore_indent: true,
9968 };
9969
9970 // If multiple selections intersect a line, the line is only toggled once.
9971 cx.set_state(indoc! {"
9972 fn a() {
9973 // «b();
9974 // c();
9975 // ˇ» d();
9976 }
9977 "});
9978
9979 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9980
9981 cx.assert_editor_state(indoc! {"
9982 fn a() {
9983 «b();
9984 c();
9985 ˇ» d();
9986 }
9987 "});
9988
9989 // The comment prefix is inserted at the beginning of each line
9990 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9991
9992 cx.assert_editor_state(indoc! {"
9993 fn a() {
9994 // «b();
9995 // c();
9996 // ˇ» d();
9997 }
9998 "});
9999
10000 // If a selection ends at the beginning of a line, that line is not toggled.
10001 cx.set_selections_state(indoc! {"
10002 fn a() {
10003 // b();
10004 // «c();
10005 ˇ»// d();
10006 }
10007 "});
10008
10009 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10010
10011 cx.assert_editor_state(indoc! {"
10012 fn a() {
10013 // b();
10014 «c();
10015 ˇ»// d();
10016 }
10017 "});
10018
10019 // If a selection span a single line and is empty, the line is toggled.
10020 cx.set_state(indoc! {"
10021 fn a() {
10022 a();
10023 b();
10024 ˇ
10025 }
10026 "});
10027
10028 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10029
10030 cx.assert_editor_state(indoc! {"
10031 fn a() {
10032 a();
10033 b();
10034 //ˇ
10035 }
10036 "});
10037
10038 // If a selection span multiple lines, empty lines are not toggled.
10039 cx.set_state(indoc! {"
10040 fn a() {
10041 «a();
10042
10043 c();ˇ»
10044 }
10045 "});
10046
10047 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10048
10049 cx.assert_editor_state(indoc! {"
10050 fn a() {
10051 // «a();
10052
10053 // c();ˇ»
10054 }
10055 "});
10056
10057 // If a selection includes multiple comment prefixes, all lines are uncommented.
10058 cx.set_state(indoc! {"
10059 fn a() {
10060 // «a();
10061 /// b();
10062 //! c();ˇ»
10063 }
10064 "});
10065
10066 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10067
10068 cx.assert_editor_state(indoc! {"
10069 fn a() {
10070 «a();
10071 b();
10072 c();ˇ»
10073 }
10074 "});
10075}
10076
10077#[gpui::test]
10078async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10079 init_test(cx, |_| {});
10080
10081 let language = Arc::new(Language::new(
10082 LanguageConfig {
10083 line_comments: vec!["// ".into()],
10084 ..Default::default()
10085 },
10086 Some(tree_sitter_rust::LANGUAGE.into()),
10087 ));
10088
10089 let mut cx = EditorTestContext::new(cx).await;
10090
10091 cx.language_registry().add(language.clone());
10092 cx.update_buffer(|buffer, cx| {
10093 buffer.set_language(Some(language), cx);
10094 });
10095
10096 let toggle_comments = &ToggleComments {
10097 advance_downwards: true,
10098 ignore_indent: false,
10099 };
10100
10101 // Single cursor on one line -> advance
10102 // Cursor moves horizontally 3 characters as well on non-blank line
10103 cx.set_state(indoc!(
10104 "fn a() {
10105 ˇdog();
10106 cat();
10107 }"
10108 ));
10109 cx.update_editor(|editor, window, cx| {
10110 editor.toggle_comments(toggle_comments, window, cx);
10111 });
10112 cx.assert_editor_state(indoc!(
10113 "fn a() {
10114 // dog();
10115 catˇ();
10116 }"
10117 ));
10118
10119 // Single selection on one line -> don't advance
10120 cx.set_state(indoc!(
10121 "fn a() {
10122 «dog()ˇ»;
10123 cat();
10124 }"
10125 ));
10126 cx.update_editor(|editor, window, cx| {
10127 editor.toggle_comments(toggle_comments, window, cx);
10128 });
10129 cx.assert_editor_state(indoc!(
10130 "fn a() {
10131 // «dog()ˇ»;
10132 cat();
10133 }"
10134 ));
10135
10136 // Multiple cursors on one line -> advance
10137 cx.set_state(indoc!(
10138 "fn a() {
10139 ˇdˇog();
10140 cat();
10141 }"
10142 ));
10143 cx.update_editor(|editor, window, cx| {
10144 editor.toggle_comments(toggle_comments, window, cx);
10145 });
10146 cx.assert_editor_state(indoc!(
10147 "fn a() {
10148 // dog();
10149 catˇ(ˇ);
10150 }"
10151 ));
10152
10153 // Multiple cursors on one line, with selection -> don't advance
10154 cx.set_state(indoc!(
10155 "fn a() {
10156 ˇdˇog«()ˇ»;
10157 cat();
10158 }"
10159 ));
10160 cx.update_editor(|editor, window, cx| {
10161 editor.toggle_comments(toggle_comments, window, cx);
10162 });
10163 cx.assert_editor_state(indoc!(
10164 "fn a() {
10165 // ˇdˇog«()ˇ»;
10166 cat();
10167 }"
10168 ));
10169
10170 // Single cursor on one line -> advance
10171 // Cursor moves to column 0 on blank line
10172 cx.set_state(indoc!(
10173 "fn a() {
10174 ˇdog();
10175
10176 cat();
10177 }"
10178 ));
10179 cx.update_editor(|editor, window, cx| {
10180 editor.toggle_comments(toggle_comments, window, cx);
10181 });
10182 cx.assert_editor_state(indoc!(
10183 "fn a() {
10184 // dog();
10185 ˇ
10186 cat();
10187 }"
10188 ));
10189
10190 // Single cursor on one line -> advance
10191 // Cursor starts and ends at column 0
10192 cx.set_state(indoc!(
10193 "fn a() {
10194 ˇ dog();
10195 cat();
10196 }"
10197 ));
10198 cx.update_editor(|editor, window, cx| {
10199 editor.toggle_comments(toggle_comments, window, cx);
10200 });
10201 cx.assert_editor_state(indoc!(
10202 "fn a() {
10203 // dog();
10204 ˇ cat();
10205 }"
10206 ));
10207}
10208
10209#[gpui::test]
10210async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10211 init_test(cx, |_| {});
10212
10213 let mut cx = EditorTestContext::new(cx).await;
10214
10215 let html_language = Arc::new(
10216 Language::new(
10217 LanguageConfig {
10218 name: "HTML".into(),
10219 block_comment: Some(("<!-- ".into(), " -->".into())),
10220 ..Default::default()
10221 },
10222 Some(tree_sitter_html::LANGUAGE.into()),
10223 )
10224 .with_injection_query(
10225 r#"
10226 (script_element
10227 (raw_text) @injection.content
10228 (#set! injection.language "javascript"))
10229 "#,
10230 )
10231 .unwrap(),
10232 );
10233
10234 let javascript_language = Arc::new(Language::new(
10235 LanguageConfig {
10236 name: "JavaScript".into(),
10237 line_comments: vec!["// ".into()],
10238 ..Default::default()
10239 },
10240 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10241 ));
10242
10243 cx.language_registry().add(html_language.clone());
10244 cx.language_registry().add(javascript_language.clone());
10245 cx.update_buffer(|buffer, cx| {
10246 buffer.set_language(Some(html_language), cx);
10247 });
10248
10249 // Toggle comments for empty selections
10250 cx.set_state(
10251 &r#"
10252 <p>A</p>ˇ
10253 <p>B</p>ˇ
10254 <p>C</p>ˇ
10255 "#
10256 .unindent(),
10257 );
10258 cx.update_editor(|editor, window, cx| {
10259 editor.toggle_comments(&ToggleComments::default(), window, cx)
10260 });
10261 cx.assert_editor_state(
10262 &r#"
10263 <!-- <p>A</p>ˇ -->
10264 <!-- <p>B</p>ˇ -->
10265 <!-- <p>C</p>ˇ -->
10266 "#
10267 .unindent(),
10268 );
10269 cx.update_editor(|editor, window, cx| {
10270 editor.toggle_comments(&ToggleComments::default(), window, cx)
10271 });
10272 cx.assert_editor_state(
10273 &r#"
10274 <p>A</p>ˇ
10275 <p>B</p>ˇ
10276 <p>C</p>ˇ
10277 "#
10278 .unindent(),
10279 );
10280
10281 // Toggle comments for mixture of empty and non-empty selections, where
10282 // multiple selections occupy a given line.
10283 cx.set_state(
10284 &r#"
10285 <p>A«</p>
10286 <p>ˇ»B</p>ˇ
10287 <p>C«</p>
10288 <p>ˇ»D</p>ˇ
10289 "#
10290 .unindent(),
10291 );
10292
10293 cx.update_editor(|editor, window, cx| {
10294 editor.toggle_comments(&ToggleComments::default(), window, cx)
10295 });
10296 cx.assert_editor_state(
10297 &r#"
10298 <!-- <p>A«</p>
10299 <p>ˇ»B</p>ˇ -->
10300 <!-- <p>C«</p>
10301 <p>ˇ»D</p>ˇ -->
10302 "#
10303 .unindent(),
10304 );
10305 cx.update_editor(|editor, window, cx| {
10306 editor.toggle_comments(&ToggleComments::default(), window, cx)
10307 });
10308 cx.assert_editor_state(
10309 &r#"
10310 <p>A«</p>
10311 <p>ˇ»B</p>ˇ
10312 <p>C«</p>
10313 <p>ˇ»D</p>ˇ
10314 "#
10315 .unindent(),
10316 );
10317
10318 // Toggle comments when different languages are active for different
10319 // selections.
10320 cx.set_state(
10321 &r#"
10322 ˇ<script>
10323 ˇvar x = new Y();
10324 ˇ</script>
10325 "#
10326 .unindent(),
10327 );
10328 cx.executor().run_until_parked();
10329 cx.update_editor(|editor, window, cx| {
10330 editor.toggle_comments(&ToggleComments::default(), window, cx)
10331 });
10332 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10333 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10334 cx.assert_editor_state(
10335 &r#"
10336 <!-- ˇ<script> -->
10337 // ˇvar x = new Y();
10338 <!-- ˇ</script> -->
10339 "#
10340 .unindent(),
10341 );
10342}
10343
10344#[gpui::test]
10345fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10346 init_test(cx, |_| {});
10347
10348 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10349 let multibuffer = cx.new(|cx| {
10350 let mut multibuffer = MultiBuffer::new(ReadWrite);
10351 multibuffer.push_excerpts(
10352 buffer.clone(),
10353 [
10354 ExcerptRange {
10355 context: Point::new(0, 0)..Point::new(0, 4),
10356 primary: None,
10357 },
10358 ExcerptRange {
10359 context: Point::new(1, 0)..Point::new(1, 4),
10360 primary: None,
10361 },
10362 ],
10363 cx,
10364 );
10365 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10366 multibuffer
10367 });
10368
10369 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10370 editor.update_in(cx, |editor, window, cx| {
10371 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10372 editor.change_selections(None, window, cx, |s| {
10373 s.select_ranges([
10374 Point::new(0, 0)..Point::new(0, 0),
10375 Point::new(1, 0)..Point::new(1, 0),
10376 ])
10377 });
10378
10379 editor.handle_input("X", window, cx);
10380 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10381 assert_eq!(
10382 editor.selections.ranges(cx),
10383 [
10384 Point::new(0, 1)..Point::new(0, 1),
10385 Point::new(1, 1)..Point::new(1, 1),
10386 ]
10387 );
10388
10389 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10390 editor.change_selections(None, window, cx, |s| {
10391 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10392 });
10393 editor.backspace(&Default::default(), window, cx);
10394 assert_eq!(editor.text(cx), "Xa\nbbb");
10395 assert_eq!(
10396 editor.selections.ranges(cx),
10397 [Point::new(1, 0)..Point::new(1, 0)]
10398 );
10399
10400 editor.change_selections(None, window, cx, |s| {
10401 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10402 });
10403 editor.backspace(&Default::default(), window, cx);
10404 assert_eq!(editor.text(cx), "X\nbb");
10405 assert_eq!(
10406 editor.selections.ranges(cx),
10407 [Point::new(0, 1)..Point::new(0, 1)]
10408 );
10409 });
10410}
10411
10412#[gpui::test]
10413fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10414 init_test(cx, |_| {});
10415
10416 let markers = vec![('[', ']').into(), ('(', ')').into()];
10417 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10418 indoc! {"
10419 [aaaa
10420 (bbbb]
10421 cccc)",
10422 },
10423 markers.clone(),
10424 );
10425 let excerpt_ranges = markers.into_iter().map(|marker| {
10426 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10427 ExcerptRange {
10428 context,
10429 primary: None,
10430 }
10431 });
10432 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10433 let multibuffer = cx.new(|cx| {
10434 let mut multibuffer = MultiBuffer::new(ReadWrite);
10435 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10436 multibuffer
10437 });
10438
10439 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10440 editor.update_in(cx, |editor, window, cx| {
10441 let (expected_text, selection_ranges) = marked_text_ranges(
10442 indoc! {"
10443 aaaa
10444 bˇbbb
10445 bˇbbˇb
10446 cccc"
10447 },
10448 true,
10449 );
10450 assert_eq!(editor.text(cx), expected_text);
10451 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10452
10453 editor.handle_input("X", window, cx);
10454
10455 let (expected_text, expected_selections) = marked_text_ranges(
10456 indoc! {"
10457 aaaa
10458 bXˇbbXb
10459 bXˇbbXˇb
10460 cccc"
10461 },
10462 false,
10463 );
10464 assert_eq!(editor.text(cx), expected_text);
10465 assert_eq!(editor.selections.ranges(cx), expected_selections);
10466
10467 editor.newline(&Newline, window, cx);
10468 let (expected_text, expected_selections) = marked_text_ranges(
10469 indoc! {"
10470 aaaa
10471 bX
10472 ˇbbX
10473 b
10474 bX
10475 ˇbbX
10476 ˇb
10477 cccc"
10478 },
10479 false,
10480 );
10481 assert_eq!(editor.text(cx), expected_text);
10482 assert_eq!(editor.selections.ranges(cx), expected_selections);
10483 });
10484}
10485
10486#[gpui::test]
10487fn test_refresh_selections(cx: &mut TestAppContext) {
10488 init_test(cx, |_| {});
10489
10490 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10491 let mut excerpt1_id = None;
10492 let multibuffer = cx.new(|cx| {
10493 let mut multibuffer = MultiBuffer::new(ReadWrite);
10494 excerpt1_id = multibuffer
10495 .push_excerpts(
10496 buffer.clone(),
10497 [
10498 ExcerptRange {
10499 context: Point::new(0, 0)..Point::new(1, 4),
10500 primary: None,
10501 },
10502 ExcerptRange {
10503 context: Point::new(1, 0)..Point::new(2, 4),
10504 primary: None,
10505 },
10506 ],
10507 cx,
10508 )
10509 .into_iter()
10510 .next();
10511 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10512 multibuffer
10513 });
10514
10515 let editor = cx.add_window(|window, cx| {
10516 let mut editor = build_editor(multibuffer.clone(), window, cx);
10517 let snapshot = editor.snapshot(window, cx);
10518 editor.change_selections(None, window, cx, |s| {
10519 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10520 });
10521 editor.begin_selection(
10522 Point::new(2, 1).to_display_point(&snapshot),
10523 true,
10524 1,
10525 window,
10526 cx,
10527 );
10528 assert_eq!(
10529 editor.selections.ranges(cx),
10530 [
10531 Point::new(1, 3)..Point::new(1, 3),
10532 Point::new(2, 1)..Point::new(2, 1),
10533 ]
10534 );
10535 editor
10536 });
10537
10538 // Refreshing selections is a no-op when excerpts haven't changed.
10539 _ = editor.update(cx, |editor, window, cx| {
10540 editor.change_selections(None, window, cx, |s| s.refresh());
10541 assert_eq!(
10542 editor.selections.ranges(cx),
10543 [
10544 Point::new(1, 3)..Point::new(1, 3),
10545 Point::new(2, 1)..Point::new(2, 1),
10546 ]
10547 );
10548 });
10549
10550 multibuffer.update(cx, |multibuffer, cx| {
10551 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10552 });
10553 _ = editor.update(cx, |editor, window, cx| {
10554 // Removing an excerpt causes the first selection to become degenerate.
10555 assert_eq!(
10556 editor.selections.ranges(cx),
10557 [
10558 Point::new(0, 0)..Point::new(0, 0),
10559 Point::new(0, 1)..Point::new(0, 1)
10560 ]
10561 );
10562
10563 // Refreshing selections will relocate the first selection to the original buffer
10564 // location.
10565 editor.change_selections(None, window, cx, |s| s.refresh());
10566 assert_eq!(
10567 editor.selections.ranges(cx),
10568 [
10569 Point::new(0, 1)..Point::new(0, 1),
10570 Point::new(0, 3)..Point::new(0, 3)
10571 ]
10572 );
10573 assert!(editor.selections.pending_anchor().is_some());
10574 });
10575}
10576
10577#[gpui::test]
10578fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10579 init_test(cx, |_| {});
10580
10581 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10582 let mut excerpt1_id = None;
10583 let multibuffer = cx.new(|cx| {
10584 let mut multibuffer = MultiBuffer::new(ReadWrite);
10585 excerpt1_id = multibuffer
10586 .push_excerpts(
10587 buffer.clone(),
10588 [
10589 ExcerptRange {
10590 context: Point::new(0, 0)..Point::new(1, 4),
10591 primary: None,
10592 },
10593 ExcerptRange {
10594 context: Point::new(1, 0)..Point::new(2, 4),
10595 primary: None,
10596 },
10597 ],
10598 cx,
10599 )
10600 .into_iter()
10601 .next();
10602 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10603 multibuffer
10604 });
10605
10606 let editor = cx.add_window(|window, cx| {
10607 let mut editor = build_editor(multibuffer.clone(), window, cx);
10608 let snapshot = editor.snapshot(window, cx);
10609 editor.begin_selection(
10610 Point::new(1, 3).to_display_point(&snapshot),
10611 false,
10612 1,
10613 window,
10614 cx,
10615 );
10616 assert_eq!(
10617 editor.selections.ranges(cx),
10618 [Point::new(1, 3)..Point::new(1, 3)]
10619 );
10620 editor
10621 });
10622
10623 multibuffer.update(cx, |multibuffer, cx| {
10624 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10625 });
10626 _ = editor.update(cx, |editor, window, cx| {
10627 assert_eq!(
10628 editor.selections.ranges(cx),
10629 [Point::new(0, 0)..Point::new(0, 0)]
10630 );
10631
10632 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10633 editor.change_selections(None, window, cx, |s| s.refresh());
10634 assert_eq!(
10635 editor.selections.ranges(cx),
10636 [Point::new(0, 3)..Point::new(0, 3)]
10637 );
10638 assert!(editor.selections.pending_anchor().is_some());
10639 });
10640}
10641
10642#[gpui::test]
10643async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10644 init_test(cx, |_| {});
10645
10646 let language = Arc::new(
10647 Language::new(
10648 LanguageConfig {
10649 brackets: BracketPairConfig {
10650 pairs: vec![
10651 BracketPair {
10652 start: "{".to_string(),
10653 end: "}".to_string(),
10654 close: true,
10655 surround: true,
10656 newline: true,
10657 },
10658 BracketPair {
10659 start: "/* ".to_string(),
10660 end: " */".to_string(),
10661 close: true,
10662 surround: true,
10663 newline: true,
10664 },
10665 ],
10666 ..Default::default()
10667 },
10668 ..Default::default()
10669 },
10670 Some(tree_sitter_rust::LANGUAGE.into()),
10671 )
10672 .with_indents_query("")
10673 .unwrap(),
10674 );
10675
10676 let text = concat!(
10677 "{ }\n", //
10678 " x\n", //
10679 " /* */\n", //
10680 "x\n", //
10681 "{{} }\n", //
10682 );
10683
10684 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10686 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10687 editor
10688 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10689 .await;
10690
10691 editor.update_in(cx, |editor, window, cx| {
10692 editor.change_selections(None, window, cx, |s| {
10693 s.select_display_ranges([
10694 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10695 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10696 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10697 ])
10698 });
10699 editor.newline(&Newline, window, cx);
10700
10701 assert_eq!(
10702 editor.buffer().read(cx).read(cx).text(),
10703 concat!(
10704 "{ \n", // Suppress rustfmt
10705 "\n", //
10706 "}\n", //
10707 " x\n", //
10708 " /* \n", //
10709 " \n", //
10710 " */\n", //
10711 "x\n", //
10712 "{{} \n", //
10713 "}\n", //
10714 )
10715 );
10716 });
10717}
10718
10719#[gpui::test]
10720fn test_highlighted_ranges(cx: &mut TestAppContext) {
10721 init_test(cx, |_| {});
10722
10723 let editor = cx.add_window(|window, cx| {
10724 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10725 build_editor(buffer.clone(), window, cx)
10726 });
10727
10728 _ = editor.update(cx, |editor, window, cx| {
10729 struct Type1;
10730 struct Type2;
10731
10732 let buffer = editor.buffer.read(cx).snapshot(cx);
10733
10734 let anchor_range =
10735 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10736
10737 editor.highlight_background::<Type1>(
10738 &[
10739 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10740 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10741 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10742 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10743 ],
10744 |_| Hsla::red(),
10745 cx,
10746 );
10747 editor.highlight_background::<Type2>(
10748 &[
10749 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10750 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10751 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10752 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10753 ],
10754 |_| Hsla::green(),
10755 cx,
10756 );
10757
10758 let snapshot = editor.snapshot(window, cx);
10759 let mut highlighted_ranges = editor.background_highlights_in_range(
10760 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10761 &snapshot,
10762 cx.theme().colors(),
10763 );
10764 // Enforce a consistent ordering based on color without relying on the ordering of the
10765 // highlight's `TypeId` which is non-executor.
10766 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10767 assert_eq!(
10768 highlighted_ranges,
10769 &[
10770 (
10771 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10772 Hsla::red(),
10773 ),
10774 (
10775 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10776 Hsla::red(),
10777 ),
10778 (
10779 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10780 Hsla::green(),
10781 ),
10782 (
10783 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10784 Hsla::green(),
10785 ),
10786 ]
10787 );
10788 assert_eq!(
10789 editor.background_highlights_in_range(
10790 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10791 &snapshot,
10792 cx.theme().colors(),
10793 ),
10794 &[(
10795 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10796 Hsla::red(),
10797 )]
10798 );
10799 });
10800}
10801
10802#[gpui::test]
10803async fn test_following(cx: &mut TestAppContext) {
10804 init_test(cx, |_| {});
10805
10806 let fs = FakeFs::new(cx.executor());
10807 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10808
10809 let buffer = project.update(cx, |project, cx| {
10810 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10811 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10812 });
10813 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10814 let follower = cx.update(|cx| {
10815 cx.open_window(
10816 WindowOptions {
10817 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10818 gpui::Point::new(px(0.), px(0.)),
10819 gpui::Point::new(px(10.), px(80.)),
10820 ))),
10821 ..Default::default()
10822 },
10823 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10824 )
10825 .unwrap()
10826 });
10827
10828 let is_still_following = Rc::new(RefCell::new(true));
10829 let follower_edit_event_count = Rc::new(RefCell::new(0));
10830 let pending_update = Rc::new(RefCell::new(None));
10831 let leader_entity = leader.root(cx).unwrap();
10832 let follower_entity = follower.root(cx).unwrap();
10833 _ = follower.update(cx, {
10834 let update = pending_update.clone();
10835 let is_still_following = is_still_following.clone();
10836 let follower_edit_event_count = follower_edit_event_count.clone();
10837 |_, window, cx| {
10838 cx.subscribe_in(
10839 &leader_entity,
10840 window,
10841 move |_, leader, event, window, cx| {
10842 leader.read(cx).add_event_to_update_proto(
10843 event,
10844 &mut update.borrow_mut(),
10845 window,
10846 cx,
10847 );
10848 },
10849 )
10850 .detach();
10851
10852 cx.subscribe_in(
10853 &follower_entity,
10854 window,
10855 move |_, _, event: &EditorEvent, _window, _cx| {
10856 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10857 *is_still_following.borrow_mut() = false;
10858 }
10859
10860 if let EditorEvent::BufferEdited = event {
10861 *follower_edit_event_count.borrow_mut() += 1;
10862 }
10863 },
10864 )
10865 .detach();
10866 }
10867 });
10868
10869 // Update the selections only
10870 _ = leader.update(cx, |leader, window, cx| {
10871 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10872 });
10873 follower
10874 .update(cx, |follower, window, cx| {
10875 follower.apply_update_proto(
10876 &project,
10877 pending_update.borrow_mut().take().unwrap(),
10878 window,
10879 cx,
10880 )
10881 })
10882 .unwrap()
10883 .await
10884 .unwrap();
10885 _ = follower.update(cx, |follower, _, cx| {
10886 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10887 });
10888 assert!(*is_still_following.borrow());
10889 assert_eq!(*follower_edit_event_count.borrow(), 0);
10890
10891 // Update the scroll position only
10892 _ = leader.update(cx, |leader, window, cx| {
10893 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10894 });
10895 follower
10896 .update(cx, |follower, window, cx| {
10897 follower.apply_update_proto(
10898 &project,
10899 pending_update.borrow_mut().take().unwrap(),
10900 window,
10901 cx,
10902 )
10903 })
10904 .unwrap()
10905 .await
10906 .unwrap();
10907 assert_eq!(
10908 follower
10909 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10910 .unwrap(),
10911 gpui::Point::new(1.5, 3.5)
10912 );
10913 assert!(*is_still_following.borrow());
10914 assert_eq!(*follower_edit_event_count.borrow(), 0);
10915
10916 // Update the selections and scroll position. The follower's scroll position is updated
10917 // via autoscroll, not via the leader's exact scroll position.
10918 _ = leader.update(cx, |leader, window, cx| {
10919 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10920 leader.request_autoscroll(Autoscroll::newest(), cx);
10921 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10922 });
10923 follower
10924 .update(cx, |follower, window, cx| {
10925 follower.apply_update_proto(
10926 &project,
10927 pending_update.borrow_mut().take().unwrap(),
10928 window,
10929 cx,
10930 )
10931 })
10932 .unwrap()
10933 .await
10934 .unwrap();
10935 _ = follower.update(cx, |follower, _, cx| {
10936 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10937 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10938 });
10939 assert!(*is_still_following.borrow());
10940
10941 // Creating a pending selection that precedes another selection
10942 _ = leader.update(cx, |leader, window, cx| {
10943 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10944 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10945 });
10946 follower
10947 .update(cx, |follower, window, cx| {
10948 follower.apply_update_proto(
10949 &project,
10950 pending_update.borrow_mut().take().unwrap(),
10951 window,
10952 cx,
10953 )
10954 })
10955 .unwrap()
10956 .await
10957 .unwrap();
10958 _ = follower.update(cx, |follower, _, cx| {
10959 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10960 });
10961 assert!(*is_still_following.borrow());
10962
10963 // Extend the pending selection so that it surrounds another selection
10964 _ = leader.update(cx, |leader, window, cx| {
10965 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10966 });
10967 follower
10968 .update(cx, |follower, window, cx| {
10969 follower.apply_update_proto(
10970 &project,
10971 pending_update.borrow_mut().take().unwrap(),
10972 window,
10973 cx,
10974 )
10975 })
10976 .unwrap()
10977 .await
10978 .unwrap();
10979 _ = follower.update(cx, |follower, _, cx| {
10980 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10981 });
10982
10983 // Scrolling locally breaks the follow
10984 _ = follower.update(cx, |follower, window, cx| {
10985 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10986 follower.set_scroll_anchor(
10987 ScrollAnchor {
10988 anchor: top_anchor,
10989 offset: gpui::Point::new(0.0, 0.5),
10990 },
10991 window,
10992 cx,
10993 );
10994 });
10995 assert!(!(*is_still_following.borrow()));
10996}
10997
10998#[gpui::test]
10999async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11000 init_test(cx, |_| {});
11001
11002 let fs = FakeFs::new(cx.executor());
11003 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11004 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11005 let pane = workspace
11006 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11007 .unwrap();
11008
11009 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11010
11011 let leader = pane.update_in(cx, |_, window, cx| {
11012 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11013 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11014 });
11015
11016 // Start following the editor when it has no excerpts.
11017 let mut state_message =
11018 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11019 let workspace_entity = workspace.root(cx).unwrap();
11020 let follower_1 = cx
11021 .update_window(*workspace.deref(), |_, window, cx| {
11022 Editor::from_state_proto(
11023 workspace_entity,
11024 ViewId {
11025 creator: Default::default(),
11026 id: 0,
11027 },
11028 &mut state_message,
11029 window,
11030 cx,
11031 )
11032 })
11033 .unwrap()
11034 .unwrap()
11035 .await
11036 .unwrap();
11037
11038 let update_message = Rc::new(RefCell::new(None));
11039 follower_1.update_in(cx, {
11040 let update = update_message.clone();
11041 |_, window, cx| {
11042 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11043 leader.read(cx).add_event_to_update_proto(
11044 event,
11045 &mut update.borrow_mut(),
11046 window,
11047 cx,
11048 );
11049 })
11050 .detach();
11051 }
11052 });
11053
11054 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11055 (
11056 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11057 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11058 )
11059 });
11060
11061 // Insert some excerpts.
11062 leader.update(cx, |leader, cx| {
11063 leader.buffer.update(cx, |multibuffer, cx| {
11064 let excerpt_ids = multibuffer.push_excerpts(
11065 buffer_1.clone(),
11066 [
11067 ExcerptRange {
11068 context: 1..6,
11069 primary: None,
11070 },
11071 ExcerptRange {
11072 context: 12..15,
11073 primary: None,
11074 },
11075 ExcerptRange {
11076 context: 0..3,
11077 primary: None,
11078 },
11079 ],
11080 cx,
11081 );
11082 multibuffer.insert_excerpts_after(
11083 excerpt_ids[0],
11084 buffer_2.clone(),
11085 [
11086 ExcerptRange {
11087 context: 8..12,
11088 primary: None,
11089 },
11090 ExcerptRange {
11091 context: 0..6,
11092 primary: None,
11093 },
11094 ],
11095 cx,
11096 );
11097 });
11098 });
11099
11100 // Apply the update of adding the excerpts.
11101 follower_1
11102 .update_in(cx, |follower, window, cx| {
11103 follower.apply_update_proto(
11104 &project,
11105 update_message.borrow().clone().unwrap(),
11106 window,
11107 cx,
11108 )
11109 })
11110 .await
11111 .unwrap();
11112 assert_eq!(
11113 follower_1.update(cx, |editor, cx| editor.text(cx)),
11114 leader.update(cx, |editor, cx| editor.text(cx))
11115 );
11116 update_message.borrow_mut().take();
11117
11118 // Start following separately after it already has excerpts.
11119 let mut state_message =
11120 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11121 let workspace_entity = workspace.root(cx).unwrap();
11122 let follower_2 = cx
11123 .update_window(*workspace.deref(), |_, window, cx| {
11124 Editor::from_state_proto(
11125 workspace_entity,
11126 ViewId {
11127 creator: Default::default(),
11128 id: 0,
11129 },
11130 &mut state_message,
11131 window,
11132 cx,
11133 )
11134 })
11135 .unwrap()
11136 .unwrap()
11137 .await
11138 .unwrap();
11139 assert_eq!(
11140 follower_2.update(cx, |editor, cx| editor.text(cx)),
11141 leader.update(cx, |editor, cx| editor.text(cx))
11142 );
11143
11144 // Remove some excerpts.
11145 leader.update(cx, |leader, cx| {
11146 leader.buffer.update(cx, |multibuffer, cx| {
11147 let excerpt_ids = multibuffer.excerpt_ids();
11148 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11149 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11150 });
11151 });
11152
11153 // Apply the update of removing the excerpts.
11154 follower_1
11155 .update_in(cx, |follower, window, cx| {
11156 follower.apply_update_proto(
11157 &project,
11158 update_message.borrow().clone().unwrap(),
11159 window,
11160 cx,
11161 )
11162 })
11163 .await
11164 .unwrap();
11165 follower_2
11166 .update_in(cx, |follower, window, cx| {
11167 follower.apply_update_proto(
11168 &project,
11169 update_message.borrow().clone().unwrap(),
11170 window,
11171 cx,
11172 )
11173 })
11174 .await
11175 .unwrap();
11176 update_message.borrow_mut().take();
11177 assert_eq!(
11178 follower_1.update(cx, |editor, cx| editor.text(cx)),
11179 leader.update(cx, |editor, cx| editor.text(cx))
11180 );
11181}
11182
11183#[gpui::test]
11184async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11185 init_test(cx, |_| {});
11186
11187 let mut cx = EditorTestContext::new(cx).await;
11188 let lsp_store =
11189 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11190
11191 cx.set_state(indoc! {"
11192 ˇfn func(abc def: i32) -> u32 {
11193 }
11194 "});
11195
11196 cx.update(|_, cx| {
11197 lsp_store.update(cx, |lsp_store, cx| {
11198 lsp_store
11199 .update_diagnostics(
11200 LanguageServerId(0),
11201 lsp::PublishDiagnosticsParams {
11202 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11203 version: None,
11204 diagnostics: vec![
11205 lsp::Diagnostic {
11206 range: lsp::Range::new(
11207 lsp::Position::new(0, 11),
11208 lsp::Position::new(0, 12),
11209 ),
11210 severity: Some(lsp::DiagnosticSeverity::ERROR),
11211 ..Default::default()
11212 },
11213 lsp::Diagnostic {
11214 range: lsp::Range::new(
11215 lsp::Position::new(0, 12),
11216 lsp::Position::new(0, 15),
11217 ),
11218 severity: Some(lsp::DiagnosticSeverity::ERROR),
11219 ..Default::default()
11220 },
11221 lsp::Diagnostic {
11222 range: lsp::Range::new(
11223 lsp::Position::new(0, 25),
11224 lsp::Position::new(0, 28),
11225 ),
11226 severity: Some(lsp::DiagnosticSeverity::ERROR),
11227 ..Default::default()
11228 },
11229 ],
11230 },
11231 &[],
11232 cx,
11233 )
11234 .unwrap()
11235 });
11236 });
11237
11238 executor.run_until_parked();
11239
11240 cx.update_editor(|editor, window, cx| {
11241 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11242 });
11243
11244 cx.assert_editor_state(indoc! {"
11245 fn func(abc def: i32) -> ˇu32 {
11246 }
11247 "});
11248
11249 cx.update_editor(|editor, window, cx| {
11250 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11251 });
11252
11253 cx.assert_editor_state(indoc! {"
11254 fn func(abc ˇdef: i32) -> u32 {
11255 }
11256 "});
11257
11258 cx.update_editor(|editor, window, cx| {
11259 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11260 });
11261
11262 cx.assert_editor_state(indoc! {"
11263 fn func(abcˇ def: i32) -> u32 {
11264 }
11265 "});
11266
11267 cx.update_editor(|editor, window, cx| {
11268 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11269 });
11270
11271 cx.assert_editor_state(indoc! {"
11272 fn func(abc def: i32) -> ˇu32 {
11273 }
11274 "});
11275}
11276
11277#[gpui::test]
11278async fn cycle_through_same_place_diagnostics(
11279 executor: BackgroundExecutor,
11280 cx: &mut TestAppContext,
11281) {
11282 init_test(cx, |_| {});
11283
11284 let mut cx = EditorTestContext::new(cx).await;
11285 let lsp_store =
11286 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11287
11288 cx.set_state(indoc! {"
11289 ˇfn func(abc def: i32) -> u32 {
11290 }
11291 "});
11292
11293 cx.update(|_, cx| {
11294 lsp_store.update(cx, |lsp_store, cx| {
11295 lsp_store
11296 .update_diagnostics(
11297 LanguageServerId(0),
11298 lsp::PublishDiagnosticsParams {
11299 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11300 version: None,
11301 diagnostics: vec![
11302 lsp::Diagnostic {
11303 range: lsp::Range::new(
11304 lsp::Position::new(0, 11),
11305 lsp::Position::new(0, 12),
11306 ),
11307 severity: Some(lsp::DiagnosticSeverity::ERROR),
11308 ..Default::default()
11309 },
11310 lsp::Diagnostic {
11311 range: lsp::Range::new(
11312 lsp::Position::new(0, 12),
11313 lsp::Position::new(0, 15),
11314 ),
11315 severity: Some(lsp::DiagnosticSeverity::ERROR),
11316 ..Default::default()
11317 },
11318 lsp::Diagnostic {
11319 range: lsp::Range::new(
11320 lsp::Position::new(0, 12),
11321 lsp::Position::new(0, 15),
11322 ),
11323 severity: Some(lsp::DiagnosticSeverity::ERROR),
11324 ..Default::default()
11325 },
11326 lsp::Diagnostic {
11327 range: lsp::Range::new(
11328 lsp::Position::new(0, 25),
11329 lsp::Position::new(0, 28),
11330 ),
11331 severity: Some(lsp::DiagnosticSeverity::ERROR),
11332 ..Default::default()
11333 },
11334 ],
11335 },
11336 &[],
11337 cx,
11338 )
11339 .unwrap()
11340 });
11341 });
11342 executor.run_until_parked();
11343
11344 //// Backward
11345
11346 // Fourth diagnostic
11347 cx.update_editor(|editor, window, cx| {
11348 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11349 });
11350 cx.assert_editor_state(indoc! {"
11351 fn func(abc def: i32) -> ˇu32 {
11352 }
11353 "});
11354
11355 // Third diagnostic
11356 cx.update_editor(|editor, window, cx| {
11357 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11358 });
11359 cx.assert_editor_state(indoc! {"
11360 fn func(abc ˇdef: i32) -> u32 {
11361 }
11362 "});
11363
11364 // Second diagnostic, same place
11365 cx.update_editor(|editor, window, cx| {
11366 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11367 });
11368 cx.assert_editor_state(indoc! {"
11369 fn func(abc ˇdef: i32) -> u32 {
11370 }
11371 "});
11372
11373 // First diagnostic
11374 cx.update_editor(|editor, window, cx| {
11375 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11376 });
11377 cx.assert_editor_state(indoc! {"
11378 fn func(abcˇ def: i32) -> u32 {
11379 }
11380 "});
11381
11382 // Wrapped over, fourth diagnostic
11383 cx.update_editor(|editor, window, cx| {
11384 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11385 });
11386 cx.assert_editor_state(indoc! {"
11387 fn func(abc def: i32) -> ˇu32 {
11388 }
11389 "});
11390
11391 cx.update_editor(|editor, window, cx| {
11392 editor.move_to_beginning(&MoveToBeginning, window, cx);
11393 });
11394 cx.assert_editor_state(indoc! {"
11395 ˇfn func(abc def: i32) -> u32 {
11396 }
11397 "});
11398
11399 //// Forward
11400
11401 // First diagnostic
11402 cx.update_editor(|editor, window, cx| {
11403 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11404 });
11405 cx.assert_editor_state(indoc! {"
11406 fn func(abcˇ def: i32) -> u32 {
11407 }
11408 "});
11409
11410 // Second diagnostic
11411 cx.update_editor(|editor, window, cx| {
11412 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11413 });
11414 cx.assert_editor_state(indoc! {"
11415 fn func(abc ˇdef: i32) -> u32 {
11416 }
11417 "});
11418
11419 // Third diagnostic, same place
11420 cx.update_editor(|editor, window, cx| {
11421 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11422 });
11423 cx.assert_editor_state(indoc! {"
11424 fn func(abc ˇdef: i32) -> u32 {
11425 }
11426 "});
11427
11428 // Fourth diagnostic
11429 cx.update_editor(|editor, window, cx| {
11430 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11431 });
11432 cx.assert_editor_state(indoc! {"
11433 fn func(abc def: i32) -> ˇu32 {
11434 }
11435 "});
11436
11437 // Wrapped around, first diagnostic
11438 cx.update_editor(|editor, window, cx| {
11439 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11440 });
11441 cx.assert_editor_state(indoc! {"
11442 fn func(abcˇ def: i32) -> u32 {
11443 }
11444 "});
11445}
11446
11447#[gpui::test]
11448async fn active_diagnostics_dismiss_after_invalidation(
11449 executor: BackgroundExecutor,
11450 cx: &mut TestAppContext,
11451) {
11452 init_test(cx, |_| {});
11453
11454 let mut cx = EditorTestContext::new(cx).await;
11455 let lsp_store =
11456 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11457
11458 cx.set_state(indoc! {"
11459 ˇfn func(abc def: i32) -> u32 {
11460 }
11461 "});
11462
11463 let message = "Something's wrong!";
11464 cx.update(|_, cx| {
11465 lsp_store.update(cx, |lsp_store, cx| {
11466 lsp_store
11467 .update_diagnostics(
11468 LanguageServerId(0),
11469 lsp::PublishDiagnosticsParams {
11470 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11471 version: None,
11472 diagnostics: vec![lsp::Diagnostic {
11473 range: lsp::Range::new(
11474 lsp::Position::new(0, 11),
11475 lsp::Position::new(0, 12),
11476 ),
11477 severity: Some(lsp::DiagnosticSeverity::ERROR),
11478 message: message.to_string(),
11479 ..Default::default()
11480 }],
11481 },
11482 &[],
11483 cx,
11484 )
11485 .unwrap()
11486 });
11487 });
11488 executor.run_until_parked();
11489
11490 cx.update_editor(|editor, window, cx| {
11491 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11492 assert_eq!(
11493 editor
11494 .active_diagnostics
11495 .as_ref()
11496 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11497 Some(message),
11498 "Should have a diagnostics group activated"
11499 );
11500 });
11501 cx.assert_editor_state(indoc! {"
11502 fn func(abcˇ def: i32) -> u32 {
11503 }
11504 "});
11505
11506 cx.update(|_, cx| {
11507 lsp_store.update(cx, |lsp_store, cx| {
11508 lsp_store
11509 .update_diagnostics(
11510 LanguageServerId(0),
11511 lsp::PublishDiagnosticsParams {
11512 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11513 version: None,
11514 diagnostics: Vec::new(),
11515 },
11516 &[],
11517 cx,
11518 )
11519 .unwrap()
11520 });
11521 });
11522 executor.run_until_parked();
11523 cx.update_editor(|editor, _, _| {
11524 assert_eq!(
11525 editor.active_diagnostics, None,
11526 "After no diagnostics set to the editor, no diagnostics should be active"
11527 );
11528 });
11529 cx.assert_editor_state(indoc! {"
11530 fn func(abcˇ def: i32) -> u32 {
11531 }
11532 "});
11533
11534 cx.update_editor(|editor, window, cx| {
11535 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11536 assert_eq!(
11537 editor.active_diagnostics, None,
11538 "Should be no diagnostics to go to and activate"
11539 );
11540 });
11541 cx.assert_editor_state(indoc! {"
11542 fn func(abcˇ def: i32) -> u32 {
11543 }
11544 "});
11545}
11546
11547#[gpui::test]
11548async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11549 init_test(cx, |_| {});
11550
11551 let mut cx = EditorTestContext::new(cx).await;
11552
11553 cx.set_state(indoc! {"
11554 fn func(abˇc def: i32) -> u32 {
11555 }
11556 "});
11557 let lsp_store =
11558 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11559
11560 cx.update(|_, cx| {
11561 lsp_store.update(cx, |lsp_store, cx| {
11562 lsp_store.update_diagnostics(
11563 LanguageServerId(0),
11564 lsp::PublishDiagnosticsParams {
11565 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11566 version: None,
11567 diagnostics: vec![lsp::Diagnostic {
11568 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11569 severity: Some(lsp::DiagnosticSeverity::ERROR),
11570 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11571 ..Default::default()
11572 }],
11573 },
11574 &[],
11575 cx,
11576 )
11577 })
11578 }).unwrap();
11579 cx.run_until_parked();
11580 cx.update_editor(|editor, window, cx| {
11581 hover_popover::hover(editor, &Default::default(), window, cx)
11582 });
11583 cx.run_until_parked();
11584 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11585}
11586
11587#[gpui::test]
11588async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11589 init_test(cx, |_| {});
11590
11591 let mut cx = EditorTestContext::new(cx).await;
11592
11593 let diff_base = r#"
11594 use some::mod;
11595
11596 const A: u32 = 42;
11597
11598 fn main() {
11599 println!("hello");
11600
11601 println!("world");
11602 }
11603 "#
11604 .unindent();
11605
11606 // Edits are modified, removed, modified, added
11607 cx.set_state(
11608 &r#"
11609 use some::modified;
11610
11611 ˇ
11612 fn main() {
11613 println!("hello there");
11614
11615 println!("around the");
11616 println!("world");
11617 }
11618 "#
11619 .unindent(),
11620 );
11621
11622 cx.set_head_text(&diff_base);
11623 executor.run_until_parked();
11624
11625 cx.update_editor(|editor, window, cx| {
11626 //Wrap around the bottom of the buffer
11627 for _ in 0..3 {
11628 editor.go_to_next_hunk(&GoToHunk, window, cx);
11629 }
11630 });
11631
11632 cx.assert_editor_state(
11633 &r#"
11634 ˇuse some::modified;
11635
11636
11637 fn main() {
11638 println!("hello there");
11639
11640 println!("around the");
11641 println!("world");
11642 }
11643 "#
11644 .unindent(),
11645 );
11646
11647 cx.update_editor(|editor, window, cx| {
11648 //Wrap around the top of the buffer
11649 for _ in 0..2 {
11650 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11651 }
11652 });
11653
11654 cx.assert_editor_state(
11655 &r#"
11656 use some::modified;
11657
11658
11659 fn main() {
11660 ˇ println!("hello there");
11661
11662 println!("around the");
11663 println!("world");
11664 }
11665 "#
11666 .unindent(),
11667 );
11668
11669 cx.update_editor(|editor, window, cx| {
11670 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11671 });
11672
11673 cx.assert_editor_state(
11674 &r#"
11675 use some::modified;
11676
11677 ˇ
11678 fn main() {
11679 println!("hello there");
11680
11681 println!("around the");
11682 println!("world");
11683 }
11684 "#
11685 .unindent(),
11686 );
11687
11688 cx.update_editor(|editor, window, cx| {
11689 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11690 });
11691
11692 cx.assert_editor_state(
11693 &r#"
11694 ˇuse some::modified;
11695
11696
11697 fn main() {
11698 println!("hello there");
11699
11700 println!("around the");
11701 println!("world");
11702 }
11703 "#
11704 .unindent(),
11705 );
11706
11707 cx.update_editor(|editor, window, cx| {
11708 for _ in 0..2 {
11709 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11710 }
11711 });
11712
11713 cx.assert_editor_state(
11714 &r#"
11715 use some::modified;
11716
11717
11718 fn main() {
11719 ˇ println!("hello there");
11720
11721 println!("around the");
11722 println!("world");
11723 }
11724 "#
11725 .unindent(),
11726 );
11727
11728 cx.update_editor(|editor, window, cx| {
11729 editor.fold(&Fold, window, cx);
11730 });
11731
11732 cx.update_editor(|editor, window, cx| {
11733 editor.go_to_next_hunk(&GoToHunk, window, cx);
11734 });
11735
11736 cx.assert_editor_state(
11737 &r#"
11738 ˇuse some::modified;
11739
11740
11741 fn main() {
11742 println!("hello there");
11743
11744 println!("around the");
11745 println!("world");
11746 }
11747 "#
11748 .unindent(),
11749 );
11750}
11751
11752#[test]
11753fn test_split_words() {
11754 fn split(text: &str) -> Vec<&str> {
11755 split_words(text).collect()
11756 }
11757
11758 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11759 assert_eq!(split("hello_world"), &["hello_", "world"]);
11760 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11761 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11762 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11763 assert_eq!(split("helloworld"), &["helloworld"]);
11764
11765 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11766}
11767
11768#[gpui::test]
11769async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11770 init_test(cx, |_| {});
11771
11772 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11773 let mut assert = |before, after| {
11774 let _state_context = cx.set_state(before);
11775 cx.run_until_parked();
11776 cx.update_editor(|editor, window, cx| {
11777 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11778 });
11779 cx.assert_editor_state(after);
11780 };
11781
11782 // Outside bracket jumps to outside of matching bracket
11783 assert("console.logˇ(var);", "console.log(var)ˇ;");
11784 assert("console.log(var)ˇ;", "console.logˇ(var);");
11785
11786 // Inside bracket jumps to inside of matching bracket
11787 assert("console.log(ˇvar);", "console.log(varˇ);");
11788 assert("console.log(varˇ);", "console.log(ˇvar);");
11789
11790 // When outside a bracket and inside, favor jumping to the inside bracket
11791 assert(
11792 "console.log('foo', [1, 2, 3]ˇ);",
11793 "console.log(ˇ'foo', [1, 2, 3]);",
11794 );
11795 assert(
11796 "console.log(ˇ'foo', [1, 2, 3]);",
11797 "console.log('foo', [1, 2, 3]ˇ);",
11798 );
11799
11800 // Bias forward if two options are equally likely
11801 assert(
11802 "let result = curried_fun()ˇ();",
11803 "let result = curried_fun()()ˇ;",
11804 );
11805
11806 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11807 assert(
11808 indoc! {"
11809 function test() {
11810 console.log('test')ˇ
11811 }"},
11812 indoc! {"
11813 function test() {
11814 console.logˇ('test')
11815 }"},
11816 );
11817}
11818
11819#[gpui::test]
11820async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11821 init_test(cx, |_| {});
11822
11823 let fs = FakeFs::new(cx.executor());
11824 fs.insert_tree(
11825 path!("/a"),
11826 json!({
11827 "main.rs": "fn main() { let a = 5; }",
11828 "other.rs": "// Test file",
11829 }),
11830 )
11831 .await;
11832 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11833
11834 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11835 language_registry.add(Arc::new(Language::new(
11836 LanguageConfig {
11837 name: "Rust".into(),
11838 matcher: LanguageMatcher {
11839 path_suffixes: vec!["rs".to_string()],
11840 ..Default::default()
11841 },
11842 brackets: BracketPairConfig {
11843 pairs: vec![BracketPair {
11844 start: "{".to_string(),
11845 end: "}".to_string(),
11846 close: true,
11847 surround: true,
11848 newline: true,
11849 }],
11850 disabled_scopes_by_bracket_ix: Vec::new(),
11851 },
11852 ..Default::default()
11853 },
11854 Some(tree_sitter_rust::LANGUAGE.into()),
11855 )));
11856 let mut fake_servers = language_registry.register_fake_lsp(
11857 "Rust",
11858 FakeLspAdapter {
11859 capabilities: lsp::ServerCapabilities {
11860 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11861 first_trigger_character: "{".to_string(),
11862 more_trigger_character: None,
11863 }),
11864 ..Default::default()
11865 },
11866 ..Default::default()
11867 },
11868 );
11869
11870 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11871
11872 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11873
11874 let worktree_id = workspace
11875 .update(cx, |workspace, _, cx| {
11876 workspace.project().update(cx, |project, cx| {
11877 project.worktrees(cx).next().unwrap().read(cx).id()
11878 })
11879 })
11880 .unwrap();
11881
11882 let buffer = project
11883 .update(cx, |project, cx| {
11884 project.open_local_buffer(path!("/a/main.rs"), cx)
11885 })
11886 .await
11887 .unwrap();
11888 let editor_handle = workspace
11889 .update(cx, |workspace, window, cx| {
11890 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11891 })
11892 .unwrap()
11893 .await
11894 .unwrap()
11895 .downcast::<Editor>()
11896 .unwrap();
11897
11898 cx.executor().start_waiting();
11899 let fake_server = fake_servers.next().await.unwrap();
11900
11901 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11902 assert_eq!(
11903 params.text_document_position.text_document.uri,
11904 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11905 );
11906 assert_eq!(
11907 params.text_document_position.position,
11908 lsp::Position::new(0, 21),
11909 );
11910
11911 Ok(Some(vec![lsp::TextEdit {
11912 new_text: "]".to_string(),
11913 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11914 }]))
11915 });
11916
11917 editor_handle.update_in(cx, |editor, window, cx| {
11918 window.focus(&editor.focus_handle(cx));
11919 editor.change_selections(None, window, cx, |s| {
11920 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11921 });
11922 editor.handle_input("{", window, cx);
11923 });
11924
11925 cx.executor().run_until_parked();
11926
11927 buffer.update(cx, |buffer, _| {
11928 assert_eq!(
11929 buffer.text(),
11930 "fn main() { let a = {5}; }",
11931 "No extra braces from on type formatting should appear in the buffer"
11932 )
11933 });
11934}
11935
11936#[gpui::test]
11937async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11938 init_test(cx, |_| {});
11939
11940 let fs = FakeFs::new(cx.executor());
11941 fs.insert_tree(
11942 path!("/a"),
11943 json!({
11944 "main.rs": "fn main() { let a = 5; }",
11945 "other.rs": "// Test file",
11946 }),
11947 )
11948 .await;
11949
11950 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11951
11952 let server_restarts = Arc::new(AtomicUsize::new(0));
11953 let closure_restarts = Arc::clone(&server_restarts);
11954 let language_server_name = "test language server";
11955 let language_name: LanguageName = "Rust".into();
11956
11957 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11958 language_registry.add(Arc::new(Language::new(
11959 LanguageConfig {
11960 name: language_name.clone(),
11961 matcher: LanguageMatcher {
11962 path_suffixes: vec!["rs".to_string()],
11963 ..Default::default()
11964 },
11965 ..Default::default()
11966 },
11967 Some(tree_sitter_rust::LANGUAGE.into()),
11968 )));
11969 let mut fake_servers = language_registry.register_fake_lsp(
11970 "Rust",
11971 FakeLspAdapter {
11972 name: language_server_name,
11973 initialization_options: Some(json!({
11974 "testOptionValue": true
11975 })),
11976 initializer: Some(Box::new(move |fake_server| {
11977 let task_restarts = Arc::clone(&closure_restarts);
11978 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11979 task_restarts.fetch_add(1, atomic::Ordering::Release);
11980 futures::future::ready(Ok(()))
11981 });
11982 })),
11983 ..Default::default()
11984 },
11985 );
11986
11987 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11988 let _buffer = project
11989 .update(cx, |project, cx| {
11990 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11991 })
11992 .await
11993 .unwrap();
11994 let _fake_server = fake_servers.next().await.unwrap();
11995 update_test_language_settings(cx, |language_settings| {
11996 language_settings.languages.insert(
11997 language_name.clone(),
11998 LanguageSettingsContent {
11999 tab_size: NonZeroU32::new(8),
12000 ..Default::default()
12001 },
12002 );
12003 });
12004 cx.executor().run_until_parked();
12005 assert_eq!(
12006 server_restarts.load(atomic::Ordering::Acquire),
12007 0,
12008 "Should not restart LSP server on an unrelated change"
12009 );
12010
12011 update_test_project_settings(cx, |project_settings| {
12012 project_settings.lsp.insert(
12013 "Some other server name".into(),
12014 LspSettings {
12015 binary: None,
12016 settings: None,
12017 initialization_options: Some(json!({
12018 "some other init value": false
12019 })),
12020 },
12021 );
12022 });
12023 cx.executor().run_until_parked();
12024 assert_eq!(
12025 server_restarts.load(atomic::Ordering::Acquire),
12026 0,
12027 "Should not restart LSP server on an unrelated LSP settings change"
12028 );
12029
12030 update_test_project_settings(cx, |project_settings| {
12031 project_settings.lsp.insert(
12032 language_server_name.into(),
12033 LspSettings {
12034 binary: None,
12035 settings: None,
12036 initialization_options: Some(json!({
12037 "anotherInitValue": false
12038 })),
12039 },
12040 );
12041 });
12042 cx.executor().run_until_parked();
12043 assert_eq!(
12044 server_restarts.load(atomic::Ordering::Acquire),
12045 1,
12046 "Should restart LSP server on a related LSP settings change"
12047 );
12048
12049 update_test_project_settings(cx, |project_settings| {
12050 project_settings.lsp.insert(
12051 language_server_name.into(),
12052 LspSettings {
12053 binary: None,
12054 settings: None,
12055 initialization_options: Some(json!({
12056 "anotherInitValue": false
12057 })),
12058 },
12059 );
12060 });
12061 cx.executor().run_until_parked();
12062 assert_eq!(
12063 server_restarts.load(atomic::Ordering::Acquire),
12064 1,
12065 "Should not restart LSP server on a related LSP settings change that is the same"
12066 );
12067
12068 update_test_project_settings(cx, |project_settings| {
12069 project_settings.lsp.insert(
12070 language_server_name.into(),
12071 LspSettings {
12072 binary: None,
12073 settings: None,
12074 initialization_options: None,
12075 },
12076 );
12077 });
12078 cx.executor().run_until_parked();
12079 assert_eq!(
12080 server_restarts.load(atomic::Ordering::Acquire),
12081 2,
12082 "Should restart LSP server on another related LSP settings change"
12083 );
12084}
12085
12086#[gpui::test]
12087async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12088 init_test(cx, |_| {});
12089
12090 let mut cx = EditorLspTestContext::new_rust(
12091 lsp::ServerCapabilities {
12092 completion_provider: Some(lsp::CompletionOptions {
12093 trigger_characters: Some(vec![".".to_string()]),
12094 resolve_provider: Some(true),
12095 ..Default::default()
12096 }),
12097 ..Default::default()
12098 },
12099 cx,
12100 )
12101 .await;
12102
12103 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12104 cx.simulate_keystroke(".");
12105 let completion_item = lsp::CompletionItem {
12106 label: "some".into(),
12107 kind: Some(lsp::CompletionItemKind::SNIPPET),
12108 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12109 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12110 kind: lsp::MarkupKind::Markdown,
12111 value: "```rust\nSome(2)\n```".to_string(),
12112 })),
12113 deprecated: Some(false),
12114 sort_text: Some("fffffff2".to_string()),
12115 filter_text: Some("some".to_string()),
12116 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12117 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12118 range: lsp::Range {
12119 start: lsp::Position {
12120 line: 0,
12121 character: 22,
12122 },
12123 end: lsp::Position {
12124 line: 0,
12125 character: 22,
12126 },
12127 },
12128 new_text: "Some(2)".to_string(),
12129 })),
12130 additional_text_edits: Some(vec![lsp::TextEdit {
12131 range: lsp::Range {
12132 start: lsp::Position {
12133 line: 0,
12134 character: 20,
12135 },
12136 end: lsp::Position {
12137 line: 0,
12138 character: 22,
12139 },
12140 },
12141 new_text: "".to_string(),
12142 }]),
12143 ..Default::default()
12144 };
12145
12146 let closure_completion_item = completion_item.clone();
12147 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12148 let task_completion_item = closure_completion_item.clone();
12149 async move {
12150 Ok(Some(lsp::CompletionResponse::Array(vec![
12151 task_completion_item,
12152 ])))
12153 }
12154 });
12155
12156 request.next().await;
12157
12158 cx.condition(|editor, _| editor.context_menu_visible())
12159 .await;
12160 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12161 editor
12162 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12163 .unwrap()
12164 });
12165 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12166
12167 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12168 let task_completion_item = completion_item.clone();
12169 async move { Ok(task_completion_item) }
12170 })
12171 .next()
12172 .await
12173 .unwrap();
12174 apply_additional_edits.await.unwrap();
12175 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12176}
12177
12178#[gpui::test]
12179async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12180 init_test(cx, |_| {});
12181
12182 let mut cx = EditorLspTestContext::new_rust(
12183 lsp::ServerCapabilities {
12184 completion_provider: Some(lsp::CompletionOptions {
12185 trigger_characters: Some(vec![".".to_string()]),
12186 resolve_provider: Some(true),
12187 ..Default::default()
12188 }),
12189 ..Default::default()
12190 },
12191 cx,
12192 )
12193 .await;
12194
12195 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12196 cx.simulate_keystroke(".");
12197
12198 let item1 = lsp::CompletionItem {
12199 label: "method id()".to_string(),
12200 filter_text: Some("id".to_string()),
12201 detail: None,
12202 documentation: None,
12203 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12204 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12205 new_text: ".id".to_string(),
12206 })),
12207 ..lsp::CompletionItem::default()
12208 };
12209
12210 let item2 = lsp::CompletionItem {
12211 label: "other".to_string(),
12212 filter_text: Some("other".to_string()),
12213 detail: None,
12214 documentation: None,
12215 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12216 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12217 new_text: ".other".to_string(),
12218 })),
12219 ..lsp::CompletionItem::default()
12220 };
12221
12222 let item1 = item1.clone();
12223 cx.handle_request::<lsp::request::Completion, _, _>({
12224 let item1 = item1.clone();
12225 move |_, _, _| {
12226 let item1 = item1.clone();
12227 let item2 = item2.clone();
12228 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12229 }
12230 })
12231 .next()
12232 .await;
12233
12234 cx.condition(|editor, _| editor.context_menu_visible())
12235 .await;
12236 cx.update_editor(|editor, _, _| {
12237 let context_menu = editor.context_menu.borrow_mut();
12238 let context_menu = context_menu
12239 .as_ref()
12240 .expect("Should have the context menu deployed");
12241 match context_menu {
12242 CodeContextMenu::Completions(completions_menu) => {
12243 let completions = completions_menu.completions.borrow_mut();
12244 assert_eq!(
12245 completions
12246 .iter()
12247 .map(|completion| &completion.label.text)
12248 .collect::<Vec<_>>(),
12249 vec!["method id()", "other"]
12250 )
12251 }
12252 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12253 }
12254 });
12255
12256 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12257 let item1 = item1.clone();
12258 move |_, item_to_resolve, _| {
12259 let item1 = item1.clone();
12260 async move {
12261 if item1 == item_to_resolve {
12262 Ok(lsp::CompletionItem {
12263 label: "method id()".to_string(),
12264 filter_text: Some("id".to_string()),
12265 detail: Some("Now resolved!".to_string()),
12266 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12267 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12268 range: lsp::Range::new(
12269 lsp::Position::new(0, 22),
12270 lsp::Position::new(0, 22),
12271 ),
12272 new_text: ".id".to_string(),
12273 })),
12274 ..lsp::CompletionItem::default()
12275 })
12276 } else {
12277 Ok(item_to_resolve)
12278 }
12279 }
12280 }
12281 })
12282 .next()
12283 .await
12284 .unwrap();
12285 cx.run_until_parked();
12286
12287 cx.update_editor(|editor, window, cx| {
12288 editor.context_menu_next(&Default::default(), window, cx);
12289 });
12290
12291 cx.update_editor(|editor, _, _| {
12292 let context_menu = editor.context_menu.borrow_mut();
12293 let context_menu = context_menu
12294 .as_ref()
12295 .expect("Should have the context menu deployed");
12296 match context_menu {
12297 CodeContextMenu::Completions(completions_menu) => {
12298 let completions = completions_menu.completions.borrow_mut();
12299 assert_eq!(
12300 completions
12301 .iter()
12302 .map(|completion| &completion.label.text)
12303 .collect::<Vec<_>>(),
12304 vec!["method id() Now resolved!", "other"],
12305 "Should update first completion label, but not second as the filter text did not match."
12306 );
12307 }
12308 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12309 }
12310 });
12311}
12312
12313#[gpui::test]
12314async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12315 init_test(cx, |_| {});
12316
12317 let mut cx = EditorLspTestContext::new_rust(
12318 lsp::ServerCapabilities {
12319 completion_provider: Some(lsp::CompletionOptions {
12320 trigger_characters: Some(vec![".".to_string()]),
12321 resolve_provider: Some(true),
12322 ..Default::default()
12323 }),
12324 ..Default::default()
12325 },
12326 cx,
12327 )
12328 .await;
12329
12330 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12331 cx.simulate_keystroke(".");
12332
12333 let unresolved_item_1 = lsp::CompletionItem {
12334 label: "id".to_string(),
12335 filter_text: Some("id".to_string()),
12336 detail: None,
12337 documentation: None,
12338 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12339 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12340 new_text: ".id".to_string(),
12341 })),
12342 ..lsp::CompletionItem::default()
12343 };
12344 let resolved_item_1 = lsp::CompletionItem {
12345 additional_text_edits: Some(vec![lsp::TextEdit {
12346 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12347 new_text: "!!".to_string(),
12348 }]),
12349 ..unresolved_item_1.clone()
12350 };
12351 let unresolved_item_2 = lsp::CompletionItem {
12352 label: "other".to_string(),
12353 filter_text: Some("other".to_string()),
12354 detail: None,
12355 documentation: None,
12356 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12357 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12358 new_text: ".other".to_string(),
12359 })),
12360 ..lsp::CompletionItem::default()
12361 };
12362 let resolved_item_2 = lsp::CompletionItem {
12363 additional_text_edits: Some(vec![lsp::TextEdit {
12364 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12365 new_text: "??".to_string(),
12366 }]),
12367 ..unresolved_item_2.clone()
12368 };
12369
12370 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12371 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12372 cx.lsp
12373 .server
12374 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12375 let unresolved_item_1 = unresolved_item_1.clone();
12376 let resolved_item_1 = resolved_item_1.clone();
12377 let unresolved_item_2 = unresolved_item_2.clone();
12378 let resolved_item_2 = resolved_item_2.clone();
12379 let resolve_requests_1 = resolve_requests_1.clone();
12380 let resolve_requests_2 = resolve_requests_2.clone();
12381 move |unresolved_request, _| {
12382 let unresolved_item_1 = unresolved_item_1.clone();
12383 let resolved_item_1 = resolved_item_1.clone();
12384 let unresolved_item_2 = unresolved_item_2.clone();
12385 let resolved_item_2 = resolved_item_2.clone();
12386 let resolve_requests_1 = resolve_requests_1.clone();
12387 let resolve_requests_2 = resolve_requests_2.clone();
12388 async move {
12389 if unresolved_request == unresolved_item_1 {
12390 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12391 Ok(resolved_item_1.clone())
12392 } else if unresolved_request == unresolved_item_2 {
12393 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12394 Ok(resolved_item_2.clone())
12395 } else {
12396 panic!("Unexpected completion item {unresolved_request:?}")
12397 }
12398 }
12399 }
12400 })
12401 .detach();
12402
12403 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12404 let unresolved_item_1 = unresolved_item_1.clone();
12405 let unresolved_item_2 = unresolved_item_2.clone();
12406 async move {
12407 Ok(Some(lsp::CompletionResponse::Array(vec![
12408 unresolved_item_1,
12409 unresolved_item_2,
12410 ])))
12411 }
12412 })
12413 .next()
12414 .await;
12415
12416 cx.condition(|editor, _| editor.context_menu_visible())
12417 .await;
12418 cx.update_editor(|editor, _, _| {
12419 let context_menu = editor.context_menu.borrow_mut();
12420 let context_menu = context_menu
12421 .as_ref()
12422 .expect("Should have the context menu deployed");
12423 match context_menu {
12424 CodeContextMenu::Completions(completions_menu) => {
12425 let completions = completions_menu.completions.borrow_mut();
12426 assert_eq!(
12427 completions
12428 .iter()
12429 .map(|completion| &completion.label.text)
12430 .collect::<Vec<_>>(),
12431 vec!["id", "other"]
12432 )
12433 }
12434 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12435 }
12436 });
12437 cx.run_until_parked();
12438
12439 cx.update_editor(|editor, window, cx| {
12440 editor.context_menu_next(&ContextMenuNext, window, cx);
12441 });
12442 cx.run_until_parked();
12443 cx.update_editor(|editor, window, cx| {
12444 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12445 });
12446 cx.run_until_parked();
12447 cx.update_editor(|editor, window, cx| {
12448 editor.context_menu_next(&ContextMenuNext, window, cx);
12449 });
12450 cx.run_until_parked();
12451 cx.update_editor(|editor, window, cx| {
12452 editor
12453 .compose_completion(&ComposeCompletion::default(), window, cx)
12454 .expect("No task returned")
12455 })
12456 .await
12457 .expect("Completion failed");
12458 cx.run_until_parked();
12459
12460 cx.update_editor(|editor, _, cx| {
12461 assert_eq!(
12462 resolve_requests_1.load(atomic::Ordering::Acquire),
12463 1,
12464 "Should always resolve once despite multiple selections"
12465 );
12466 assert_eq!(
12467 resolve_requests_2.load(atomic::Ordering::Acquire),
12468 1,
12469 "Should always resolve once after multiple selections and applying the completion"
12470 );
12471 assert_eq!(
12472 editor.text(cx),
12473 "fn main() { let a = ??.other; }",
12474 "Should use resolved data when applying the completion"
12475 );
12476 });
12477}
12478
12479#[gpui::test]
12480async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12481 init_test(cx, |_| {});
12482
12483 let item_0 = lsp::CompletionItem {
12484 label: "abs".into(),
12485 insert_text: Some("abs".into()),
12486 data: Some(json!({ "very": "special"})),
12487 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12488 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12489 lsp::InsertReplaceEdit {
12490 new_text: "abs".to_string(),
12491 insert: lsp::Range::default(),
12492 replace: lsp::Range::default(),
12493 },
12494 )),
12495 ..lsp::CompletionItem::default()
12496 };
12497 let items = iter::once(item_0.clone())
12498 .chain((11..51).map(|i| lsp::CompletionItem {
12499 label: format!("item_{}", i),
12500 insert_text: Some(format!("item_{}", i)),
12501 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12502 ..lsp::CompletionItem::default()
12503 }))
12504 .collect::<Vec<_>>();
12505
12506 let default_commit_characters = vec!["?".to_string()];
12507 let default_data = json!({ "default": "data"});
12508 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12509 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12510 let default_edit_range = lsp::Range {
12511 start: lsp::Position {
12512 line: 0,
12513 character: 5,
12514 },
12515 end: lsp::Position {
12516 line: 0,
12517 character: 5,
12518 },
12519 };
12520
12521 let mut cx = EditorLspTestContext::new_rust(
12522 lsp::ServerCapabilities {
12523 completion_provider: Some(lsp::CompletionOptions {
12524 trigger_characters: Some(vec![".".to_string()]),
12525 resolve_provider: Some(true),
12526 ..Default::default()
12527 }),
12528 ..Default::default()
12529 },
12530 cx,
12531 )
12532 .await;
12533
12534 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12535 cx.simulate_keystroke(".");
12536
12537 let completion_data = default_data.clone();
12538 let completion_characters = default_commit_characters.clone();
12539 let completion_items = items.clone();
12540 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12541 let default_data = completion_data.clone();
12542 let default_commit_characters = completion_characters.clone();
12543 let items = completion_items.clone();
12544 async move {
12545 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12546 items,
12547 item_defaults: Some(lsp::CompletionListItemDefaults {
12548 data: Some(default_data.clone()),
12549 commit_characters: Some(default_commit_characters.clone()),
12550 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12551 default_edit_range,
12552 )),
12553 insert_text_format: Some(default_insert_text_format),
12554 insert_text_mode: Some(default_insert_text_mode),
12555 }),
12556 ..lsp::CompletionList::default()
12557 })))
12558 }
12559 })
12560 .next()
12561 .await;
12562
12563 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12564 cx.lsp
12565 .server
12566 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12567 let closure_resolved_items = resolved_items.clone();
12568 move |item_to_resolve, _| {
12569 let closure_resolved_items = closure_resolved_items.clone();
12570 async move {
12571 closure_resolved_items.lock().push(item_to_resolve.clone());
12572 Ok(item_to_resolve)
12573 }
12574 }
12575 })
12576 .detach();
12577
12578 cx.condition(|editor, _| editor.context_menu_visible())
12579 .await;
12580 cx.run_until_parked();
12581 cx.update_editor(|editor, _, _| {
12582 let menu = editor.context_menu.borrow_mut();
12583 match menu.as_ref().expect("should have the completions menu") {
12584 CodeContextMenu::Completions(completions_menu) => {
12585 assert_eq!(
12586 completions_menu
12587 .entries
12588 .borrow()
12589 .iter()
12590 .map(|mat| mat.string.clone())
12591 .collect::<Vec<String>>(),
12592 items
12593 .iter()
12594 .map(|completion| completion.label.clone())
12595 .collect::<Vec<String>>()
12596 );
12597 }
12598 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12599 }
12600 });
12601 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12602 // with 4 from the end.
12603 assert_eq!(
12604 *resolved_items.lock(),
12605 [&items[0..16], &items[items.len() - 4..items.len()]]
12606 .concat()
12607 .iter()
12608 .cloned()
12609 .map(|mut item| {
12610 if item.data.is_none() {
12611 item.data = Some(default_data.clone());
12612 }
12613 item
12614 })
12615 .collect::<Vec<lsp::CompletionItem>>(),
12616 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12617 );
12618 resolved_items.lock().clear();
12619
12620 cx.update_editor(|editor, window, cx| {
12621 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12622 });
12623 cx.run_until_parked();
12624 // Completions that have already been resolved are skipped.
12625 assert_eq!(
12626 *resolved_items.lock(),
12627 items[items.len() - 16..items.len() - 4]
12628 .iter()
12629 .cloned()
12630 .map(|mut item| {
12631 if item.data.is_none() {
12632 item.data = Some(default_data.clone());
12633 }
12634 item
12635 })
12636 .collect::<Vec<lsp::CompletionItem>>()
12637 );
12638 resolved_items.lock().clear();
12639}
12640
12641#[gpui::test]
12642async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12643 init_test(cx, |_| {});
12644
12645 let mut cx = EditorLspTestContext::new(
12646 Language::new(
12647 LanguageConfig {
12648 matcher: LanguageMatcher {
12649 path_suffixes: vec!["jsx".into()],
12650 ..Default::default()
12651 },
12652 overrides: [(
12653 "element".into(),
12654 LanguageConfigOverride {
12655 word_characters: Override::Set(['-'].into_iter().collect()),
12656 ..Default::default()
12657 },
12658 )]
12659 .into_iter()
12660 .collect(),
12661 ..Default::default()
12662 },
12663 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12664 )
12665 .with_override_query("(jsx_self_closing_element) @element")
12666 .unwrap(),
12667 lsp::ServerCapabilities {
12668 completion_provider: Some(lsp::CompletionOptions {
12669 trigger_characters: Some(vec![":".to_string()]),
12670 ..Default::default()
12671 }),
12672 ..Default::default()
12673 },
12674 cx,
12675 )
12676 .await;
12677
12678 cx.lsp
12679 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12680 Ok(Some(lsp::CompletionResponse::Array(vec![
12681 lsp::CompletionItem {
12682 label: "bg-blue".into(),
12683 ..Default::default()
12684 },
12685 lsp::CompletionItem {
12686 label: "bg-red".into(),
12687 ..Default::default()
12688 },
12689 lsp::CompletionItem {
12690 label: "bg-yellow".into(),
12691 ..Default::default()
12692 },
12693 ])))
12694 });
12695
12696 cx.set_state(r#"<p class="bgˇ" />"#);
12697
12698 // Trigger completion when typing a dash, because the dash is an extra
12699 // word character in the 'element' scope, which contains the cursor.
12700 cx.simulate_keystroke("-");
12701 cx.executor().run_until_parked();
12702 cx.update_editor(|editor, _, _| {
12703 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12704 {
12705 assert_eq!(
12706 completion_menu_entries(&menu),
12707 &["bg-red", "bg-blue", "bg-yellow"]
12708 );
12709 } else {
12710 panic!("expected completion menu to be open");
12711 }
12712 });
12713
12714 cx.simulate_keystroke("l");
12715 cx.executor().run_until_parked();
12716 cx.update_editor(|editor, _, _| {
12717 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12718 {
12719 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12720 } else {
12721 panic!("expected completion menu to be open");
12722 }
12723 });
12724
12725 // When filtering completions, consider the character after the '-' to
12726 // be the start of a subword.
12727 cx.set_state(r#"<p class="yelˇ" />"#);
12728 cx.simulate_keystroke("l");
12729 cx.executor().run_until_parked();
12730 cx.update_editor(|editor, _, _| {
12731 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12732 {
12733 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12734 } else {
12735 panic!("expected completion menu to be open");
12736 }
12737 });
12738}
12739
12740fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12741 let entries = menu.entries.borrow();
12742 entries.iter().map(|mat| mat.string.clone()).collect()
12743}
12744
12745#[gpui::test]
12746async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12747 init_test(cx, |settings| {
12748 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12749 FormatterList(vec![Formatter::Prettier].into()),
12750 ))
12751 });
12752
12753 let fs = FakeFs::new(cx.executor());
12754 fs.insert_file(path!("/file.ts"), Default::default()).await;
12755
12756 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12757 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12758
12759 language_registry.add(Arc::new(Language::new(
12760 LanguageConfig {
12761 name: "TypeScript".into(),
12762 matcher: LanguageMatcher {
12763 path_suffixes: vec!["ts".to_string()],
12764 ..Default::default()
12765 },
12766 ..Default::default()
12767 },
12768 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12769 )));
12770 update_test_language_settings(cx, |settings| {
12771 settings.defaults.prettier = Some(PrettierSettings {
12772 allowed: true,
12773 ..PrettierSettings::default()
12774 });
12775 });
12776
12777 let test_plugin = "test_plugin";
12778 let _ = language_registry.register_fake_lsp(
12779 "TypeScript",
12780 FakeLspAdapter {
12781 prettier_plugins: vec![test_plugin],
12782 ..Default::default()
12783 },
12784 );
12785
12786 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12787 let buffer = project
12788 .update(cx, |project, cx| {
12789 project.open_local_buffer(path!("/file.ts"), cx)
12790 })
12791 .await
12792 .unwrap();
12793
12794 let buffer_text = "one\ntwo\nthree\n";
12795 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12796 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12797 editor.update_in(cx, |editor, window, cx| {
12798 editor.set_text(buffer_text, window, cx)
12799 });
12800
12801 editor
12802 .update_in(cx, |editor, window, cx| {
12803 editor.perform_format(
12804 project.clone(),
12805 FormatTrigger::Manual,
12806 FormatTarget::Buffers,
12807 window,
12808 cx,
12809 )
12810 })
12811 .unwrap()
12812 .await;
12813 assert_eq!(
12814 editor.update(cx, |editor, cx| editor.text(cx)),
12815 buffer_text.to_string() + prettier_format_suffix,
12816 "Test prettier formatting was not applied to the original buffer text",
12817 );
12818
12819 update_test_language_settings(cx, |settings| {
12820 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12821 });
12822 let format = editor.update_in(cx, |editor, window, cx| {
12823 editor.perform_format(
12824 project.clone(),
12825 FormatTrigger::Manual,
12826 FormatTarget::Buffers,
12827 window,
12828 cx,
12829 )
12830 });
12831 format.await.unwrap();
12832 assert_eq!(
12833 editor.update(cx, |editor, cx| editor.text(cx)),
12834 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12835 "Autoformatting (via test prettier) was not applied to the original buffer text",
12836 );
12837}
12838
12839#[gpui::test]
12840async fn test_addition_reverts(cx: &mut TestAppContext) {
12841 init_test(cx, |_| {});
12842 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12843 let base_text = indoc! {r#"
12844 struct Row;
12845 struct Row1;
12846 struct Row2;
12847
12848 struct Row4;
12849 struct Row5;
12850 struct Row6;
12851
12852 struct Row8;
12853 struct Row9;
12854 struct Row10;"#};
12855
12856 // When addition hunks are not adjacent to carets, no hunk revert is performed
12857 assert_hunk_revert(
12858 indoc! {r#"struct Row;
12859 struct Row1;
12860 struct Row1.1;
12861 struct Row1.2;
12862 struct Row2;ˇ
12863
12864 struct Row4;
12865 struct Row5;
12866 struct Row6;
12867
12868 struct Row8;
12869 ˇstruct Row9;
12870 struct Row9.1;
12871 struct Row9.2;
12872 struct Row9.3;
12873 struct Row10;"#},
12874 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12875 indoc! {r#"struct Row;
12876 struct Row1;
12877 struct Row1.1;
12878 struct Row1.2;
12879 struct Row2;ˇ
12880
12881 struct Row4;
12882 struct Row5;
12883 struct Row6;
12884
12885 struct Row8;
12886 ˇstruct Row9;
12887 struct Row9.1;
12888 struct Row9.2;
12889 struct Row9.3;
12890 struct Row10;"#},
12891 base_text,
12892 &mut cx,
12893 );
12894 // Same for selections
12895 assert_hunk_revert(
12896 indoc! {r#"struct Row;
12897 struct Row1;
12898 struct Row2;
12899 struct Row2.1;
12900 struct Row2.2;
12901 «ˇ
12902 struct Row4;
12903 struct» Row5;
12904 «struct Row6;
12905 ˇ»
12906 struct Row9.1;
12907 struct Row9.2;
12908 struct Row9.3;
12909 struct Row8;
12910 struct Row9;
12911 struct Row10;"#},
12912 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12913 indoc! {r#"struct Row;
12914 struct Row1;
12915 struct Row2;
12916 struct Row2.1;
12917 struct Row2.2;
12918 «ˇ
12919 struct Row4;
12920 struct» Row5;
12921 «struct Row6;
12922 ˇ»
12923 struct Row9.1;
12924 struct Row9.2;
12925 struct Row9.3;
12926 struct Row8;
12927 struct Row9;
12928 struct Row10;"#},
12929 base_text,
12930 &mut cx,
12931 );
12932
12933 // When carets and selections intersect the addition hunks, those are reverted.
12934 // Adjacent carets got merged.
12935 assert_hunk_revert(
12936 indoc! {r#"struct Row;
12937 ˇ// something on the top
12938 struct Row1;
12939 struct Row2;
12940 struct Roˇw3.1;
12941 struct Row2.2;
12942 struct Row2.3;ˇ
12943
12944 struct Row4;
12945 struct ˇRow5.1;
12946 struct Row5.2;
12947 struct «Rowˇ»5.3;
12948 struct Row5;
12949 struct Row6;
12950 ˇ
12951 struct Row9.1;
12952 struct «Rowˇ»9.2;
12953 struct «ˇRow»9.3;
12954 struct Row8;
12955 struct Row9;
12956 «ˇ// something on bottom»
12957 struct Row10;"#},
12958 vec![
12959 DiffHunkStatusKind::Added,
12960 DiffHunkStatusKind::Added,
12961 DiffHunkStatusKind::Added,
12962 DiffHunkStatusKind::Added,
12963 DiffHunkStatusKind::Added,
12964 ],
12965 indoc! {r#"struct Row;
12966 ˇstruct Row1;
12967 struct Row2;
12968 ˇ
12969 struct Row4;
12970 ˇstruct Row5;
12971 struct Row6;
12972 ˇ
12973 ˇstruct Row8;
12974 struct Row9;
12975 ˇstruct Row10;"#},
12976 base_text,
12977 &mut cx,
12978 );
12979}
12980
12981#[gpui::test]
12982async fn test_modification_reverts(cx: &mut TestAppContext) {
12983 init_test(cx, |_| {});
12984 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12985 let base_text = indoc! {r#"
12986 struct Row;
12987 struct Row1;
12988 struct Row2;
12989
12990 struct Row4;
12991 struct Row5;
12992 struct Row6;
12993
12994 struct Row8;
12995 struct Row9;
12996 struct Row10;"#};
12997
12998 // Modification hunks behave the same as the addition ones.
12999 assert_hunk_revert(
13000 indoc! {r#"struct Row;
13001 struct Row1;
13002 struct Row33;
13003 ˇ
13004 struct Row4;
13005 struct Row5;
13006 struct Row6;
13007 ˇ
13008 struct Row99;
13009 struct Row9;
13010 struct Row10;"#},
13011 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13012 indoc! {r#"struct Row;
13013 struct Row1;
13014 struct Row33;
13015 ˇ
13016 struct Row4;
13017 struct Row5;
13018 struct Row6;
13019 ˇ
13020 struct Row99;
13021 struct Row9;
13022 struct Row10;"#},
13023 base_text,
13024 &mut cx,
13025 );
13026 assert_hunk_revert(
13027 indoc! {r#"struct Row;
13028 struct Row1;
13029 struct Row33;
13030 «ˇ
13031 struct Row4;
13032 struct» Row5;
13033 «struct Row6;
13034 ˇ»
13035 struct Row99;
13036 struct Row9;
13037 struct Row10;"#},
13038 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13039 indoc! {r#"struct Row;
13040 struct Row1;
13041 struct Row33;
13042 «ˇ
13043 struct Row4;
13044 struct» Row5;
13045 «struct Row6;
13046 ˇ»
13047 struct Row99;
13048 struct Row9;
13049 struct Row10;"#},
13050 base_text,
13051 &mut cx,
13052 );
13053
13054 assert_hunk_revert(
13055 indoc! {r#"ˇstruct Row1.1;
13056 struct Row1;
13057 «ˇstr»uct Row22;
13058
13059 struct ˇRow44;
13060 struct Row5;
13061 struct «Rˇ»ow66;ˇ
13062
13063 «struˇ»ct Row88;
13064 struct Row9;
13065 struct Row1011;ˇ"#},
13066 vec![
13067 DiffHunkStatusKind::Modified,
13068 DiffHunkStatusKind::Modified,
13069 DiffHunkStatusKind::Modified,
13070 DiffHunkStatusKind::Modified,
13071 DiffHunkStatusKind::Modified,
13072 DiffHunkStatusKind::Modified,
13073 ],
13074 indoc! {r#"struct Row;
13075 ˇstruct Row1;
13076 struct Row2;
13077 ˇ
13078 struct Row4;
13079 ˇstruct Row5;
13080 struct Row6;
13081 ˇ
13082 struct Row8;
13083 ˇstruct Row9;
13084 struct Row10;ˇ"#},
13085 base_text,
13086 &mut cx,
13087 );
13088}
13089
13090#[gpui::test]
13091async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13092 init_test(cx, |_| {});
13093 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13094 let base_text = indoc! {r#"
13095 one
13096
13097 two
13098 three
13099 "#};
13100
13101 cx.set_head_text(base_text);
13102 cx.set_state("\nˇ\n");
13103 cx.executor().run_until_parked();
13104 cx.update_editor(|editor, _window, cx| {
13105 editor.expand_selected_diff_hunks(cx);
13106 });
13107 cx.executor().run_until_parked();
13108 cx.update_editor(|editor, window, cx| {
13109 editor.backspace(&Default::default(), window, cx);
13110 });
13111 cx.run_until_parked();
13112 cx.assert_state_with_diff(
13113 indoc! {r#"
13114
13115 - two
13116 - threeˇ
13117 +
13118 "#}
13119 .to_string(),
13120 );
13121}
13122
13123#[gpui::test]
13124async fn test_deletion_reverts(cx: &mut TestAppContext) {
13125 init_test(cx, |_| {});
13126 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13127 let base_text = indoc! {r#"struct Row;
13128struct Row1;
13129struct Row2;
13130
13131struct Row4;
13132struct Row5;
13133struct Row6;
13134
13135struct Row8;
13136struct Row9;
13137struct Row10;"#};
13138
13139 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13140 assert_hunk_revert(
13141 indoc! {r#"struct Row;
13142 struct Row2;
13143
13144 ˇstruct Row4;
13145 struct Row5;
13146 struct Row6;
13147 ˇ
13148 struct Row8;
13149 struct Row10;"#},
13150 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13151 indoc! {r#"struct Row;
13152 struct Row2;
13153
13154 ˇstruct Row4;
13155 struct Row5;
13156 struct Row6;
13157 ˇ
13158 struct Row8;
13159 struct Row10;"#},
13160 base_text,
13161 &mut cx,
13162 );
13163 assert_hunk_revert(
13164 indoc! {r#"struct Row;
13165 struct Row2;
13166
13167 «ˇstruct Row4;
13168 struct» Row5;
13169 «struct Row6;
13170 ˇ»
13171 struct Row8;
13172 struct Row10;"#},
13173 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13174 indoc! {r#"struct Row;
13175 struct Row2;
13176
13177 «ˇstruct Row4;
13178 struct» Row5;
13179 «struct Row6;
13180 ˇ»
13181 struct Row8;
13182 struct Row10;"#},
13183 base_text,
13184 &mut cx,
13185 );
13186
13187 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13188 assert_hunk_revert(
13189 indoc! {r#"struct Row;
13190 ˇstruct Row2;
13191
13192 struct Row4;
13193 struct Row5;
13194 struct Row6;
13195
13196 struct Row8;ˇ
13197 struct Row10;"#},
13198 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13199 indoc! {r#"struct Row;
13200 struct Row1;
13201 ˇstruct Row2;
13202
13203 struct Row4;
13204 struct Row5;
13205 struct Row6;
13206
13207 struct Row8;ˇ
13208 struct Row9;
13209 struct Row10;"#},
13210 base_text,
13211 &mut cx,
13212 );
13213 assert_hunk_revert(
13214 indoc! {r#"struct Row;
13215 struct Row2«ˇ;
13216 struct Row4;
13217 struct» Row5;
13218 «struct Row6;
13219
13220 struct Row8;ˇ»
13221 struct Row10;"#},
13222 vec![
13223 DiffHunkStatusKind::Deleted,
13224 DiffHunkStatusKind::Deleted,
13225 DiffHunkStatusKind::Deleted,
13226 ],
13227 indoc! {r#"struct Row;
13228 struct Row1;
13229 struct Row2«ˇ;
13230
13231 struct Row4;
13232 struct» Row5;
13233 «struct Row6;
13234
13235 struct Row8;ˇ»
13236 struct Row9;
13237 struct Row10;"#},
13238 base_text,
13239 &mut cx,
13240 );
13241}
13242
13243#[gpui::test]
13244async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13245 init_test(cx, |_| {});
13246
13247 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13248 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13249 let base_text_3 =
13250 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13251
13252 let text_1 = edit_first_char_of_every_line(base_text_1);
13253 let text_2 = edit_first_char_of_every_line(base_text_2);
13254 let text_3 = edit_first_char_of_every_line(base_text_3);
13255
13256 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13257 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13258 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13259
13260 let multibuffer = cx.new(|cx| {
13261 let mut multibuffer = MultiBuffer::new(ReadWrite);
13262 multibuffer.push_excerpts(
13263 buffer_1.clone(),
13264 [
13265 ExcerptRange {
13266 context: Point::new(0, 0)..Point::new(3, 0),
13267 primary: None,
13268 },
13269 ExcerptRange {
13270 context: Point::new(5, 0)..Point::new(7, 0),
13271 primary: None,
13272 },
13273 ExcerptRange {
13274 context: Point::new(9, 0)..Point::new(10, 4),
13275 primary: None,
13276 },
13277 ],
13278 cx,
13279 );
13280 multibuffer.push_excerpts(
13281 buffer_2.clone(),
13282 [
13283 ExcerptRange {
13284 context: Point::new(0, 0)..Point::new(3, 0),
13285 primary: None,
13286 },
13287 ExcerptRange {
13288 context: Point::new(5, 0)..Point::new(7, 0),
13289 primary: None,
13290 },
13291 ExcerptRange {
13292 context: Point::new(9, 0)..Point::new(10, 4),
13293 primary: None,
13294 },
13295 ],
13296 cx,
13297 );
13298 multibuffer.push_excerpts(
13299 buffer_3.clone(),
13300 [
13301 ExcerptRange {
13302 context: Point::new(0, 0)..Point::new(3, 0),
13303 primary: None,
13304 },
13305 ExcerptRange {
13306 context: Point::new(5, 0)..Point::new(7, 0),
13307 primary: None,
13308 },
13309 ExcerptRange {
13310 context: Point::new(9, 0)..Point::new(10, 4),
13311 primary: None,
13312 },
13313 ],
13314 cx,
13315 );
13316 multibuffer
13317 });
13318
13319 let fs = FakeFs::new(cx.executor());
13320 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13321 let (editor, cx) = cx
13322 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13323 editor.update_in(cx, |editor, _window, cx| {
13324 for (buffer, diff_base) in [
13325 (buffer_1.clone(), base_text_1),
13326 (buffer_2.clone(), base_text_2),
13327 (buffer_3.clone(), base_text_3),
13328 ] {
13329 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13330 editor
13331 .buffer
13332 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13333 }
13334 });
13335 cx.executor().run_until_parked();
13336
13337 editor.update_in(cx, |editor, window, cx| {
13338 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}");
13339 editor.select_all(&SelectAll, window, cx);
13340 editor.git_restore(&Default::default(), window, cx);
13341 });
13342 cx.executor().run_until_parked();
13343
13344 // When all ranges are selected, all buffer hunks are reverted.
13345 editor.update(cx, |editor, cx| {
13346 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");
13347 });
13348 buffer_1.update(cx, |buffer, _| {
13349 assert_eq!(buffer.text(), base_text_1);
13350 });
13351 buffer_2.update(cx, |buffer, _| {
13352 assert_eq!(buffer.text(), base_text_2);
13353 });
13354 buffer_3.update(cx, |buffer, _| {
13355 assert_eq!(buffer.text(), base_text_3);
13356 });
13357
13358 editor.update_in(cx, |editor, window, cx| {
13359 editor.undo(&Default::default(), window, cx);
13360 });
13361
13362 editor.update_in(cx, |editor, window, cx| {
13363 editor.change_selections(None, window, cx, |s| {
13364 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13365 });
13366 editor.git_restore(&Default::default(), window, cx);
13367 });
13368
13369 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13370 // but not affect buffer_2 and its related excerpts.
13371 editor.update(cx, |editor, cx| {
13372 assert_eq!(
13373 editor.text(cx),
13374 "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}"
13375 );
13376 });
13377 buffer_1.update(cx, |buffer, _| {
13378 assert_eq!(buffer.text(), base_text_1);
13379 });
13380 buffer_2.update(cx, |buffer, _| {
13381 assert_eq!(
13382 buffer.text(),
13383 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13384 );
13385 });
13386 buffer_3.update(cx, |buffer, _| {
13387 assert_eq!(
13388 buffer.text(),
13389 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13390 );
13391 });
13392
13393 fn edit_first_char_of_every_line(text: &str) -> String {
13394 text.split('\n')
13395 .map(|line| format!("X{}", &line[1..]))
13396 .collect::<Vec<_>>()
13397 .join("\n")
13398 }
13399}
13400
13401#[gpui::test]
13402async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13403 init_test(cx, |_| {});
13404
13405 let cols = 4;
13406 let rows = 10;
13407 let sample_text_1 = sample_text(rows, cols, 'a');
13408 assert_eq!(
13409 sample_text_1,
13410 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13411 );
13412 let sample_text_2 = sample_text(rows, cols, 'l');
13413 assert_eq!(
13414 sample_text_2,
13415 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13416 );
13417 let sample_text_3 = sample_text(rows, cols, 'v');
13418 assert_eq!(
13419 sample_text_3,
13420 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13421 );
13422
13423 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13424 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13425 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13426
13427 let multi_buffer = cx.new(|cx| {
13428 let mut multibuffer = MultiBuffer::new(ReadWrite);
13429 multibuffer.push_excerpts(
13430 buffer_1.clone(),
13431 [
13432 ExcerptRange {
13433 context: Point::new(0, 0)..Point::new(3, 0),
13434 primary: None,
13435 },
13436 ExcerptRange {
13437 context: Point::new(5, 0)..Point::new(7, 0),
13438 primary: None,
13439 },
13440 ExcerptRange {
13441 context: Point::new(9, 0)..Point::new(10, 4),
13442 primary: None,
13443 },
13444 ],
13445 cx,
13446 );
13447 multibuffer.push_excerpts(
13448 buffer_2.clone(),
13449 [
13450 ExcerptRange {
13451 context: Point::new(0, 0)..Point::new(3, 0),
13452 primary: None,
13453 },
13454 ExcerptRange {
13455 context: Point::new(5, 0)..Point::new(7, 0),
13456 primary: None,
13457 },
13458 ExcerptRange {
13459 context: Point::new(9, 0)..Point::new(10, 4),
13460 primary: None,
13461 },
13462 ],
13463 cx,
13464 );
13465 multibuffer.push_excerpts(
13466 buffer_3.clone(),
13467 [
13468 ExcerptRange {
13469 context: Point::new(0, 0)..Point::new(3, 0),
13470 primary: None,
13471 },
13472 ExcerptRange {
13473 context: Point::new(5, 0)..Point::new(7, 0),
13474 primary: None,
13475 },
13476 ExcerptRange {
13477 context: Point::new(9, 0)..Point::new(10, 4),
13478 primary: None,
13479 },
13480 ],
13481 cx,
13482 );
13483 multibuffer
13484 });
13485
13486 let fs = FakeFs::new(cx.executor());
13487 fs.insert_tree(
13488 "/a",
13489 json!({
13490 "main.rs": sample_text_1,
13491 "other.rs": sample_text_2,
13492 "lib.rs": sample_text_3,
13493 }),
13494 )
13495 .await;
13496 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13497 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13498 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13499 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13500 Editor::new(
13501 EditorMode::Full,
13502 multi_buffer,
13503 Some(project.clone()),
13504 true,
13505 window,
13506 cx,
13507 )
13508 });
13509 let multibuffer_item_id = workspace
13510 .update(cx, |workspace, window, cx| {
13511 assert!(
13512 workspace.active_item(cx).is_none(),
13513 "active item should be None before the first item is added"
13514 );
13515 workspace.add_item_to_active_pane(
13516 Box::new(multi_buffer_editor.clone()),
13517 None,
13518 true,
13519 window,
13520 cx,
13521 );
13522 let active_item = workspace
13523 .active_item(cx)
13524 .expect("should have an active item after adding the multi buffer");
13525 assert!(
13526 !active_item.is_singleton(cx),
13527 "A multi buffer was expected to active after adding"
13528 );
13529 active_item.item_id()
13530 })
13531 .unwrap();
13532 cx.executor().run_until_parked();
13533
13534 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13535 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13536 s.select_ranges(Some(1..2))
13537 });
13538 editor.open_excerpts(&OpenExcerpts, window, cx);
13539 });
13540 cx.executor().run_until_parked();
13541 let first_item_id = workspace
13542 .update(cx, |workspace, window, cx| {
13543 let active_item = workspace
13544 .active_item(cx)
13545 .expect("should have an active item after navigating into the 1st buffer");
13546 let first_item_id = active_item.item_id();
13547 assert_ne!(
13548 first_item_id, multibuffer_item_id,
13549 "Should navigate into the 1st buffer and activate it"
13550 );
13551 assert!(
13552 active_item.is_singleton(cx),
13553 "New active item should be a singleton buffer"
13554 );
13555 assert_eq!(
13556 active_item
13557 .act_as::<Editor>(cx)
13558 .expect("should have navigated into an editor for the 1st buffer")
13559 .read(cx)
13560 .text(cx),
13561 sample_text_1
13562 );
13563
13564 workspace
13565 .go_back(workspace.active_pane().downgrade(), window, cx)
13566 .detach_and_log_err(cx);
13567
13568 first_item_id
13569 })
13570 .unwrap();
13571 cx.executor().run_until_parked();
13572 workspace
13573 .update(cx, |workspace, _, cx| {
13574 let active_item = workspace
13575 .active_item(cx)
13576 .expect("should have an active item after navigating back");
13577 assert_eq!(
13578 active_item.item_id(),
13579 multibuffer_item_id,
13580 "Should navigate back to the multi buffer"
13581 );
13582 assert!(!active_item.is_singleton(cx));
13583 })
13584 .unwrap();
13585
13586 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13587 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13588 s.select_ranges(Some(39..40))
13589 });
13590 editor.open_excerpts(&OpenExcerpts, window, cx);
13591 });
13592 cx.executor().run_until_parked();
13593 let second_item_id = workspace
13594 .update(cx, |workspace, window, cx| {
13595 let active_item = workspace
13596 .active_item(cx)
13597 .expect("should have an active item after navigating into the 2nd buffer");
13598 let second_item_id = active_item.item_id();
13599 assert_ne!(
13600 second_item_id, multibuffer_item_id,
13601 "Should navigate away from the multibuffer"
13602 );
13603 assert_ne!(
13604 second_item_id, first_item_id,
13605 "Should navigate into the 2nd buffer and activate it"
13606 );
13607 assert!(
13608 active_item.is_singleton(cx),
13609 "New active item should be a singleton buffer"
13610 );
13611 assert_eq!(
13612 active_item
13613 .act_as::<Editor>(cx)
13614 .expect("should have navigated into an editor")
13615 .read(cx)
13616 .text(cx),
13617 sample_text_2
13618 );
13619
13620 workspace
13621 .go_back(workspace.active_pane().downgrade(), window, cx)
13622 .detach_and_log_err(cx);
13623
13624 second_item_id
13625 })
13626 .unwrap();
13627 cx.executor().run_until_parked();
13628 workspace
13629 .update(cx, |workspace, _, cx| {
13630 let active_item = workspace
13631 .active_item(cx)
13632 .expect("should have an active item after navigating back from the 2nd buffer");
13633 assert_eq!(
13634 active_item.item_id(),
13635 multibuffer_item_id,
13636 "Should navigate back from the 2nd buffer to the multi buffer"
13637 );
13638 assert!(!active_item.is_singleton(cx));
13639 })
13640 .unwrap();
13641
13642 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13643 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13644 s.select_ranges(Some(70..70))
13645 });
13646 editor.open_excerpts(&OpenExcerpts, window, cx);
13647 });
13648 cx.executor().run_until_parked();
13649 workspace
13650 .update(cx, |workspace, window, cx| {
13651 let active_item = workspace
13652 .active_item(cx)
13653 .expect("should have an active item after navigating into the 3rd buffer");
13654 let third_item_id = active_item.item_id();
13655 assert_ne!(
13656 third_item_id, multibuffer_item_id,
13657 "Should navigate into the 3rd buffer and activate it"
13658 );
13659 assert_ne!(third_item_id, first_item_id);
13660 assert_ne!(third_item_id, second_item_id);
13661 assert!(
13662 active_item.is_singleton(cx),
13663 "New active item should be a singleton buffer"
13664 );
13665 assert_eq!(
13666 active_item
13667 .act_as::<Editor>(cx)
13668 .expect("should have navigated into an editor")
13669 .read(cx)
13670 .text(cx),
13671 sample_text_3
13672 );
13673
13674 workspace
13675 .go_back(workspace.active_pane().downgrade(), window, cx)
13676 .detach_and_log_err(cx);
13677 })
13678 .unwrap();
13679 cx.executor().run_until_parked();
13680 workspace
13681 .update(cx, |workspace, _, cx| {
13682 let active_item = workspace
13683 .active_item(cx)
13684 .expect("should have an active item after navigating back from the 3rd buffer");
13685 assert_eq!(
13686 active_item.item_id(),
13687 multibuffer_item_id,
13688 "Should navigate back from the 3rd buffer to the multi buffer"
13689 );
13690 assert!(!active_item.is_singleton(cx));
13691 })
13692 .unwrap();
13693}
13694
13695#[gpui::test]
13696async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13697 init_test(cx, |_| {});
13698
13699 let mut cx = EditorTestContext::new(cx).await;
13700
13701 let diff_base = r#"
13702 use some::mod;
13703
13704 const A: u32 = 42;
13705
13706 fn main() {
13707 println!("hello");
13708
13709 println!("world");
13710 }
13711 "#
13712 .unindent();
13713
13714 cx.set_state(
13715 &r#"
13716 use some::modified;
13717
13718 ˇ
13719 fn main() {
13720 println!("hello there");
13721
13722 println!("around the");
13723 println!("world");
13724 }
13725 "#
13726 .unindent(),
13727 );
13728
13729 cx.set_head_text(&diff_base);
13730 executor.run_until_parked();
13731
13732 cx.update_editor(|editor, window, cx| {
13733 editor.go_to_next_hunk(&GoToHunk, window, cx);
13734 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13735 });
13736 executor.run_until_parked();
13737 cx.assert_state_with_diff(
13738 r#"
13739 use some::modified;
13740
13741
13742 fn main() {
13743 - println!("hello");
13744 + ˇ println!("hello there");
13745
13746 println!("around the");
13747 println!("world");
13748 }
13749 "#
13750 .unindent(),
13751 );
13752
13753 cx.update_editor(|editor, window, cx| {
13754 for _ in 0..2 {
13755 editor.go_to_next_hunk(&GoToHunk, window, cx);
13756 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13757 }
13758 });
13759 executor.run_until_parked();
13760 cx.assert_state_with_diff(
13761 r#"
13762 - use some::mod;
13763 + ˇuse some::modified;
13764
13765
13766 fn main() {
13767 - println!("hello");
13768 + println!("hello there");
13769
13770 + println!("around the");
13771 println!("world");
13772 }
13773 "#
13774 .unindent(),
13775 );
13776
13777 cx.update_editor(|editor, window, cx| {
13778 editor.go_to_next_hunk(&GoToHunk, window, cx);
13779 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13780 });
13781 executor.run_until_parked();
13782 cx.assert_state_with_diff(
13783 r#"
13784 - use some::mod;
13785 + use some::modified;
13786
13787 - const A: u32 = 42;
13788 ˇ
13789 fn main() {
13790 - println!("hello");
13791 + println!("hello there");
13792
13793 + println!("around the");
13794 println!("world");
13795 }
13796 "#
13797 .unindent(),
13798 );
13799
13800 cx.update_editor(|editor, window, cx| {
13801 editor.cancel(&Cancel, window, cx);
13802 });
13803
13804 cx.assert_state_with_diff(
13805 r#"
13806 use some::modified;
13807
13808 ˇ
13809 fn main() {
13810 println!("hello there");
13811
13812 println!("around the");
13813 println!("world");
13814 }
13815 "#
13816 .unindent(),
13817 );
13818}
13819
13820#[gpui::test]
13821async fn test_diff_base_change_with_expanded_diff_hunks(
13822 executor: BackgroundExecutor,
13823 cx: &mut TestAppContext,
13824) {
13825 init_test(cx, |_| {});
13826
13827 let mut cx = EditorTestContext::new(cx).await;
13828
13829 let diff_base = r#"
13830 use some::mod1;
13831 use some::mod2;
13832
13833 const A: u32 = 42;
13834 const B: u32 = 42;
13835 const C: u32 = 42;
13836
13837 fn main() {
13838 println!("hello");
13839
13840 println!("world");
13841 }
13842 "#
13843 .unindent();
13844
13845 cx.set_state(
13846 &r#"
13847 use some::mod2;
13848
13849 const A: u32 = 42;
13850 const C: u32 = 42;
13851
13852 fn main(ˇ) {
13853 //println!("hello");
13854
13855 println!("world");
13856 //
13857 //
13858 }
13859 "#
13860 .unindent(),
13861 );
13862
13863 cx.set_head_text(&diff_base);
13864 executor.run_until_parked();
13865
13866 cx.update_editor(|editor, window, cx| {
13867 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13868 });
13869 executor.run_until_parked();
13870 cx.assert_state_with_diff(
13871 r#"
13872 - use some::mod1;
13873 use some::mod2;
13874
13875 const A: u32 = 42;
13876 - const B: u32 = 42;
13877 const C: u32 = 42;
13878
13879 fn main(ˇ) {
13880 - println!("hello");
13881 + //println!("hello");
13882
13883 println!("world");
13884 + //
13885 + //
13886 }
13887 "#
13888 .unindent(),
13889 );
13890
13891 cx.set_head_text("new diff base!");
13892 executor.run_until_parked();
13893 cx.assert_state_with_diff(
13894 r#"
13895 - new diff base!
13896 + use some::mod2;
13897 +
13898 + const A: u32 = 42;
13899 + const C: u32 = 42;
13900 +
13901 + fn main(ˇ) {
13902 + //println!("hello");
13903 +
13904 + println!("world");
13905 + //
13906 + //
13907 + }
13908 "#
13909 .unindent(),
13910 );
13911}
13912
13913#[gpui::test]
13914async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13915 init_test(cx, |_| {});
13916
13917 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13918 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13919 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13920 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13921 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13922 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13923
13924 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13925 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13926 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13927
13928 let multi_buffer = cx.new(|cx| {
13929 let mut multibuffer = MultiBuffer::new(ReadWrite);
13930 multibuffer.push_excerpts(
13931 buffer_1.clone(),
13932 [
13933 ExcerptRange {
13934 context: Point::new(0, 0)..Point::new(3, 0),
13935 primary: None,
13936 },
13937 ExcerptRange {
13938 context: Point::new(5, 0)..Point::new(7, 0),
13939 primary: None,
13940 },
13941 ExcerptRange {
13942 context: Point::new(9, 0)..Point::new(10, 3),
13943 primary: None,
13944 },
13945 ],
13946 cx,
13947 );
13948 multibuffer.push_excerpts(
13949 buffer_2.clone(),
13950 [
13951 ExcerptRange {
13952 context: Point::new(0, 0)..Point::new(3, 0),
13953 primary: None,
13954 },
13955 ExcerptRange {
13956 context: Point::new(5, 0)..Point::new(7, 0),
13957 primary: None,
13958 },
13959 ExcerptRange {
13960 context: Point::new(9, 0)..Point::new(10, 3),
13961 primary: None,
13962 },
13963 ],
13964 cx,
13965 );
13966 multibuffer.push_excerpts(
13967 buffer_3.clone(),
13968 [
13969 ExcerptRange {
13970 context: Point::new(0, 0)..Point::new(3, 0),
13971 primary: None,
13972 },
13973 ExcerptRange {
13974 context: Point::new(5, 0)..Point::new(7, 0),
13975 primary: None,
13976 },
13977 ExcerptRange {
13978 context: Point::new(9, 0)..Point::new(10, 3),
13979 primary: None,
13980 },
13981 ],
13982 cx,
13983 );
13984 multibuffer
13985 });
13986
13987 let editor = cx.add_window(|window, cx| {
13988 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13989 });
13990 editor
13991 .update(cx, |editor, _window, cx| {
13992 for (buffer, diff_base) in [
13993 (buffer_1.clone(), file_1_old),
13994 (buffer_2.clone(), file_2_old),
13995 (buffer_3.clone(), file_3_old),
13996 ] {
13997 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13998 editor
13999 .buffer
14000 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14001 }
14002 })
14003 .unwrap();
14004
14005 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14006 cx.run_until_parked();
14007
14008 cx.assert_editor_state(
14009 &"
14010 ˇaaa
14011 ccc
14012 ddd
14013
14014 ggg
14015 hhh
14016
14017
14018 lll
14019 mmm
14020 NNN
14021
14022 qqq
14023 rrr
14024
14025 uuu
14026 111
14027 222
14028 333
14029
14030 666
14031 777
14032
14033 000
14034 !!!"
14035 .unindent(),
14036 );
14037
14038 cx.update_editor(|editor, window, cx| {
14039 editor.select_all(&SelectAll, window, cx);
14040 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14041 });
14042 cx.executor().run_until_parked();
14043
14044 cx.assert_state_with_diff(
14045 "
14046 «aaa
14047 - bbb
14048 ccc
14049 ddd
14050
14051 ggg
14052 hhh
14053
14054
14055 lll
14056 mmm
14057 - nnn
14058 + NNN
14059
14060 qqq
14061 rrr
14062
14063 uuu
14064 111
14065 222
14066 333
14067
14068 + 666
14069 777
14070
14071 000
14072 !!!ˇ»"
14073 .unindent(),
14074 );
14075}
14076
14077#[gpui::test]
14078async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14079 init_test(cx, |_| {});
14080
14081 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14082 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14083
14084 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14085 let multi_buffer = cx.new(|cx| {
14086 let mut multibuffer = MultiBuffer::new(ReadWrite);
14087 multibuffer.push_excerpts(
14088 buffer.clone(),
14089 [
14090 ExcerptRange {
14091 context: Point::new(0, 0)..Point::new(2, 0),
14092 primary: None,
14093 },
14094 ExcerptRange {
14095 context: Point::new(4, 0)..Point::new(7, 0),
14096 primary: None,
14097 },
14098 ExcerptRange {
14099 context: Point::new(9, 0)..Point::new(10, 0),
14100 primary: None,
14101 },
14102 ],
14103 cx,
14104 );
14105 multibuffer
14106 });
14107
14108 let editor = cx.add_window(|window, cx| {
14109 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
14110 });
14111 editor
14112 .update(cx, |editor, _window, cx| {
14113 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14114 editor
14115 .buffer
14116 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14117 })
14118 .unwrap();
14119
14120 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14121 cx.run_until_parked();
14122
14123 cx.update_editor(|editor, window, cx| {
14124 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14125 });
14126 cx.executor().run_until_parked();
14127
14128 // When the start of a hunk coincides with the start of its excerpt,
14129 // the hunk is expanded. When the start of a a hunk is earlier than
14130 // the start of its excerpt, the hunk is not expanded.
14131 cx.assert_state_with_diff(
14132 "
14133 ˇaaa
14134 - bbb
14135 + BBB
14136
14137 - ddd
14138 - eee
14139 + DDD
14140 + EEE
14141 fff
14142
14143 iii
14144 "
14145 .unindent(),
14146 );
14147}
14148
14149#[gpui::test]
14150async fn test_edits_around_expanded_insertion_hunks(
14151 executor: BackgroundExecutor,
14152 cx: &mut TestAppContext,
14153) {
14154 init_test(cx, |_| {});
14155
14156 let mut cx = EditorTestContext::new(cx).await;
14157
14158 let diff_base = r#"
14159 use some::mod1;
14160 use some::mod2;
14161
14162 const A: u32 = 42;
14163
14164 fn main() {
14165 println!("hello");
14166
14167 println!("world");
14168 }
14169 "#
14170 .unindent();
14171 executor.run_until_parked();
14172 cx.set_state(
14173 &r#"
14174 use some::mod1;
14175 use some::mod2;
14176
14177 const A: u32 = 42;
14178 const B: u32 = 42;
14179 const C: u32 = 42;
14180 ˇ
14181
14182 fn main() {
14183 println!("hello");
14184
14185 println!("world");
14186 }
14187 "#
14188 .unindent(),
14189 );
14190
14191 cx.set_head_text(&diff_base);
14192 executor.run_until_parked();
14193
14194 cx.update_editor(|editor, window, cx| {
14195 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14196 });
14197 executor.run_until_parked();
14198
14199 cx.assert_state_with_diff(
14200 r#"
14201 use some::mod1;
14202 use some::mod2;
14203
14204 const A: u32 = 42;
14205 + const B: u32 = 42;
14206 + const C: u32 = 42;
14207 + ˇ
14208
14209 fn main() {
14210 println!("hello");
14211
14212 println!("world");
14213 }
14214 "#
14215 .unindent(),
14216 );
14217
14218 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14219 executor.run_until_parked();
14220
14221 cx.assert_state_with_diff(
14222 r#"
14223 use some::mod1;
14224 use some::mod2;
14225
14226 const A: u32 = 42;
14227 + const B: u32 = 42;
14228 + const C: u32 = 42;
14229 + const D: u32 = 42;
14230 + ˇ
14231
14232 fn main() {
14233 println!("hello");
14234
14235 println!("world");
14236 }
14237 "#
14238 .unindent(),
14239 );
14240
14241 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14242 executor.run_until_parked();
14243
14244 cx.assert_state_with_diff(
14245 r#"
14246 use some::mod1;
14247 use some::mod2;
14248
14249 const A: u32 = 42;
14250 + const B: u32 = 42;
14251 + const C: u32 = 42;
14252 + const D: u32 = 42;
14253 + const E: u32 = 42;
14254 + ˇ
14255
14256 fn main() {
14257 println!("hello");
14258
14259 println!("world");
14260 }
14261 "#
14262 .unindent(),
14263 );
14264
14265 cx.update_editor(|editor, window, cx| {
14266 editor.delete_line(&DeleteLine, window, cx);
14267 });
14268 executor.run_until_parked();
14269
14270 cx.assert_state_with_diff(
14271 r#"
14272 use some::mod1;
14273 use some::mod2;
14274
14275 const A: u32 = 42;
14276 + const B: u32 = 42;
14277 + const C: u32 = 42;
14278 + const D: u32 = 42;
14279 + const E: u32 = 42;
14280 ˇ
14281 fn main() {
14282 println!("hello");
14283
14284 println!("world");
14285 }
14286 "#
14287 .unindent(),
14288 );
14289
14290 cx.update_editor(|editor, window, cx| {
14291 editor.move_up(&MoveUp, window, cx);
14292 editor.delete_line(&DeleteLine, window, cx);
14293 editor.move_up(&MoveUp, window, cx);
14294 editor.delete_line(&DeleteLine, window, cx);
14295 editor.move_up(&MoveUp, window, cx);
14296 editor.delete_line(&DeleteLine, window, cx);
14297 });
14298 executor.run_until_parked();
14299 cx.assert_state_with_diff(
14300 r#"
14301 use some::mod1;
14302 use some::mod2;
14303
14304 const A: u32 = 42;
14305 + const B: u32 = 42;
14306 ˇ
14307 fn main() {
14308 println!("hello");
14309
14310 println!("world");
14311 }
14312 "#
14313 .unindent(),
14314 );
14315
14316 cx.update_editor(|editor, window, cx| {
14317 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14318 editor.delete_line(&DeleteLine, window, cx);
14319 });
14320 executor.run_until_parked();
14321 cx.assert_state_with_diff(
14322 r#"
14323 ˇ
14324 fn main() {
14325 println!("hello");
14326
14327 println!("world");
14328 }
14329 "#
14330 .unindent(),
14331 );
14332}
14333
14334#[gpui::test]
14335async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14336 init_test(cx, |_| {});
14337
14338 let mut cx = EditorTestContext::new(cx).await;
14339 cx.set_head_text(indoc! { "
14340 one
14341 two
14342 three
14343 four
14344 five
14345 "
14346 });
14347 cx.set_state(indoc! { "
14348 one
14349 ˇthree
14350 five
14351 "});
14352 cx.run_until_parked();
14353 cx.update_editor(|editor, window, cx| {
14354 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14355 });
14356 cx.assert_state_with_diff(
14357 indoc! { "
14358 one
14359 - two
14360 ˇthree
14361 - four
14362 five
14363 "}
14364 .to_string(),
14365 );
14366 cx.update_editor(|editor, window, cx| {
14367 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14368 });
14369
14370 cx.assert_state_with_diff(
14371 indoc! { "
14372 one
14373 ˇthree
14374 five
14375 "}
14376 .to_string(),
14377 );
14378
14379 cx.set_state(indoc! { "
14380 one
14381 ˇTWO
14382 three
14383 four
14384 five
14385 "});
14386 cx.run_until_parked();
14387 cx.update_editor(|editor, window, cx| {
14388 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14389 });
14390
14391 cx.assert_state_with_diff(
14392 indoc! { "
14393 one
14394 - two
14395 + ˇTWO
14396 three
14397 four
14398 five
14399 "}
14400 .to_string(),
14401 );
14402 cx.update_editor(|editor, window, cx| {
14403 editor.move_up(&Default::default(), window, cx);
14404 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14405 });
14406 cx.assert_state_with_diff(
14407 indoc! { "
14408 one
14409 ˇTWO
14410 three
14411 four
14412 five
14413 "}
14414 .to_string(),
14415 );
14416}
14417
14418#[gpui::test]
14419async fn test_edits_around_expanded_deletion_hunks(
14420 executor: BackgroundExecutor,
14421 cx: &mut TestAppContext,
14422) {
14423 init_test(cx, |_| {});
14424
14425 let mut cx = EditorTestContext::new(cx).await;
14426
14427 let diff_base = r#"
14428 use some::mod1;
14429 use some::mod2;
14430
14431 const A: u32 = 42;
14432 const B: u32 = 42;
14433 const C: u32 = 42;
14434
14435
14436 fn main() {
14437 println!("hello");
14438
14439 println!("world");
14440 }
14441 "#
14442 .unindent();
14443 executor.run_until_parked();
14444 cx.set_state(
14445 &r#"
14446 use some::mod1;
14447 use some::mod2;
14448
14449 ˇconst B: u32 = 42;
14450 const C: u32 = 42;
14451
14452
14453 fn main() {
14454 println!("hello");
14455
14456 println!("world");
14457 }
14458 "#
14459 .unindent(),
14460 );
14461
14462 cx.set_head_text(&diff_base);
14463 executor.run_until_parked();
14464
14465 cx.update_editor(|editor, window, cx| {
14466 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14467 });
14468 executor.run_until_parked();
14469
14470 cx.assert_state_with_diff(
14471 r#"
14472 use some::mod1;
14473 use some::mod2;
14474
14475 - const A: u32 = 42;
14476 ˇconst B: u32 = 42;
14477 const C: u32 = 42;
14478
14479
14480 fn main() {
14481 println!("hello");
14482
14483 println!("world");
14484 }
14485 "#
14486 .unindent(),
14487 );
14488
14489 cx.update_editor(|editor, window, cx| {
14490 editor.delete_line(&DeleteLine, window, cx);
14491 });
14492 executor.run_until_parked();
14493 cx.assert_state_with_diff(
14494 r#"
14495 use some::mod1;
14496 use some::mod2;
14497
14498 - const A: u32 = 42;
14499 - const B: u32 = 42;
14500 ˇconst C: u32 = 42;
14501
14502
14503 fn main() {
14504 println!("hello");
14505
14506 println!("world");
14507 }
14508 "#
14509 .unindent(),
14510 );
14511
14512 cx.update_editor(|editor, window, cx| {
14513 editor.delete_line(&DeleteLine, window, cx);
14514 });
14515 executor.run_until_parked();
14516 cx.assert_state_with_diff(
14517 r#"
14518 use some::mod1;
14519 use some::mod2;
14520
14521 - const A: u32 = 42;
14522 - const B: u32 = 42;
14523 - const C: u32 = 42;
14524 ˇ
14525
14526 fn main() {
14527 println!("hello");
14528
14529 println!("world");
14530 }
14531 "#
14532 .unindent(),
14533 );
14534
14535 cx.update_editor(|editor, window, cx| {
14536 editor.handle_input("replacement", window, cx);
14537 });
14538 executor.run_until_parked();
14539 cx.assert_state_with_diff(
14540 r#"
14541 use some::mod1;
14542 use some::mod2;
14543
14544 - const A: u32 = 42;
14545 - const B: u32 = 42;
14546 - const C: u32 = 42;
14547 -
14548 + replacementˇ
14549
14550 fn main() {
14551 println!("hello");
14552
14553 println!("world");
14554 }
14555 "#
14556 .unindent(),
14557 );
14558}
14559
14560#[gpui::test]
14561async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14562 init_test(cx, |_| {});
14563
14564 let mut cx = EditorTestContext::new(cx).await;
14565
14566 let base_text = r#"
14567 one
14568 two
14569 three
14570 four
14571 five
14572 "#
14573 .unindent();
14574 executor.run_until_parked();
14575 cx.set_state(
14576 &r#"
14577 one
14578 two
14579 fˇour
14580 five
14581 "#
14582 .unindent(),
14583 );
14584
14585 cx.set_head_text(&base_text);
14586 executor.run_until_parked();
14587
14588 cx.update_editor(|editor, window, cx| {
14589 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14590 });
14591 executor.run_until_parked();
14592
14593 cx.assert_state_with_diff(
14594 r#"
14595 one
14596 two
14597 - three
14598 fˇour
14599 five
14600 "#
14601 .unindent(),
14602 );
14603
14604 cx.update_editor(|editor, window, cx| {
14605 editor.backspace(&Backspace, window, cx);
14606 editor.backspace(&Backspace, window, cx);
14607 });
14608 executor.run_until_parked();
14609 cx.assert_state_with_diff(
14610 r#"
14611 one
14612 two
14613 - threeˇ
14614 - four
14615 + our
14616 five
14617 "#
14618 .unindent(),
14619 );
14620}
14621
14622#[gpui::test]
14623async fn test_edit_after_expanded_modification_hunk(
14624 executor: BackgroundExecutor,
14625 cx: &mut TestAppContext,
14626) {
14627 init_test(cx, |_| {});
14628
14629 let mut cx = EditorTestContext::new(cx).await;
14630
14631 let diff_base = r#"
14632 use some::mod1;
14633 use some::mod2;
14634
14635 const A: u32 = 42;
14636 const B: u32 = 42;
14637 const C: u32 = 42;
14638 const D: u32 = 42;
14639
14640
14641 fn main() {
14642 println!("hello");
14643
14644 println!("world");
14645 }"#
14646 .unindent();
14647
14648 cx.set_state(
14649 &r#"
14650 use some::mod1;
14651 use some::mod2;
14652
14653 const A: u32 = 42;
14654 const B: u32 = 42;
14655 const C: u32 = 43ˇ
14656 const D: u32 = 42;
14657
14658
14659 fn main() {
14660 println!("hello");
14661
14662 println!("world");
14663 }"#
14664 .unindent(),
14665 );
14666
14667 cx.set_head_text(&diff_base);
14668 executor.run_until_parked();
14669 cx.update_editor(|editor, window, cx| {
14670 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14671 });
14672 executor.run_until_parked();
14673
14674 cx.assert_state_with_diff(
14675 r#"
14676 use some::mod1;
14677 use some::mod2;
14678
14679 const A: u32 = 42;
14680 const B: u32 = 42;
14681 - const C: u32 = 42;
14682 + const C: u32 = 43ˇ
14683 const D: u32 = 42;
14684
14685
14686 fn main() {
14687 println!("hello");
14688
14689 println!("world");
14690 }"#
14691 .unindent(),
14692 );
14693
14694 cx.update_editor(|editor, window, cx| {
14695 editor.handle_input("\nnew_line\n", window, cx);
14696 });
14697 executor.run_until_parked();
14698
14699 cx.assert_state_with_diff(
14700 r#"
14701 use some::mod1;
14702 use some::mod2;
14703
14704 const A: u32 = 42;
14705 const B: u32 = 42;
14706 - const C: u32 = 42;
14707 + const C: u32 = 43
14708 + new_line
14709 + ˇ
14710 const D: u32 = 42;
14711
14712
14713 fn main() {
14714 println!("hello");
14715
14716 println!("world");
14717 }"#
14718 .unindent(),
14719 );
14720}
14721
14722#[gpui::test]
14723async fn test_stage_and_unstage_added_file_hunk(
14724 executor: BackgroundExecutor,
14725 cx: &mut TestAppContext,
14726) {
14727 init_test(cx, |_| {});
14728
14729 let mut cx = EditorTestContext::new(cx).await;
14730 cx.update_editor(|editor, _, cx| {
14731 editor.set_expand_all_diff_hunks(cx);
14732 });
14733
14734 let working_copy = r#"
14735 ˇfn main() {
14736 println!("hello, world!");
14737 }
14738 "#
14739 .unindent();
14740
14741 cx.set_state(&working_copy);
14742 executor.run_until_parked();
14743
14744 cx.assert_state_with_diff(
14745 r#"
14746 + ˇfn main() {
14747 + println!("hello, world!");
14748 + }
14749 "#
14750 .unindent(),
14751 );
14752 cx.assert_index_text(None);
14753
14754 cx.update_editor(|editor, window, cx| {
14755 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14756 });
14757 executor.run_until_parked();
14758 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14759 cx.assert_state_with_diff(
14760 r#"
14761 + ˇfn main() {
14762 + println!("hello, world!");
14763 + }
14764 "#
14765 .unindent(),
14766 );
14767
14768 cx.update_editor(|editor, window, cx| {
14769 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14770 });
14771 executor.run_until_parked();
14772 cx.assert_index_text(None);
14773}
14774
14775async fn setup_indent_guides_editor(
14776 text: &str,
14777 cx: &mut TestAppContext,
14778) -> (BufferId, EditorTestContext) {
14779 init_test(cx, |_| {});
14780
14781 let mut cx = EditorTestContext::new(cx).await;
14782
14783 let buffer_id = cx.update_editor(|editor, window, cx| {
14784 editor.set_text(text, window, cx);
14785 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14786
14787 buffer_ids[0]
14788 });
14789
14790 (buffer_id, cx)
14791}
14792
14793fn assert_indent_guides(
14794 range: Range<u32>,
14795 expected: Vec<IndentGuide>,
14796 active_indices: Option<Vec<usize>>,
14797 cx: &mut EditorTestContext,
14798) {
14799 let indent_guides = cx.update_editor(|editor, window, cx| {
14800 let snapshot = editor.snapshot(window, cx).display_snapshot;
14801 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14802 editor,
14803 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14804 true,
14805 &snapshot,
14806 cx,
14807 );
14808
14809 indent_guides.sort_by(|a, b| {
14810 a.depth.cmp(&b.depth).then(
14811 a.start_row
14812 .cmp(&b.start_row)
14813 .then(a.end_row.cmp(&b.end_row)),
14814 )
14815 });
14816 indent_guides
14817 });
14818
14819 if let Some(expected) = active_indices {
14820 let active_indices = cx.update_editor(|editor, window, cx| {
14821 let snapshot = editor.snapshot(window, cx).display_snapshot;
14822 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14823 });
14824
14825 assert_eq!(
14826 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14827 expected,
14828 "Active indent guide indices do not match"
14829 );
14830 }
14831
14832 assert_eq!(indent_guides, expected, "Indent guides do not match");
14833}
14834
14835fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14836 IndentGuide {
14837 buffer_id,
14838 start_row: MultiBufferRow(start_row),
14839 end_row: MultiBufferRow(end_row),
14840 depth,
14841 tab_size: 4,
14842 settings: IndentGuideSettings {
14843 enabled: true,
14844 line_width: 1,
14845 active_line_width: 1,
14846 ..Default::default()
14847 },
14848 }
14849}
14850
14851#[gpui::test]
14852async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14853 let (buffer_id, mut cx) = setup_indent_guides_editor(
14854 &"
14855 fn main() {
14856 let a = 1;
14857 }"
14858 .unindent(),
14859 cx,
14860 )
14861 .await;
14862
14863 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14864}
14865
14866#[gpui::test]
14867async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14868 let (buffer_id, mut cx) = setup_indent_guides_editor(
14869 &"
14870 fn main() {
14871 let a = 1;
14872 let b = 2;
14873 }"
14874 .unindent(),
14875 cx,
14876 )
14877 .await;
14878
14879 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14880}
14881
14882#[gpui::test]
14883async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14884 let (buffer_id, mut cx) = setup_indent_guides_editor(
14885 &"
14886 fn main() {
14887 let a = 1;
14888 if a == 3 {
14889 let b = 2;
14890 } else {
14891 let c = 3;
14892 }
14893 }"
14894 .unindent(),
14895 cx,
14896 )
14897 .await;
14898
14899 assert_indent_guides(
14900 0..8,
14901 vec![
14902 indent_guide(buffer_id, 1, 6, 0),
14903 indent_guide(buffer_id, 3, 3, 1),
14904 indent_guide(buffer_id, 5, 5, 1),
14905 ],
14906 None,
14907 &mut cx,
14908 );
14909}
14910
14911#[gpui::test]
14912async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14913 let (buffer_id, mut cx) = setup_indent_guides_editor(
14914 &"
14915 fn main() {
14916 let a = 1;
14917 let b = 2;
14918 let c = 3;
14919 }"
14920 .unindent(),
14921 cx,
14922 )
14923 .await;
14924
14925 assert_indent_guides(
14926 0..5,
14927 vec![
14928 indent_guide(buffer_id, 1, 3, 0),
14929 indent_guide(buffer_id, 2, 2, 1),
14930 ],
14931 None,
14932 &mut cx,
14933 );
14934}
14935
14936#[gpui::test]
14937async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14938 let (buffer_id, mut cx) = setup_indent_guides_editor(
14939 &"
14940 fn main() {
14941 let a = 1;
14942
14943 let c = 3;
14944 }"
14945 .unindent(),
14946 cx,
14947 )
14948 .await;
14949
14950 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14951}
14952
14953#[gpui::test]
14954async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14955 let (buffer_id, mut cx) = setup_indent_guides_editor(
14956 &"
14957 fn main() {
14958 let a = 1;
14959
14960 let c = 3;
14961
14962 if a == 3 {
14963 let b = 2;
14964 } else {
14965 let c = 3;
14966 }
14967 }"
14968 .unindent(),
14969 cx,
14970 )
14971 .await;
14972
14973 assert_indent_guides(
14974 0..11,
14975 vec![
14976 indent_guide(buffer_id, 1, 9, 0),
14977 indent_guide(buffer_id, 6, 6, 1),
14978 indent_guide(buffer_id, 8, 8, 1),
14979 ],
14980 None,
14981 &mut cx,
14982 );
14983}
14984
14985#[gpui::test]
14986async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14987 let (buffer_id, mut cx) = setup_indent_guides_editor(
14988 &"
14989 fn main() {
14990 let a = 1;
14991
14992 let c = 3;
14993
14994 if a == 3 {
14995 let b = 2;
14996 } else {
14997 let c = 3;
14998 }
14999 }"
15000 .unindent(),
15001 cx,
15002 )
15003 .await;
15004
15005 assert_indent_guides(
15006 1..11,
15007 vec![
15008 indent_guide(buffer_id, 1, 9, 0),
15009 indent_guide(buffer_id, 6, 6, 1),
15010 indent_guide(buffer_id, 8, 8, 1),
15011 ],
15012 None,
15013 &mut cx,
15014 );
15015}
15016
15017#[gpui::test]
15018async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15019 let (buffer_id, mut cx) = setup_indent_guides_editor(
15020 &"
15021 fn main() {
15022 let a = 1;
15023
15024 let c = 3;
15025
15026 if a == 3 {
15027 let b = 2;
15028 } else {
15029 let c = 3;
15030 }
15031 }"
15032 .unindent(),
15033 cx,
15034 )
15035 .await;
15036
15037 assert_indent_guides(
15038 1..10,
15039 vec![
15040 indent_guide(buffer_id, 1, 9, 0),
15041 indent_guide(buffer_id, 6, 6, 1),
15042 indent_guide(buffer_id, 8, 8, 1),
15043 ],
15044 None,
15045 &mut cx,
15046 );
15047}
15048
15049#[gpui::test]
15050async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15051 let (buffer_id, mut cx) = setup_indent_guides_editor(
15052 &"
15053 block1
15054 block2
15055 block3
15056 block4
15057 block2
15058 block1
15059 block1"
15060 .unindent(),
15061 cx,
15062 )
15063 .await;
15064
15065 assert_indent_guides(
15066 1..10,
15067 vec![
15068 indent_guide(buffer_id, 1, 4, 0),
15069 indent_guide(buffer_id, 2, 3, 1),
15070 indent_guide(buffer_id, 3, 3, 2),
15071 ],
15072 None,
15073 &mut cx,
15074 );
15075}
15076
15077#[gpui::test]
15078async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15079 let (buffer_id, mut cx) = setup_indent_guides_editor(
15080 &"
15081 block1
15082 block2
15083 block3
15084
15085 block1
15086 block1"
15087 .unindent(),
15088 cx,
15089 )
15090 .await;
15091
15092 assert_indent_guides(
15093 0..6,
15094 vec![
15095 indent_guide(buffer_id, 1, 2, 0),
15096 indent_guide(buffer_id, 2, 2, 1),
15097 ],
15098 None,
15099 &mut cx,
15100 );
15101}
15102
15103#[gpui::test]
15104async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15105 let (buffer_id, mut cx) = setup_indent_guides_editor(
15106 &"
15107 block1
15108
15109
15110
15111 block2
15112 "
15113 .unindent(),
15114 cx,
15115 )
15116 .await;
15117
15118 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15119}
15120
15121#[gpui::test]
15122async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15123 let (buffer_id, mut cx) = setup_indent_guides_editor(
15124 &"
15125 def a:
15126 \tb = 3
15127 \tif True:
15128 \t\tc = 4
15129 \t\td = 5
15130 \tprint(b)
15131 "
15132 .unindent(),
15133 cx,
15134 )
15135 .await;
15136
15137 assert_indent_guides(
15138 0..6,
15139 vec![
15140 indent_guide(buffer_id, 1, 6, 0),
15141 indent_guide(buffer_id, 3, 4, 1),
15142 ],
15143 None,
15144 &mut cx,
15145 );
15146}
15147
15148#[gpui::test]
15149async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15150 let (buffer_id, mut cx) = setup_indent_guides_editor(
15151 &"
15152 fn main() {
15153 let a = 1;
15154 }"
15155 .unindent(),
15156 cx,
15157 )
15158 .await;
15159
15160 cx.update_editor(|editor, window, cx| {
15161 editor.change_selections(None, window, cx, |s| {
15162 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15163 });
15164 });
15165
15166 assert_indent_guides(
15167 0..3,
15168 vec![indent_guide(buffer_id, 1, 1, 0)],
15169 Some(vec![0]),
15170 &mut cx,
15171 );
15172}
15173
15174#[gpui::test]
15175async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15176 let (buffer_id, mut cx) = setup_indent_guides_editor(
15177 &"
15178 fn main() {
15179 if 1 == 2 {
15180 let a = 1;
15181 }
15182 }"
15183 .unindent(),
15184 cx,
15185 )
15186 .await;
15187
15188 cx.update_editor(|editor, window, cx| {
15189 editor.change_selections(None, window, cx, |s| {
15190 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15191 });
15192 });
15193
15194 assert_indent_guides(
15195 0..4,
15196 vec![
15197 indent_guide(buffer_id, 1, 3, 0),
15198 indent_guide(buffer_id, 2, 2, 1),
15199 ],
15200 Some(vec![1]),
15201 &mut cx,
15202 );
15203
15204 cx.update_editor(|editor, window, cx| {
15205 editor.change_selections(None, window, cx, |s| {
15206 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15207 });
15208 });
15209
15210 assert_indent_guides(
15211 0..4,
15212 vec![
15213 indent_guide(buffer_id, 1, 3, 0),
15214 indent_guide(buffer_id, 2, 2, 1),
15215 ],
15216 Some(vec![1]),
15217 &mut cx,
15218 );
15219
15220 cx.update_editor(|editor, window, cx| {
15221 editor.change_selections(None, window, cx, |s| {
15222 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15223 });
15224 });
15225
15226 assert_indent_guides(
15227 0..4,
15228 vec![
15229 indent_guide(buffer_id, 1, 3, 0),
15230 indent_guide(buffer_id, 2, 2, 1),
15231 ],
15232 Some(vec![0]),
15233 &mut cx,
15234 );
15235}
15236
15237#[gpui::test]
15238async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15239 let (buffer_id, mut cx) = setup_indent_guides_editor(
15240 &"
15241 fn main() {
15242 let a = 1;
15243
15244 let b = 2;
15245 }"
15246 .unindent(),
15247 cx,
15248 )
15249 .await;
15250
15251 cx.update_editor(|editor, window, cx| {
15252 editor.change_selections(None, window, cx, |s| {
15253 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15254 });
15255 });
15256
15257 assert_indent_guides(
15258 0..5,
15259 vec![indent_guide(buffer_id, 1, 3, 0)],
15260 Some(vec![0]),
15261 &mut cx,
15262 );
15263}
15264
15265#[gpui::test]
15266async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15267 let (buffer_id, mut cx) = setup_indent_guides_editor(
15268 &"
15269 def m:
15270 a = 1
15271 pass"
15272 .unindent(),
15273 cx,
15274 )
15275 .await;
15276
15277 cx.update_editor(|editor, window, cx| {
15278 editor.change_selections(None, window, cx, |s| {
15279 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15280 });
15281 });
15282
15283 assert_indent_guides(
15284 0..3,
15285 vec![indent_guide(buffer_id, 1, 2, 0)],
15286 Some(vec![0]),
15287 &mut cx,
15288 );
15289}
15290
15291#[gpui::test]
15292async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15293 init_test(cx, |_| {});
15294 let mut cx = EditorTestContext::new(cx).await;
15295 let text = indoc! {
15296 "
15297 impl A {
15298 fn b() {
15299 0;
15300 3;
15301 5;
15302 6;
15303 7;
15304 }
15305 }
15306 "
15307 };
15308 let base_text = indoc! {
15309 "
15310 impl A {
15311 fn b() {
15312 0;
15313 1;
15314 2;
15315 3;
15316 4;
15317 }
15318 fn c() {
15319 5;
15320 6;
15321 7;
15322 }
15323 }
15324 "
15325 };
15326
15327 cx.update_editor(|editor, window, cx| {
15328 editor.set_text(text, window, cx);
15329
15330 editor.buffer().update(cx, |multibuffer, cx| {
15331 let buffer = multibuffer.as_singleton().unwrap();
15332 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15333
15334 multibuffer.set_all_diff_hunks_expanded(cx);
15335 multibuffer.add_diff(diff, cx);
15336
15337 buffer.read(cx).remote_id()
15338 })
15339 });
15340 cx.run_until_parked();
15341
15342 cx.assert_state_with_diff(
15343 indoc! { "
15344 impl A {
15345 fn b() {
15346 0;
15347 - 1;
15348 - 2;
15349 3;
15350 - 4;
15351 - }
15352 - fn c() {
15353 5;
15354 6;
15355 7;
15356 }
15357 }
15358 ˇ"
15359 }
15360 .to_string(),
15361 );
15362
15363 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15364 editor
15365 .snapshot(window, cx)
15366 .buffer_snapshot
15367 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15368 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15369 .collect::<Vec<_>>()
15370 });
15371 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15372 assert_eq!(
15373 actual_guides,
15374 vec![
15375 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15376 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15377 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15378 ]
15379 );
15380}
15381
15382#[gpui::test]
15383async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15384 init_test(cx, |_| {});
15385 let mut cx = EditorTestContext::new(cx).await;
15386
15387 let diff_base = r#"
15388 a
15389 b
15390 c
15391 "#
15392 .unindent();
15393
15394 cx.set_state(
15395 &r#"
15396 ˇA
15397 b
15398 C
15399 "#
15400 .unindent(),
15401 );
15402 cx.set_head_text(&diff_base);
15403 cx.update_editor(|editor, window, cx| {
15404 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15405 });
15406 executor.run_until_parked();
15407
15408 let both_hunks_expanded = r#"
15409 - a
15410 + ˇA
15411 b
15412 - c
15413 + C
15414 "#
15415 .unindent();
15416
15417 cx.assert_state_with_diff(both_hunks_expanded.clone());
15418
15419 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15420 let snapshot = editor.snapshot(window, cx);
15421 let hunks = editor
15422 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15423 .collect::<Vec<_>>();
15424 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15425 let buffer_id = hunks[0].buffer_id;
15426 hunks
15427 .into_iter()
15428 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15429 .collect::<Vec<_>>()
15430 });
15431 assert_eq!(hunk_ranges.len(), 2);
15432
15433 cx.update_editor(|editor, _, cx| {
15434 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15435 });
15436 executor.run_until_parked();
15437
15438 let second_hunk_expanded = r#"
15439 ˇA
15440 b
15441 - c
15442 + C
15443 "#
15444 .unindent();
15445
15446 cx.assert_state_with_diff(second_hunk_expanded);
15447
15448 cx.update_editor(|editor, _, cx| {
15449 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15450 });
15451 executor.run_until_parked();
15452
15453 cx.assert_state_with_diff(both_hunks_expanded.clone());
15454
15455 cx.update_editor(|editor, _, cx| {
15456 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15457 });
15458 executor.run_until_parked();
15459
15460 let first_hunk_expanded = r#"
15461 - a
15462 + ˇA
15463 b
15464 C
15465 "#
15466 .unindent();
15467
15468 cx.assert_state_with_diff(first_hunk_expanded);
15469
15470 cx.update_editor(|editor, _, cx| {
15471 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15472 });
15473 executor.run_until_parked();
15474
15475 cx.assert_state_with_diff(both_hunks_expanded);
15476
15477 cx.set_state(
15478 &r#"
15479 ˇA
15480 b
15481 "#
15482 .unindent(),
15483 );
15484 cx.run_until_parked();
15485
15486 // TODO this cursor position seems bad
15487 cx.assert_state_with_diff(
15488 r#"
15489 - ˇa
15490 + A
15491 b
15492 "#
15493 .unindent(),
15494 );
15495
15496 cx.update_editor(|editor, window, cx| {
15497 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15498 });
15499
15500 cx.assert_state_with_diff(
15501 r#"
15502 - ˇa
15503 + A
15504 b
15505 - c
15506 "#
15507 .unindent(),
15508 );
15509
15510 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15511 let snapshot = editor.snapshot(window, cx);
15512 let hunks = editor
15513 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15514 .collect::<Vec<_>>();
15515 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15516 let buffer_id = hunks[0].buffer_id;
15517 hunks
15518 .into_iter()
15519 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15520 .collect::<Vec<_>>()
15521 });
15522 assert_eq!(hunk_ranges.len(), 2);
15523
15524 cx.update_editor(|editor, _, cx| {
15525 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15526 });
15527 executor.run_until_parked();
15528
15529 cx.assert_state_with_diff(
15530 r#"
15531 - ˇa
15532 + A
15533 b
15534 "#
15535 .unindent(),
15536 );
15537}
15538
15539#[gpui::test]
15540async fn test_toggle_deletion_hunk_at_start_of_file(
15541 executor: BackgroundExecutor,
15542 cx: &mut TestAppContext,
15543) {
15544 init_test(cx, |_| {});
15545 let mut cx = EditorTestContext::new(cx).await;
15546
15547 let diff_base = r#"
15548 a
15549 b
15550 c
15551 "#
15552 .unindent();
15553
15554 cx.set_state(
15555 &r#"
15556 ˇb
15557 c
15558 "#
15559 .unindent(),
15560 );
15561 cx.set_head_text(&diff_base);
15562 cx.update_editor(|editor, window, cx| {
15563 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15564 });
15565 executor.run_until_parked();
15566
15567 let hunk_expanded = r#"
15568 - a
15569 ˇb
15570 c
15571 "#
15572 .unindent();
15573
15574 cx.assert_state_with_diff(hunk_expanded.clone());
15575
15576 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15577 let snapshot = editor.snapshot(window, cx);
15578 let hunks = editor
15579 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15580 .collect::<Vec<_>>();
15581 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15582 let buffer_id = hunks[0].buffer_id;
15583 hunks
15584 .into_iter()
15585 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15586 .collect::<Vec<_>>()
15587 });
15588 assert_eq!(hunk_ranges.len(), 1);
15589
15590 cx.update_editor(|editor, _, cx| {
15591 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15592 });
15593 executor.run_until_parked();
15594
15595 let hunk_collapsed = r#"
15596 ˇb
15597 c
15598 "#
15599 .unindent();
15600
15601 cx.assert_state_with_diff(hunk_collapsed);
15602
15603 cx.update_editor(|editor, _, cx| {
15604 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15605 });
15606 executor.run_until_parked();
15607
15608 cx.assert_state_with_diff(hunk_expanded.clone());
15609}
15610
15611#[gpui::test]
15612async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15613 init_test(cx, |_| {});
15614
15615 let fs = FakeFs::new(cx.executor());
15616 fs.insert_tree(
15617 path!("/test"),
15618 json!({
15619 ".git": {},
15620 "file-1": "ONE\n",
15621 "file-2": "TWO\n",
15622 "file-3": "THREE\n",
15623 }),
15624 )
15625 .await;
15626
15627 fs.set_head_for_repo(
15628 path!("/test/.git").as_ref(),
15629 &[
15630 ("file-1".into(), "one\n".into()),
15631 ("file-2".into(), "two\n".into()),
15632 ("file-3".into(), "three\n".into()),
15633 ],
15634 );
15635
15636 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15637 let mut buffers = vec![];
15638 for i in 1..=3 {
15639 let buffer = project
15640 .update(cx, |project, cx| {
15641 let path = format!(path!("/test/file-{}"), i);
15642 project.open_local_buffer(path, cx)
15643 })
15644 .await
15645 .unwrap();
15646 buffers.push(buffer);
15647 }
15648
15649 let multibuffer = cx.new(|cx| {
15650 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15651 multibuffer.set_all_diff_hunks_expanded(cx);
15652 for buffer in &buffers {
15653 let snapshot = buffer.read(cx).snapshot();
15654 multibuffer.set_excerpts_for_path(
15655 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15656 buffer.clone(),
15657 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15658 DEFAULT_MULTIBUFFER_CONTEXT,
15659 cx,
15660 );
15661 }
15662 multibuffer
15663 });
15664
15665 let editor = cx.add_window(|window, cx| {
15666 Editor::new(
15667 EditorMode::Full,
15668 multibuffer,
15669 Some(project),
15670 true,
15671 window,
15672 cx,
15673 )
15674 });
15675 cx.run_until_parked();
15676
15677 let snapshot = editor
15678 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15679 .unwrap();
15680 let hunks = snapshot
15681 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15682 .map(|hunk| match hunk {
15683 DisplayDiffHunk::Unfolded {
15684 display_row_range, ..
15685 } => display_row_range,
15686 DisplayDiffHunk::Folded { .. } => unreachable!(),
15687 })
15688 .collect::<Vec<_>>();
15689 assert_eq!(
15690 hunks,
15691 [
15692 DisplayRow(3)..DisplayRow(5),
15693 DisplayRow(10)..DisplayRow(12),
15694 DisplayRow(17)..DisplayRow(19),
15695 ]
15696 );
15697}
15698
15699#[gpui::test]
15700async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15701 init_test(cx, |_| {});
15702
15703 let mut cx = EditorTestContext::new(cx).await;
15704 cx.set_head_text(indoc! { "
15705 one
15706 two
15707 three
15708 four
15709 five
15710 "
15711 });
15712 cx.set_index_text(indoc! { "
15713 one
15714 two
15715 three
15716 four
15717 five
15718 "
15719 });
15720 cx.set_state(indoc! {"
15721 one
15722 TWO
15723 ˇTHREE
15724 FOUR
15725 five
15726 "});
15727 cx.run_until_parked();
15728 cx.update_editor(|editor, window, cx| {
15729 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15730 });
15731 cx.run_until_parked();
15732 cx.assert_index_text(Some(indoc! {"
15733 one
15734 TWO
15735 THREE
15736 FOUR
15737 five
15738 "}));
15739 cx.set_state(indoc! { "
15740 one
15741 TWO
15742 ˇTHREE-HUNDRED
15743 FOUR
15744 five
15745 "});
15746 cx.run_until_parked();
15747 cx.update_editor(|editor, window, cx| {
15748 let snapshot = editor.snapshot(window, cx);
15749 let hunks = editor
15750 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15751 .collect::<Vec<_>>();
15752 assert_eq!(hunks.len(), 1);
15753 assert_eq!(
15754 hunks[0].status(),
15755 DiffHunkStatus {
15756 kind: DiffHunkStatusKind::Modified,
15757 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15758 }
15759 );
15760
15761 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15762 });
15763 cx.run_until_parked();
15764 cx.assert_index_text(Some(indoc! {"
15765 one
15766 TWO
15767 THREE-HUNDRED
15768 FOUR
15769 five
15770 "}));
15771}
15772
15773#[gpui::test]
15774fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15775 init_test(cx, |_| {});
15776
15777 let editor = cx.add_window(|window, cx| {
15778 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15779 build_editor(buffer, window, cx)
15780 });
15781
15782 let render_args = Arc::new(Mutex::new(None));
15783 let snapshot = editor
15784 .update(cx, |editor, window, cx| {
15785 let snapshot = editor.buffer().read(cx).snapshot(cx);
15786 let range =
15787 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15788
15789 struct RenderArgs {
15790 row: MultiBufferRow,
15791 folded: bool,
15792 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15793 }
15794
15795 let crease = Crease::inline(
15796 range,
15797 FoldPlaceholder::test(),
15798 {
15799 let toggle_callback = render_args.clone();
15800 move |row, folded, callback, _window, _cx| {
15801 *toggle_callback.lock() = Some(RenderArgs {
15802 row,
15803 folded,
15804 callback,
15805 });
15806 div()
15807 }
15808 },
15809 |_row, _folded, _window, _cx| div(),
15810 );
15811
15812 editor.insert_creases(Some(crease), cx);
15813 let snapshot = editor.snapshot(window, cx);
15814 let _div = snapshot.render_crease_toggle(
15815 MultiBufferRow(1),
15816 false,
15817 cx.entity().clone(),
15818 window,
15819 cx,
15820 );
15821 snapshot
15822 })
15823 .unwrap();
15824
15825 let render_args = render_args.lock().take().unwrap();
15826 assert_eq!(render_args.row, MultiBufferRow(1));
15827 assert!(!render_args.folded);
15828 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15829
15830 cx.update_window(*editor, |_, window, cx| {
15831 (render_args.callback)(true, window, cx)
15832 })
15833 .unwrap();
15834 let snapshot = editor
15835 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15836 .unwrap();
15837 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15838
15839 cx.update_window(*editor, |_, window, cx| {
15840 (render_args.callback)(false, window, cx)
15841 })
15842 .unwrap();
15843 let snapshot = editor
15844 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15845 .unwrap();
15846 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15847}
15848
15849#[gpui::test]
15850async fn test_input_text(cx: &mut TestAppContext) {
15851 init_test(cx, |_| {});
15852 let mut cx = EditorTestContext::new(cx).await;
15853
15854 cx.set_state(
15855 &r#"ˇone
15856 two
15857
15858 three
15859 fourˇ
15860 five
15861
15862 siˇx"#
15863 .unindent(),
15864 );
15865
15866 cx.dispatch_action(HandleInput(String::new()));
15867 cx.assert_editor_state(
15868 &r#"ˇone
15869 two
15870
15871 three
15872 fourˇ
15873 five
15874
15875 siˇx"#
15876 .unindent(),
15877 );
15878
15879 cx.dispatch_action(HandleInput("AAAA".to_string()));
15880 cx.assert_editor_state(
15881 &r#"AAAAˇone
15882 two
15883
15884 three
15885 fourAAAAˇ
15886 five
15887
15888 siAAAAˇx"#
15889 .unindent(),
15890 );
15891}
15892
15893#[gpui::test]
15894async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15895 init_test(cx, |_| {});
15896
15897 let mut cx = EditorTestContext::new(cx).await;
15898 cx.set_state(
15899 r#"let foo = 1;
15900let foo = 2;
15901let foo = 3;
15902let fooˇ = 4;
15903let foo = 5;
15904let foo = 6;
15905let foo = 7;
15906let foo = 8;
15907let foo = 9;
15908let foo = 10;
15909let foo = 11;
15910let foo = 12;
15911let foo = 13;
15912let foo = 14;
15913let foo = 15;"#,
15914 );
15915
15916 cx.update_editor(|e, window, cx| {
15917 assert_eq!(
15918 e.next_scroll_position,
15919 NextScrollCursorCenterTopBottom::Center,
15920 "Default next scroll direction is center",
15921 );
15922
15923 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15924 assert_eq!(
15925 e.next_scroll_position,
15926 NextScrollCursorCenterTopBottom::Top,
15927 "After center, next scroll direction should be top",
15928 );
15929
15930 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15931 assert_eq!(
15932 e.next_scroll_position,
15933 NextScrollCursorCenterTopBottom::Bottom,
15934 "After top, next scroll direction should be bottom",
15935 );
15936
15937 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15938 assert_eq!(
15939 e.next_scroll_position,
15940 NextScrollCursorCenterTopBottom::Center,
15941 "After bottom, scrolling should start over",
15942 );
15943
15944 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15945 assert_eq!(
15946 e.next_scroll_position,
15947 NextScrollCursorCenterTopBottom::Top,
15948 "Scrolling continues if retriggered fast enough"
15949 );
15950 });
15951
15952 cx.executor()
15953 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15954 cx.executor().run_until_parked();
15955 cx.update_editor(|e, _, _| {
15956 assert_eq!(
15957 e.next_scroll_position,
15958 NextScrollCursorCenterTopBottom::Center,
15959 "If scrolling is not triggered fast enough, it should reset"
15960 );
15961 });
15962}
15963
15964#[gpui::test]
15965async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15966 init_test(cx, |_| {});
15967 let mut cx = EditorLspTestContext::new_rust(
15968 lsp::ServerCapabilities {
15969 definition_provider: Some(lsp::OneOf::Left(true)),
15970 references_provider: Some(lsp::OneOf::Left(true)),
15971 ..lsp::ServerCapabilities::default()
15972 },
15973 cx,
15974 )
15975 .await;
15976
15977 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15978 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15979 move |params, _| async move {
15980 if empty_go_to_definition {
15981 Ok(None)
15982 } else {
15983 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15984 uri: params.text_document_position_params.text_document.uri,
15985 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15986 })))
15987 }
15988 },
15989 );
15990 let references =
15991 cx.lsp
15992 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15993 Ok(Some(vec![lsp::Location {
15994 uri: params.text_document_position.text_document.uri,
15995 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15996 }]))
15997 });
15998 (go_to_definition, references)
15999 };
16000
16001 cx.set_state(
16002 &r#"fn one() {
16003 let mut a = ˇtwo();
16004 }
16005
16006 fn two() {}"#
16007 .unindent(),
16008 );
16009 set_up_lsp_handlers(false, &mut cx);
16010 let navigated = cx
16011 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16012 .await
16013 .expect("Failed to navigate to definition");
16014 assert_eq!(
16015 navigated,
16016 Navigated::Yes,
16017 "Should have navigated to definition from the GetDefinition response"
16018 );
16019 cx.assert_editor_state(
16020 &r#"fn one() {
16021 let mut a = two();
16022 }
16023
16024 fn «twoˇ»() {}"#
16025 .unindent(),
16026 );
16027
16028 let editors = cx.update_workspace(|workspace, _, cx| {
16029 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16030 });
16031 cx.update_editor(|_, _, test_editor_cx| {
16032 assert_eq!(
16033 editors.len(),
16034 1,
16035 "Initially, only one, test, editor should be open in the workspace"
16036 );
16037 assert_eq!(
16038 test_editor_cx.entity(),
16039 editors.last().expect("Asserted len is 1").clone()
16040 );
16041 });
16042
16043 set_up_lsp_handlers(true, &mut cx);
16044 let navigated = cx
16045 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16046 .await
16047 .expect("Failed to navigate to lookup references");
16048 assert_eq!(
16049 navigated,
16050 Navigated::Yes,
16051 "Should have navigated to references as a fallback after empty GoToDefinition response"
16052 );
16053 // We should not change the selections in the existing file,
16054 // if opening another milti buffer with the references
16055 cx.assert_editor_state(
16056 &r#"fn one() {
16057 let mut a = two();
16058 }
16059
16060 fn «twoˇ»() {}"#
16061 .unindent(),
16062 );
16063 let editors = cx.update_workspace(|workspace, _, cx| {
16064 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16065 });
16066 cx.update_editor(|_, _, test_editor_cx| {
16067 assert_eq!(
16068 editors.len(),
16069 2,
16070 "After falling back to references search, we open a new editor with the results"
16071 );
16072 let references_fallback_text = editors
16073 .into_iter()
16074 .find(|new_editor| *new_editor != test_editor_cx.entity())
16075 .expect("Should have one non-test editor now")
16076 .read(test_editor_cx)
16077 .text(test_editor_cx);
16078 assert_eq!(
16079 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16080 "Should use the range from the references response and not the GoToDefinition one"
16081 );
16082 });
16083}
16084
16085#[gpui::test]
16086async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16087 init_test(cx, |_| {});
16088
16089 let language = Arc::new(Language::new(
16090 LanguageConfig::default(),
16091 Some(tree_sitter_rust::LANGUAGE.into()),
16092 ));
16093
16094 let text = r#"
16095 #[cfg(test)]
16096 mod tests() {
16097 #[test]
16098 fn runnable_1() {
16099 let a = 1;
16100 }
16101
16102 #[test]
16103 fn runnable_2() {
16104 let a = 1;
16105 let b = 2;
16106 }
16107 }
16108 "#
16109 .unindent();
16110
16111 let fs = FakeFs::new(cx.executor());
16112 fs.insert_file("/file.rs", Default::default()).await;
16113
16114 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16115 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16116 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16117 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16118 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16119
16120 let editor = cx.new_window_entity(|window, cx| {
16121 Editor::new(
16122 EditorMode::Full,
16123 multi_buffer,
16124 Some(project.clone()),
16125 true,
16126 window,
16127 cx,
16128 )
16129 });
16130
16131 editor.update_in(cx, |editor, window, cx| {
16132 let snapshot = editor.buffer().read(cx).snapshot(cx);
16133 editor.tasks.insert(
16134 (buffer.read(cx).remote_id(), 3),
16135 RunnableTasks {
16136 templates: vec![],
16137 offset: snapshot.anchor_before(43),
16138 column: 0,
16139 extra_variables: HashMap::default(),
16140 context_range: BufferOffset(43)..BufferOffset(85),
16141 },
16142 );
16143 editor.tasks.insert(
16144 (buffer.read(cx).remote_id(), 8),
16145 RunnableTasks {
16146 templates: vec![],
16147 offset: snapshot.anchor_before(86),
16148 column: 0,
16149 extra_variables: HashMap::default(),
16150 context_range: BufferOffset(86)..BufferOffset(191),
16151 },
16152 );
16153
16154 // Test finding task when cursor is inside function body
16155 editor.change_selections(None, window, cx, |s| {
16156 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16157 });
16158 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16159 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16160
16161 // Test finding task when cursor is on function name
16162 editor.change_selections(None, window, cx, |s| {
16163 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16164 });
16165 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16166 assert_eq!(row, 8, "Should find task when cursor is on function name");
16167 });
16168}
16169
16170#[gpui::test]
16171async fn test_folding_buffers(cx: &mut TestAppContext) {
16172 init_test(cx, |_| {});
16173
16174 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16175 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16176 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16177
16178 let fs = FakeFs::new(cx.executor());
16179 fs.insert_tree(
16180 path!("/a"),
16181 json!({
16182 "first.rs": sample_text_1,
16183 "second.rs": sample_text_2,
16184 "third.rs": sample_text_3,
16185 }),
16186 )
16187 .await;
16188 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16189 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16190 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16191 let worktree = project.update(cx, |project, cx| {
16192 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16193 assert_eq!(worktrees.len(), 1);
16194 worktrees.pop().unwrap()
16195 });
16196 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16197
16198 let buffer_1 = project
16199 .update(cx, |project, cx| {
16200 project.open_buffer((worktree_id, "first.rs"), cx)
16201 })
16202 .await
16203 .unwrap();
16204 let buffer_2 = project
16205 .update(cx, |project, cx| {
16206 project.open_buffer((worktree_id, "second.rs"), cx)
16207 })
16208 .await
16209 .unwrap();
16210 let buffer_3 = project
16211 .update(cx, |project, cx| {
16212 project.open_buffer((worktree_id, "third.rs"), cx)
16213 })
16214 .await
16215 .unwrap();
16216
16217 let multi_buffer = cx.new(|cx| {
16218 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16219 multi_buffer.push_excerpts(
16220 buffer_1.clone(),
16221 [
16222 ExcerptRange {
16223 context: Point::new(0, 0)..Point::new(3, 0),
16224 primary: None,
16225 },
16226 ExcerptRange {
16227 context: Point::new(5, 0)..Point::new(7, 0),
16228 primary: None,
16229 },
16230 ExcerptRange {
16231 context: Point::new(9, 0)..Point::new(10, 4),
16232 primary: None,
16233 },
16234 ],
16235 cx,
16236 );
16237 multi_buffer.push_excerpts(
16238 buffer_2.clone(),
16239 [
16240 ExcerptRange {
16241 context: Point::new(0, 0)..Point::new(3, 0),
16242 primary: None,
16243 },
16244 ExcerptRange {
16245 context: Point::new(5, 0)..Point::new(7, 0),
16246 primary: None,
16247 },
16248 ExcerptRange {
16249 context: Point::new(9, 0)..Point::new(10, 4),
16250 primary: None,
16251 },
16252 ],
16253 cx,
16254 );
16255 multi_buffer.push_excerpts(
16256 buffer_3.clone(),
16257 [
16258 ExcerptRange {
16259 context: Point::new(0, 0)..Point::new(3, 0),
16260 primary: None,
16261 },
16262 ExcerptRange {
16263 context: Point::new(5, 0)..Point::new(7, 0),
16264 primary: None,
16265 },
16266 ExcerptRange {
16267 context: Point::new(9, 0)..Point::new(10, 4),
16268 primary: None,
16269 },
16270 ],
16271 cx,
16272 );
16273 multi_buffer
16274 });
16275 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16276 Editor::new(
16277 EditorMode::Full,
16278 multi_buffer.clone(),
16279 Some(project.clone()),
16280 true,
16281 window,
16282 cx,
16283 )
16284 });
16285
16286 assert_eq!(
16287 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16288 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16289 );
16290
16291 multi_buffer_editor.update(cx, |editor, cx| {
16292 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16293 });
16294 assert_eq!(
16295 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16296 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16297 "After folding the first buffer, its text should not be displayed"
16298 );
16299
16300 multi_buffer_editor.update(cx, |editor, cx| {
16301 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16302 });
16303 assert_eq!(
16304 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16305 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16306 "After folding the second buffer, its text should not be displayed"
16307 );
16308
16309 multi_buffer_editor.update(cx, |editor, cx| {
16310 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16311 });
16312 assert_eq!(
16313 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16314 "\n\n\n\n\n",
16315 "After folding the third buffer, its text should not be displayed"
16316 );
16317
16318 // Emulate selection inside the fold logic, that should work
16319 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16320 editor
16321 .snapshot(window, cx)
16322 .next_line_boundary(Point::new(0, 4));
16323 });
16324
16325 multi_buffer_editor.update(cx, |editor, cx| {
16326 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16327 });
16328 assert_eq!(
16329 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16330 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16331 "After unfolding the second buffer, its text should be displayed"
16332 );
16333
16334 // Typing inside of buffer 1 causes that buffer to be unfolded.
16335 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16336 assert_eq!(
16337 multi_buffer
16338 .read(cx)
16339 .snapshot(cx)
16340 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16341 .collect::<String>(),
16342 "bbbb"
16343 );
16344 editor.change_selections(None, window, cx, |selections| {
16345 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16346 });
16347 editor.handle_input("B", window, cx);
16348 });
16349
16350 assert_eq!(
16351 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16352 "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16353 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16354 );
16355
16356 multi_buffer_editor.update(cx, |editor, cx| {
16357 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16358 });
16359 assert_eq!(
16360 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16361 "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16362 "After unfolding the all buffers, all original text should be displayed"
16363 );
16364}
16365
16366#[gpui::test]
16367async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16368 init_test(cx, |_| {});
16369
16370 let sample_text_1 = "1111\n2222\n3333".to_string();
16371 let sample_text_2 = "4444\n5555\n6666".to_string();
16372 let sample_text_3 = "7777\n8888\n9999".to_string();
16373
16374 let fs = FakeFs::new(cx.executor());
16375 fs.insert_tree(
16376 path!("/a"),
16377 json!({
16378 "first.rs": sample_text_1,
16379 "second.rs": sample_text_2,
16380 "third.rs": sample_text_3,
16381 }),
16382 )
16383 .await;
16384 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16385 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16386 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16387 let worktree = project.update(cx, |project, cx| {
16388 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16389 assert_eq!(worktrees.len(), 1);
16390 worktrees.pop().unwrap()
16391 });
16392 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16393
16394 let buffer_1 = project
16395 .update(cx, |project, cx| {
16396 project.open_buffer((worktree_id, "first.rs"), cx)
16397 })
16398 .await
16399 .unwrap();
16400 let buffer_2 = project
16401 .update(cx, |project, cx| {
16402 project.open_buffer((worktree_id, "second.rs"), cx)
16403 })
16404 .await
16405 .unwrap();
16406 let buffer_3 = project
16407 .update(cx, |project, cx| {
16408 project.open_buffer((worktree_id, "third.rs"), cx)
16409 })
16410 .await
16411 .unwrap();
16412
16413 let multi_buffer = cx.new(|cx| {
16414 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16415 multi_buffer.push_excerpts(
16416 buffer_1.clone(),
16417 [ExcerptRange {
16418 context: Point::new(0, 0)..Point::new(3, 0),
16419 primary: None,
16420 }],
16421 cx,
16422 );
16423 multi_buffer.push_excerpts(
16424 buffer_2.clone(),
16425 [ExcerptRange {
16426 context: Point::new(0, 0)..Point::new(3, 0),
16427 primary: None,
16428 }],
16429 cx,
16430 );
16431 multi_buffer.push_excerpts(
16432 buffer_3.clone(),
16433 [ExcerptRange {
16434 context: Point::new(0, 0)..Point::new(3, 0),
16435 primary: None,
16436 }],
16437 cx,
16438 );
16439 multi_buffer
16440 });
16441
16442 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16443 Editor::new(
16444 EditorMode::Full,
16445 multi_buffer,
16446 Some(project.clone()),
16447 true,
16448 window,
16449 cx,
16450 )
16451 });
16452
16453 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16454 assert_eq!(
16455 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16456 full_text,
16457 );
16458
16459 multi_buffer_editor.update(cx, |editor, cx| {
16460 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16461 });
16462 assert_eq!(
16463 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16464 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16465 "After folding the first buffer, its text should not be displayed"
16466 );
16467
16468 multi_buffer_editor.update(cx, |editor, cx| {
16469 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16470 });
16471
16472 assert_eq!(
16473 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16474 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16475 "After folding the second buffer, its text should not be displayed"
16476 );
16477
16478 multi_buffer_editor.update(cx, |editor, cx| {
16479 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16480 });
16481 assert_eq!(
16482 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16483 "\n\n\n\n\n",
16484 "After folding the third buffer, its text should not be displayed"
16485 );
16486
16487 multi_buffer_editor.update(cx, |editor, cx| {
16488 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16489 });
16490 assert_eq!(
16491 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16492 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16493 "After unfolding the second buffer, its text should be displayed"
16494 );
16495
16496 multi_buffer_editor.update(cx, |editor, cx| {
16497 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16498 });
16499 assert_eq!(
16500 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16501 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16502 "After unfolding the first buffer, its text should be displayed"
16503 );
16504
16505 multi_buffer_editor.update(cx, |editor, cx| {
16506 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16507 });
16508 assert_eq!(
16509 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16510 full_text,
16511 "After unfolding all buffers, all original text should be displayed"
16512 );
16513}
16514
16515#[gpui::test]
16516async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16517 init_test(cx, |_| {});
16518
16519 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16520
16521 let fs = FakeFs::new(cx.executor());
16522 fs.insert_tree(
16523 path!("/a"),
16524 json!({
16525 "main.rs": sample_text,
16526 }),
16527 )
16528 .await;
16529 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16530 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16531 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16532 let worktree = project.update(cx, |project, cx| {
16533 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16534 assert_eq!(worktrees.len(), 1);
16535 worktrees.pop().unwrap()
16536 });
16537 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16538
16539 let buffer_1 = project
16540 .update(cx, |project, cx| {
16541 project.open_buffer((worktree_id, "main.rs"), cx)
16542 })
16543 .await
16544 .unwrap();
16545
16546 let multi_buffer = cx.new(|cx| {
16547 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16548 multi_buffer.push_excerpts(
16549 buffer_1.clone(),
16550 [ExcerptRange {
16551 context: Point::new(0, 0)
16552 ..Point::new(
16553 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16554 0,
16555 ),
16556 primary: None,
16557 }],
16558 cx,
16559 );
16560 multi_buffer
16561 });
16562 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16563 Editor::new(
16564 EditorMode::Full,
16565 multi_buffer,
16566 Some(project.clone()),
16567 true,
16568 window,
16569 cx,
16570 )
16571 });
16572
16573 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16574 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16575 enum TestHighlight {}
16576 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16577 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16578 editor.highlight_text::<TestHighlight>(
16579 vec![highlight_range.clone()],
16580 HighlightStyle::color(Hsla::green()),
16581 cx,
16582 );
16583 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16584 });
16585
16586 let full_text = format!("\n\n\n{sample_text}\n");
16587 assert_eq!(
16588 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16589 full_text,
16590 );
16591}
16592
16593#[gpui::test]
16594async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16595 init_test(cx, |_| {});
16596 cx.update(|cx| {
16597 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16598 "keymaps/default-linux.json",
16599 cx,
16600 )
16601 .unwrap();
16602 cx.bind_keys(default_key_bindings);
16603 });
16604
16605 let (editor, cx) = cx.add_window_view(|window, cx| {
16606 let multi_buffer = MultiBuffer::build_multi(
16607 [
16608 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16609 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16610 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16611 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16612 ],
16613 cx,
16614 );
16615 let mut editor = Editor::new(
16616 EditorMode::Full,
16617 multi_buffer.clone(),
16618 None,
16619 true,
16620 window,
16621 cx,
16622 );
16623
16624 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16625 // fold all but the second buffer, so that we test navigating between two
16626 // adjacent folded buffers, as well as folded buffers at the start and
16627 // end the multibuffer
16628 editor.fold_buffer(buffer_ids[0], cx);
16629 editor.fold_buffer(buffer_ids[2], cx);
16630 editor.fold_buffer(buffer_ids[3], cx);
16631
16632 editor
16633 });
16634 cx.simulate_resize(size(px(1000.), px(1000.)));
16635
16636 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16637 cx.assert_excerpts_with_selections(indoc! {"
16638 [EXCERPT]
16639 ˇ[FOLDED]
16640 [EXCERPT]
16641 a1
16642 b1
16643 [EXCERPT]
16644 [FOLDED]
16645 [EXCERPT]
16646 [FOLDED]
16647 "
16648 });
16649 cx.simulate_keystroke("down");
16650 cx.assert_excerpts_with_selections(indoc! {"
16651 [EXCERPT]
16652 [FOLDED]
16653 [EXCERPT]
16654 ˇa1
16655 b1
16656 [EXCERPT]
16657 [FOLDED]
16658 [EXCERPT]
16659 [FOLDED]
16660 "
16661 });
16662 cx.simulate_keystroke("down");
16663 cx.assert_excerpts_with_selections(indoc! {"
16664 [EXCERPT]
16665 [FOLDED]
16666 [EXCERPT]
16667 a1
16668 ˇb1
16669 [EXCERPT]
16670 [FOLDED]
16671 [EXCERPT]
16672 [FOLDED]
16673 "
16674 });
16675 cx.simulate_keystroke("down");
16676 cx.assert_excerpts_with_selections(indoc! {"
16677 [EXCERPT]
16678 [FOLDED]
16679 [EXCERPT]
16680 a1
16681 b1
16682 ˇ[EXCERPT]
16683 [FOLDED]
16684 [EXCERPT]
16685 [FOLDED]
16686 "
16687 });
16688 cx.simulate_keystroke("down");
16689 cx.assert_excerpts_with_selections(indoc! {"
16690 [EXCERPT]
16691 [FOLDED]
16692 [EXCERPT]
16693 a1
16694 b1
16695 [EXCERPT]
16696 ˇ[FOLDED]
16697 [EXCERPT]
16698 [FOLDED]
16699 "
16700 });
16701 for _ in 0..5 {
16702 cx.simulate_keystroke("down");
16703 cx.assert_excerpts_with_selections(indoc! {"
16704 [EXCERPT]
16705 [FOLDED]
16706 [EXCERPT]
16707 a1
16708 b1
16709 [EXCERPT]
16710 [FOLDED]
16711 [EXCERPT]
16712 ˇ[FOLDED]
16713 "
16714 });
16715 }
16716
16717 cx.simulate_keystroke("up");
16718 cx.assert_excerpts_with_selections(indoc! {"
16719 [EXCERPT]
16720 [FOLDED]
16721 [EXCERPT]
16722 a1
16723 b1
16724 [EXCERPT]
16725 ˇ[FOLDED]
16726 [EXCERPT]
16727 [FOLDED]
16728 "
16729 });
16730 cx.simulate_keystroke("up");
16731 cx.assert_excerpts_with_selections(indoc! {"
16732 [EXCERPT]
16733 [FOLDED]
16734 [EXCERPT]
16735 a1
16736 b1
16737 ˇ[EXCERPT]
16738 [FOLDED]
16739 [EXCERPT]
16740 [FOLDED]
16741 "
16742 });
16743 cx.simulate_keystroke("up");
16744 cx.assert_excerpts_with_selections(indoc! {"
16745 [EXCERPT]
16746 [FOLDED]
16747 [EXCERPT]
16748 a1
16749 ˇb1
16750 [EXCERPT]
16751 [FOLDED]
16752 [EXCERPT]
16753 [FOLDED]
16754 "
16755 });
16756 cx.simulate_keystroke("up");
16757 cx.assert_excerpts_with_selections(indoc! {"
16758 [EXCERPT]
16759 [FOLDED]
16760 [EXCERPT]
16761 ˇa1
16762 b1
16763 [EXCERPT]
16764 [FOLDED]
16765 [EXCERPT]
16766 [FOLDED]
16767 "
16768 });
16769 for _ in 0..5 {
16770 cx.simulate_keystroke("up");
16771 cx.assert_excerpts_with_selections(indoc! {"
16772 [EXCERPT]
16773 ˇ[FOLDED]
16774 [EXCERPT]
16775 a1
16776 b1
16777 [EXCERPT]
16778 [FOLDED]
16779 [EXCERPT]
16780 [FOLDED]
16781 "
16782 });
16783 }
16784}
16785
16786#[gpui::test]
16787async fn test_inline_completion_text(cx: &mut TestAppContext) {
16788 init_test(cx, |_| {});
16789
16790 // Simple insertion
16791 assert_highlighted_edits(
16792 "Hello, world!",
16793 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16794 true,
16795 cx,
16796 |highlighted_edits, cx| {
16797 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16798 assert_eq!(highlighted_edits.highlights.len(), 1);
16799 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16800 assert_eq!(
16801 highlighted_edits.highlights[0].1.background_color,
16802 Some(cx.theme().status().created_background)
16803 );
16804 },
16805 )
16806 .await;
16807
16808 // Replacement
16809 assert_highlighted_edits(
16810 "This is a test.",
16811 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16812 false,
16813 cx,
16814 |highlighted_edits, cx| {
16815 assert_eq!(highlighted_edits.text, "That is a test.");
16816 assert_eq!(highlighted_edits.highlights.len(), 1);
16817 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16818 assert_eq!(
16819 highlighted_edits.highlights[0].1.background_color,
16820 Some(cx.theme().status().created_background)
16821 );
16822 },
16823 )
16824 .await;
16825
16826 // Multiple edits
16827 assert_highlighted_edits(
16828 "Hello, world!",
16829 vec![
16830 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16831 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16832 ],
16833 false,
16834 cx,
16835 |highlighted_edits, cx| {
16836 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16837 assert_eq!(highlighted_edits.highlights.len(), 2);
16838 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16839 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16840 assert_eq!(
16841 highlighted_edits.highlights[0].1.background_color,
16842 Some(cx.theme().status().created_background)
16843 );
16844 assert_eq!(
16845 highlighted_edits.highlights[1].1.background_color,
16846 Some(cx.theme().status().created_background)
16847 );
16848 },
16849 )
16850 .await;
16851
16852 // Multiple lines with edits
16853 assert_highlighted_edits(
16854 "First line\nSecond line\nThird line\nFourth line",
16855 vec![
16856 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16857 (
16858 Point::new(2, 0)..Point::new(2, 10),
16859 "New third line".to_string(),
16860 ),
16861 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16862 ],
16863 false,
16864 cx,
16865 |highlighted_edits, cx| {
16866 assert_eq!(
16867 highlighted_edits.text,
16868 "Second modified\nNew third line\nFourth updated line"
16869 );
16870 assert_eq!(highlighted_edits.highlights.len(), 3);
16871 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16872 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16873 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16874 for highlight in &highlighted_edits.highlights {
16875 assert_eq!(
16876 highlight.1.background_color,
16877 Some(cx.theme().status().created_background)
16878 );
16879 }
16880 },
16881 )
16882 .await;
16883}
16884
16885#[gpui::test]
16886async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16887 init_test(cx, |_| {});
16888
16889 // Deletion
16890 assert_highlighted_edits(
16891 "Hello, world!",
16892 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16893 true,
16894 cx,
16895 |highlighted_edits, cx| {
16896 assert_eq!(highlighted_edits.text, "Hello, world!");
16897 assert_eq!(highlighted_edits.highlights.len(), 1);
16898 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16899 assert_eq!(
16900 highlighted_edits.highlights[0].1.background_color,
16901 Some(cx.theme().status().deleted_background)
16902 );
16903 },
16904 )
16905 .await;
16906
16907 // Insertion
16908 assert_highlighted_edits(
16909 "Hello, world!",
16910 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16911 true,
16912 cx,
16913 |highlighted_edits, cx| {
16914 assert_eq!(highlighted_edits.highlights.len(), 1);
16915 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16916 assert_eq!(
16917 highlighted_edits.highlights[0].1.background_color,
16918 Some(cx.theme().status().created_background)
16919 );
16920 },
16921 )
16922 .await;
16923}
16924
16925async fn assert_highlighted_edits(
16926 text: &str,
16927 edits: Vec<(Range<Point>, String)>,
16928 include_deletions: bool,
16929 cx: &mut TestAppContext,
16930 assertion_fn: impl Fn(HighlightedText, &App),
16931) {
16932 let window = cx.add_window(|window, cx| {
16933 let buffer = MultiBuffer::build_simple(text, cx);
16934 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16935 });
16936 let cx = &mut VisualTestContext::from_window(*window, cx);
16937
16938 let (buffer, snapshot) = window
16939 .update(cx, |editor, _window, cx| {
16940 (
16941 editor.buffer().clone(),
16942 editor.buffer().read(cx).snapshot(cx),
16943 )
16944 })
16945 .unwrap();
16946
16947 let edits = edits
16948 .into_iter()
16949 .map(|(range, edit)| {
16950 (
16951 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16952 edit,
16953 )
16954 })
16955 .collect::<Vec<_>>();
16956
16957 let text_anchor_edits = edits
16958 .clone()
16959 .into_iter()
16960 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16961 .collect::<Vec<_>>();
16962
16963 let edit_preview = window
16964 .update(cx, |_, _window, cx| {
16965 buffer
16966 .read(cx)
16967 .as_singleton()
16968 .unwrap()
16969 .read(cx)
16970 .preview_edits(text_anchor_edits.into(), cx)
16971 })
16972 .unwrap()
16973 .await;
16974
16975 cx.update(|_window, cx| {
16976 let highlighted_edits = inline_completion_edit_text(
16977 &snapshot.as_singleton().unwrap().2,
16978 &edits,
16979 &edit_preview,
16980 include_deletions,
16981 cx,
16982 );
16983 assertion_fn(highlighted_edits, cx)
16984 });
16985}
16986
16987#[gpui::test]
16988async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16989 init_test(cx, |_| {});
16990 let capabilities = lsp::ServerCapabilities {
16991 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16992 prepare_provider: Some(true),
16993 work_done_progress_options: Default::default(),
16994 })),
16995 ..Default::default()
16996 };
16997 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16998
16999 cx.set_state(indoc! {"
17000 struct Fˇoo {}
17001 "});
17002
17003 cx.update_editor(|editor, _, cx| {
17004 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17005 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17006 editor.highlight_background::<DocumentHighlightRead>(
17007 &[highlight_range],
17008 |c| c.editor_document_highlight_read_background,
17009 cx,
17010 );
17011 });
17012
17013 let mut prepare_rename_handler =
17014 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17015 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17016 start: lsp::Position {
17017 line: 0,
17018 character: 7,
17019 },
17020 end: lsp::Position {
17021 line: 0,
17022 character: 10,
17023 },
17024 })))
17025 });
17026 let prepare_rename_task = cx
17027 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17028 .expect("Prepare rename was not started");
17029 prepare_rename_handler.next().await.unwrap();
17030 prepare_rename_task.await.expect("Prepare rename failed");
17031
17032 let mut rename_handler =
17033 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17034 let edit = lsp::TextEdit {
17035 range: lsp::Range {
17036 start: lsp::Position {
17037 line: 0,
17038 character: 7,
17039 },
17040 end: lsp::Position {
17041 line: 0,
17042 character: 10,
17043 },
17044 },
17045 new_text: "FooRenamed".to_string(),
17046 };
17047 Ok(Some(lsp::WorkspaceEdit::new(
17048 // Specify the same edit twice
17049 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17050 )))
17051 });
17052 let rename_task = cx
17053 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17054 .expect("Confirm rename was not started");
17055 rename_handler.next().await.unwrap();
17056 rename_task.await.expect("Confirm rename failed");
17057 cx.run_until_parked();
17058
17059 // Despite two edits, only one is actually applied as those are identical
17060 cx.assert_editor_state(indoc! {"
17061 struct FooRenamedˇ {}
17062 "});
17063}
17064
17065#[gpui::test]
17066async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17067 init_test(cx, |_| {});
17068 // These capabilities indicate that the server does not support prepare rename.
17069 let capabilities = lsp::ServerCapabilities {
17070 rename_provider: Some(lsp::OneOf::Left(true)),
17071 ..Default::default()
17072 };
17073 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17074
17075 cx.set_state(indoc! {"
17076 struct Fˇoo {}
17077 "});
17078
17079 cx.update_editor(|editor, _window, cx| {
17080 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17081 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17082 editor.highlight_background::<DocumentHighlightRead>(
17083 &[highlight_range],
17084 |c| c.editor_document_highlight_read_background,
17085 cx,
17086 );
17087 });
17088
17089 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17090 .expect("Prepare rename was not started")
17091 .await
17092 .expect("Prepare rename failed");
17093
17094 let mut rename_handler =
17095 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17096 let edit = lsp::TextEdit {
17097 range: lsp::Range {
17098 start: lsp::Position {
17099 line: 0,
17100 character: 7,
17101 },
17102 end: lsp::Position {
17103 line: 0,
17104 character: 10,
17105 },
17106 },
17107 new_text: "FooRenamed".to_string(),
17108 };
17109 Ok(Some(lsp::WorkspaceEdit::new(
17110 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17111 )))
17112 });
17113 let rename_task = cx
17114 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17115 .expect("Confirm rename was not started");
17116 rename_handler.next().await.unwrap();
17117 rename_task.await.expect("Confirm rename failed");
17118 cx.run_until_parked();
17119
17120 // Correct range is renamed, as `surrounding_word` is used to find it.
17121 cx.assert_editor_state(indoc! {"
17122 struct FooRenamedˇ {}
17123 "});
17124}
17125
17126#[gpui::test]
17127async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17128 init_test(cx, |_| {});
17129 let mut cx = EditorTestContext::new(cx).await;
17130
17131 let language = Arc::new(
17132 Language::new(
17133 LanguageConfig::default(),
17134 Some(tree_sitter_html::LANGUAGE.into()),
17135 )
17136 .with_brackets_query(
17137 r#"
17138 ("<" @open "/>" @close)
17139 ("</" @open ">" @close)
17140 ("<" @open ">" @close)
17141 ("\"" @open "\"" @close)
17142 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17143 "#,
17144 )
17145 .unwrap(),
17146 );
17147 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17148
17149 cx.set_state(indoc! {"
17150 <span>ˇ</span>
17151 "});
17152 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17153 cx.assert_editor_state(indoc! {"
17154 <span>
17155 ˇ
17156 </span>
17157 "});
17158
17159 cx.set_state(indoc! {"
17160 <span><span></span>ˇ</span>
17161 "});
17162 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17163 cx.assert_editor_state(indoc! {"
17164 <span><span></span>
17165 ˇ</span>
17166 "});
17167
17168 cx.set_state(indoc! {"
17169 <span>ˇ
17170 </span>
17171 "});
17172 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17173 cx.assert_editor_state(indoc! {"
17174 <span>
17175 ˇ
17176 </span>
17177 "});
17178}
17179
17180mod autoclose_tags {
17181 use super::*;
17182 use language::language_settings::JsxTagAutoCloseSettings;
17183 use languages::language;
17184
17185 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17186 init_test(cx, |settings| {
17187 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17188 });
17189
17190 let mut cx = EditorTestContext::new(cx).await;
17191 cx.update_buffer(|buffer, cx| {
17192 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17193
17194 buffer.set_language(Some(language), cx)
17195 });
17196
17197 cx
17198 }
17199
17200 macro_rules! check {
17201 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17202 #[gpui::test]
17203 async fn $name(cx: &mut TestAppContext) {
17204 let mut cx = test_setup(cx).await;
17205 cx.set_state($initial);
17206 cx.run_until_parked();
17207
17208 cx.update_editor(|editor, window, cx| {
17209 editor.handle_input($input, window, cx);
17210 });
17211 cx.run_until_parked();
17212 cx.assert_editor_state($expected);
17213 }
17214 };
17215 }
17216
17217 check!(
17218 test_basic,
17219 "<divˇ" + ">" => "<div>ˇ</div>"
17220 );
17221
17222 check!(
17223 test_basic_nested,
17224 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17225 );
17226
17227 check!(
17228 test_basic_ignore_already_closed,
17229 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17230 );
17231
17232 check!(
17233 test_doesnt_autoclose_closing_tag,
17234 "</divˇ" + ">" => "</div>ˇ"
17235 );
17236
17237 check!(
17238 test_jsx_attr,
17239 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17240 );
17241
17242 check!(
17243 test_ignores_closing_tags_in_expr_block,
17244 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17245 );
17246
17247 check!(
17248 test_doesnt_autoclose_on_gt_in_expr,
17249 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17250 );
17251
17252 check!(
17253 test_ignores_closing_tags_with_different_tag_names,
17254 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17255 );
17256
17257 check!(
17258 test_autocloses_in_jsx_expression,
17259 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17260 );
17261
17262 check!(
17263 test_doesnt_autoclose_already_closed_in_jsx_expression,
17264 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17265 );
17266
17267 check!(
17268 test_autocloses_fragment,
17269 "<ˇ" + ">" => "<>ˇ</>"
17270 );
17271
17272 check!(
17273 test_does_not_include_type_argument_in_autoclose_tag_name,
17274 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17275 );
17276
17277 check!(
17278 test_does_not_autoclose_doctype,
17279 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17280 );
17281
17282 check!(
17283 test_does_not_autoclose_comment,
17284 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17285 );
17286
17287 check!(
17288 test_multi_cursor_autoclose_same_tag,
17289 r#"
17290 <divˇ
17291 <divˇ
17292 "#
17293 + ">" =>
17294 r#"
17295 <div>ˇ</div>
17296 <div>ˇ</div>
17297 "#
17298 );
17299
17300 check!(
17301 test_multi_cursor_autoclose_different_tags,
17302 r#"
17303 <divˇ
17304 <spanˇ
17305 "#
17306 + ">" =>
17307 r#"
17308 <div>ˇ</div>
17309 <span>ˇ</span>
17310 "#
17311 );
17312
17313 check!(
17314 test_multi_cursor_autoclose_some_dont_autoclose_others,
17315 r#"
17316 <divˇ
17317 <div /ˇ
17318 <spanˇ</span>
17319 <!DOCTYPE htmlˇ
17320 </headˇ
17321 <Component<T>ˇ
17322 ˇ
17323 "#
17324 + ">" =>
17325 r#"
17326 <div>ˇ</div>
17327 <div />ˇ
17328 <span>ˇ</span>
17329 <!DOCTYPE html>ˇ
17330 </head>ˇ
17331 <Component<T>>ˇ</Component>
17332 >ˇ
17333 "#
17334 );
17335
17336 check!(
17337 test_doesnt_mess_up_trailing_text,
17338 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17339 );
17340
17341 #[gpui::test]
17342 async fn test_multibuffer(cx: &mut TestAppContext) {
17343 init_test(cx, |settings| {
17344 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17345 });
17346
17347 let buffer_a = cx.new(|cx| {
17348 let mut buf = language::Buffer::local("<div", cx);
17349 buf.set_language(
17350 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17351 cx,
17352 );
17353 buf
17354 });
17355 let buffer_b = cx.new(|cx| {
17356 let mut buf = language::Buffer::local("<pre", cx);
17357 buf.set_language(
17358 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17359 cx,
17360 );
17361 buf
17362 });
17363 let buffer_c = cx.new(|cx| {
17364 let buf = language::Buffer::local("<span", cx);
17365 buf
17366 });
17367 let buffer = cx.new(|cx| {
17368 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17369 buf.push_excerpts(
17370 buffer_a,
17371 [ExcerptRange {
17372 context: text::Anchor::MIN..text::Anchor::MAX,
17373 primary: None,
17374 }],
17375 cx,
17376 );
17377 buf.push_excerpts(
17378 buffer_b,
17379 [ExcerptRange {
17380 context: text::Anchor::MIN..text::Anchor::MAX,
17381 primary: None,
17382 }],
17383 cx,
17384 );
17385 buf.push_excerpts(
17386 buffer_c,
17387 [ExcerptRange {
17388 context: text::Anchor::MIN..text::Anchor::MAX,
17389 primary: None,
17390 }],
17391 cx,
17392 );
17393 buf
17394 });
17395 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17396
17397 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17398
17399 cx.update_editor(|editor, window, cx| {
17400 editor.change_selections(None, window, cx, |selections| {
17401 selections.select(vec![
17402 Selection::from_offset(4),
17403 Selection::from_offset(9),
17404 Selection::from_offset(15),
17405 ])
17406 })
17407 });
17408 cx.run_until_parked();
17409
17410 cx.update_editor(|editor, window, cx| {
17411 editor.handle_input(">", window, cx);
17412 });
17413 cx.run_until_parked();
17414
17415 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17416 }
17417}
17418
17419fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17420 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17421 point..point
17422}
17423
17424fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17425 let (text, ranges) = marked_text_ranges(marked_text, true);
17426 assert_eq!(editor.text(cx), text);
17427 assert_eq!(
17428 editor.selections.ranges(cx),
17429 ranges,
17430 "Assert selections are {}",
17431 marked_text
17432 );
17433}
17434
17435pub fn handle_signature_help_request(
17436 cx: &mut EditorLspTestContext,
17437 mocked_response: lsp::SignatureHelp,
17438) -> impl Future<Output = ()> {
17439 let mut request =
17440 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17441 let mocked_response = mocked_response.clone();
17442 async move { Ok(Some(mocked_response)) }
17443 });
17444
17445 async move {
17446 request.next().await;
17447 }
17448}
17449
17450/// Handle completion request passing a marked string specifying where the completion
17451/// should be triggered from using '|' character, what range should be replaced, and what completions
17452/// should be returned using '<' and '>' to delimit the range
17453pub fn handle_completion_request(
17454 cx: &mut EditorLspTestContext,
17455 marked_string: &str,
17456 completions: Vec<&'static str>,
17457 counter: Arc<AtomicUsize>,
17458) -> impl Future<Output = ()> {
17459 let complete_from_marker: TextRangeMarker = '|'.into();
17460 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17461 let (_, mut marked_ranges) = marked_text_ranges_by(
17462 marked_string,
17463 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17464 );
17465
17466 let complete_from_position =
17467 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17468 let replace_range =
17469 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17470
17471 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17472 let completions = completions.clone();
17473 counter.fetch_add(1, atomic::Ordering::Release);
17474 async move {
17475 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17476 assert_eq!(
17477 params.text_document_position.position,
17478 complete_from_position
17479 );
17480 Ok(Some(lsp::CompletionResponse::Array(
17481 completions
17482 .iter()
17483 .map(|completion_text| lsp::CompletionItem {
17484 label: completion_text.to_string(),
17485 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17486 range: replace_range,
17487 new_text: completion_text.to_string(),
17488 })),
17489 ..Default::default()
17490 })
17491 .collect(),
17492 )))
17493 }
17494 });
17495
17496 async move {
17497 request.next().await;
17498 }
17499}
17500
17501fn handle_resolve_completion_request(
17502 cx: &mut EditorLspTestContext,
17503 edits: Option<Vec<(&'static str, &'static str)>>,
17504) -> impl Future<Output = ()> {
17505 let edits = edits.map(|edits| {
17506 edits
17507 .iter()
17508 .map(|(marked_string, new_text)| {
17509 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17510 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17511 lsp::TextEdit::new(replace_range, new_text.to_string())
17512 })
17513 .collect::<Vec<_>>()
17514 });
17515
17516 let mut request =
17517 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17518 let edits = edits.clone();
17519 async move {
17520 Ok(lsp::CompletionItem {
17521 additional_text_edits: edits,
17522 ..Default::default()
17523 })
17524 }
17525 });
17526
17527 async move {
17528 request.next().await;
17529 }
17530}
17531
17532pub(crate) fn update_test_language_settings(
17533 cx: &mut TestAppContext,
17534 f: impl Fn(&mut AllLanguageSettingsContent),
17535) {
17536 cx.update(|cx| {
17537 SettingsStore::update_global(cx, |store, cx| {
17538 store.update_user_settings::<AllLanguageSettings>(cx, f);
17539 });
17540 });
17541}
17542
17543pub(crate) fn update_test_project_settings(
17544 cx: &mut TestAppContext,
17545 f: impl Fn(&mut ProjectSettings),
17546) {
17547 cx.update(|cx| {
17548 SettingsStore::update_global(cx, |store, cx| {
17549 store.update_user_settings::<ProjectSettings>(cx, f);
17550 });
17551 });
17552}
17553
17554pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17555 cx.update(|cx| {
17556 assets::Assets.load_test_fonts(cx);
17557 let store = SettingsStore::test(cx);
17558 cx.set_global(store);
17559 theme::init(theme::LoadThemes::JustBase, cx);
17560 release_channel::init(SemanticVersion::default(), cx);
17561 client::init_settings(cx);
17562 language::init(cx);
17563 Project::init_settings(cx);
17564 workspace::init_settings(cx);
17565 crate::init(cx);
17566 });
17567
17568 update_test_language_settings(cx, f);
17569}
17570
17571#[track_caller]
17572fn assert_hunk_revert(
17573 not_reverted_text_with_selections: &str,
17574 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17575 expected_reverted_text_with_selections: &str,
17576 base_text: &str,
17577 cx: &mut EditorLspTestContext,
17578) {
17579 cx.set_state(not_reverted_text_with_selections);
17580 cx.set_head_text(base_text);
17581 cx.executor().run_until_parked();
17582
17583 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17584 let snapshot = editor.snapshot(window, cx);
17585 let reverted_hunk_statuses = snapshot
17586 .buffer_snapshot
17587 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17588 .map(|hunk| hunk.status().kind)
17589 .collect::<Vec<_>>();
17590
17591 editor.git_restore(&Default::default(), window, cx);
17592 reverted_hunk_statuses
17593 });
17594 cx.executor().run_until_parked();
17595 cx.assert_editor_state(expected_reverted_text_with_selections);
17596 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17597}