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_clipboard(cx: &mut TestAppContext) {
4743 init_test(cx, |_| {});
4744
4745 let mut cx = EditorTestContext::new(cx).await;
4746
4747 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4748 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4749 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4750
4751 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4752 cx.set_state("two ˇfour ˇsix ˇ");
4753 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4754 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4755
4756 // Paste again but with only two cursors. Since the number of cursors doesn't
4757 // match the number of slices in the clipboard, the entire clipboard text
4758 // is pasted at each cursor.
4759 cx.set_state("ˇtwo one✅ four three six five ˇ");
4760 cx.update_editor(|e, window, cx| {
4761 e.handle_input("( ", window, cx);
4762 e.paste(&Paste, window, cx);
4763 e.handle_input(") ", window, cx);
4764 });
4765 cx.assert_editor_state(
4766 &([
4767 "( one✅ ",
4768 "three ",
4769 "five ) ˇtwo one✅ four three six five ( one✅ ",
4770 "three ",
4771 "five ) ˇ",
4772 ]
4773 .join("\n")),
4774 );
4775
4776 // Cut with three selections, one of which is full-line.
4777 cx.set_state(indoc! {"
4778 1«2ˇ»3
4779 4ˇ567
4780 «8ˇ»9"});
4781 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4782 cx.assert_editor_state(indoc! {"
4783 1ˇ3
4784 ˇ9"});
4785
4786 // Paste with three selections, noticing how the copied selection that was full-line
4787 // gets inserted before the second cursor.
4788 cx.set_state(indoc! {"
4789 1ˇ3
4790 9ˇ
4791 «oˇ»ne"});
4792 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4793 cx.assert_editor_state(indoc! {"
4794 12ˇ3
4795 4567
4796 9ˇ
4797 8ˇne"});
4798
4799 // Copy with a single cursor only, which writes the whole line into the clipboard.
4800 cx.set_state(indoc! {"
4801 The quick brown
4802 fox juˇmps over
4803 the lazy dog"});
4804 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4805 assert_eq!(
4806 cx.read_from_clipboard()
4807 .and_then(|item| item.text().as_deref().map(str::to_string)),
4808 Some("fox jumps over\n".to_string())
4809 );
4810
4811 // Paste with three selections, noticing how the copied full-line selection is inserted
4812 // before the empty selections but replaces the selection that is non-empty.
4813 cx.set_state(indoc! {"
4814 Tˇhe quick brown
4815 «foˇ»x jumps over
4816 tˇhe lazy dog"});
4817 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4818 cx.assert_editor_state(indoc! {"
4819 fox jumps over
4820 Tˇhe quick brown
4821 fox jumps over
4822 ˇx jumps over
4823 fox jumps over
4824 tˇhe lazy dog"});
4825}
4826
4827#[gpui::test]
4828async fn test_paste_multiline(cx: &mut TestAppContext) {
4829 init_test(cx, |_| {});
4830
4831 let mut cx = EditorTestContext::new(cx).await;
4832 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4833
4834 // Cut an indented block, without the leading whitespace.
4835 cx.set_state(indoc! {"
4836 const a: B = (
4837 c(),
4838 «d(
4839 e,
4840 f
4841 )ˇ»
4842 );
4843 "});
4844 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4845 cx.assert_editor_state(indoc! {"
4846 const a: B = (
4847 c(),
4848 ˇ
4849 );
4850 "});
4851
4852 // Paste it at the same position.
4853 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4854 cx.assert_editor_state(indoc! {"
4855 const a: B = (
4856 c(),
4857 d(
4858 e,
4859 f
4860 )ˇ
4861 );
4862 "});
4863
4864 // Paste it at a line with a lower indent level.
4865 cx.set_state(indoc! {"
4866 ˇ
4867 const a: B = (
4868 c(),
4869 );
4870 "});
4871 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4872 cx.assert_editor_state(indoc! {"
4873 d(
4874 e,
4875 f
4876 )ˇ
4877 const a: B = (
4878 c(),
4879 );
4880 "});
4881
4882 // Cut an indented block, with the leading whitespace.
4883 cx.set_state(indoc! {"
4884 const a: B = (
4885 c(),
4886 « d(
4887 e,
4888 f
4889 )
4890 ˇ»);
4891 "});
4892 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4893 cx.assert_editor_state(indoc! {"
4894 const a: B = (
4895 c(),
4896 ˇ);
4897 "});
4898
4899 // Paste it at the same position.
4900 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4901 cx.assert_editor_state(indoc! {"
4902 const a: B = (
4903 c(),
4904 d(
4905 e,
4906 f
4907 )
4908 ˇ);
4909 "});
4910
4911 // Paste it at a line with a higher indent level.
4912 cx.set_state(indoc! {"
4913 const a: B = (
4914 c(),
4915 d(
4916 e,
4917 fˇ
4918 )
4919 );
4920 "});
4921 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4922 cx.assert_editor_state(indoc! {"
4923 const a: B = (
4924 c(),
4925 d(
4926 e,
4927 f d(
4928 e,
4929 f
4930 )
4931 ˇ
4932 )
4933 );
4934 "});
4935
4936 // Copy an indented block, starting mid-line
4937 cx.set_state(indoc! {"
4938 const a: B = (
4939 c(),
4940 somethin«g(
4941 e,
4942 f
4943 )ˇ»
4944 );
4945 "});
4946 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4947
4948 // Paste it on a line with a lower indent level
4949 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4950 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4951 cx.assert_editor_state(indoc! {"
4952 const a: B = (
4953 c(),
4954 something(
4955 e,
4956 f
4957 )
4958 );
4959 g(
4960 e,
4961 f
4962 )ˇ"});
4963}
4964
4965#[gpui::test]
4966async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4967 init_test(cx, |_| {});
4968
4969 cx.write_to_clipboard(ClipboardItem::new_string(
4970 " d(\n e\n );\n".into(),
4971 ));
4972
4973 let mut cx = EditorTestContext::new(cx).await;
4974 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4975
4976 cx.set_state(indoc! {"
4977 fn a() {
4978 b();
4979 if c() {
4980 ˇ
4981 }
4982 }
4983 "});
4984
4985 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4986 cx.assert_editor_state(indoc! {"
4987 fn a() {
4988 b();
4989 if c() {
4990 d(
4991 e
4992 );
4993 ˇ
4994 }
4995 }
4996 "});
4997
4998 cx.set_state(indoc! {"
4999 fn a() {
5000 b();
5001 ˇ
5002 }
5003 "});
5004
5005 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5006 cx.assert_editor_state(indoc! {"
5007 fn a() {
5008 b();
5009 d(
5010 e
5011 );
5012 ˇ
5013 }
5014 "});
5015}
5016
5017#[gpui::test]
5018fn test_select_all(cx: &mut TestAppContext) {
5019 init_test(cx, |_| {});
5020
5021 let editor = cx.add_window(|window, cx| {
5022 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5023 build_editor(buffer, window, cx)
5024 });
5025 _ = editor.update(cx, |editor, window, cx| {
5026 editor.select_all(&SelectAll, window, cx);
5027 assert_eq!(
5028 editor.selections.display_ranges(cx),
5029 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5030 );
5031 });
5032}
5033
5034#[gpui::test]
5035fn test_select_line(cx: &mut TestAppContext) {
5036 init_test(cx, |_| {});
5037
5038 let editor = cx.add_window(|window, cx| {
5039 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5040 build_editor(buffer, window, cx)
5041 });
5042 _ = editor.update(cx, |editor, window, cx| {
5043 editor.change_selections(None, window, cx, |s| {
5044 s.select_display_ranges([
5045 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5046 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5047 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5048 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5049 ])
5050 });
5051 editor.select_line(&SelectLine, window, cx);
5052 assert_eq!(
5053 editor.selections.display_ranges(cx),
5054 vec![
5055 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5056 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5057 ]
5058 );
5059 });
5060
5061 _ = editor.update(cx, |editor, window, cx| {
5062 editor.select_line(&SelectLine, window, cx);
5063 assert_eq!(
5064 editor.selections.display_ranges(cx),
5065 vec![
5066 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5067 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5068 ]
5069 );
5070 });
5071
5072 _ = editor.update(cx, |editor, window, cx| {
5073 editor.select_line(&SelectLine, window, cx);
5074 assert_eq!(
5075 editor.selections.display_ranges(cx),
5076 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5077 );
5078 });
5079}
5080
5081#[gpui::test]
5082async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5083 init_test(cx, |_| {});
5084 let mut cx = EditorTestContext::new(cx).await;
5085
5086 #[track_caller]
5087 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5088 cx.set_state(initial_state);
5089 cx.update_editor(|e, window, cx| {
5090 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5091 });
5092 cx.assert_editor_state(expected_state);
5093 }
5094
5095 // Selection starts and ends at the middle of lines, left-to-right
5096 test(
5097 &mut cx,
5098 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5099 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5100 );
5101 // Same thing, right-to-left
5102 test(
5103 &mut cx,
5104 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5105 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5106 );
5107
5108 // Whole buffer, left-to-right, last line *doesn't* end with newline
5109 test(
5110 &mut cx,
5111 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5112 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5113 );
5114 // Same thing, right-to-left
5115 test(
5116 &mut cx,
5117 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5118 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5119 );
5120
5121 // Whole buffer, left-to-right, last line ends with newline
5122 test(
5123 &mut cx,
5124 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5125 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5126 );
5127 // Same thing, right-to-left
5128 test(
5129 &mut cx,
5130 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5131 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5132 );
5133
5134 // Starts at the end of a line, ends at the start of another
5135 test(
5136 &mut cx,
5137 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5138 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5139 );
5140}
5141
5142#[gpui::test]
5143async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5144 init_test(cx, |_| {});
5145
5146 let editor = cx.add_window(|window, cx| {
5147 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5148 build_editor(buffer, window, cx)
5149 });
5150
5151 // setup
5152 _ = editor.update(cx, |editor, window, cx| {
5153 editor.fold_creases(
5154 vec![
5155 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5156 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5157 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5158 ],
5159 true,
5160 window,
5161 cx,
5162 );
5163 assert_eq!(
5164 editor.display_text(cx),
5165 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5166 );
5167 });
5168
5169 _ = editor.update(cx, |editor, window, cx| {
5170 editor.change_selections(None, window, cx, |s| {
5171 s.select_display_ranges([
5172 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5173 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5174 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5175 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5176 ])
5177 });
5178 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5179 assert_eq!(
5180 editor.display_text(cx),
5181 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5182 );
5183 });
5184 EditorTestContext::for_editor(editor, cx)
5185 .await
5186 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5187
5188 _ = editor.update(cx, |editor, window, cx| {
5189 editor.change_selections(None, window, cx, |s| {
5190 s.select_display_ranges([
5191 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5192 ])
5193 });
5194 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5195 assert_eq!(
5196 editor.display_text(cx),
5197 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5198 );
5199 assert_eq!(
5200 editor.selections.display_ranges(cx),
5201 [
5202 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5203 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5204 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5205 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5206 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5207 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5208 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5209 ]
5210 );
5211 });
5212 EditorTestContext::for_editor(editor, cx)
5213 .await
5214 .assert_editor_state(
5215 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5216 );
5217}
5218
5219#[gpui::test]
5220async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5221 init_test(cx, |_| {});
5222
5223 let mut cx = EditorTestContext::new(cx).await;
5224
5225 cx.set_state(indoc!(
5226 r#"abc
5227 defˇghi
5228
5229 jk
5230 nlmo
5231 "#
5232 ));
5233
5234 cx.update_editor(|editor, window, cx| {
5235 editor.add_selection_above(&Default::default(), window, cx);
5236 });
5237
5238 cx.assert_editor_state(indoc!(
5239 r#"abcˇ
5240 defˇghi
5241
5242 jk
5243 nlmo
5244 "#
5245 ));
5246
5247 cx.update_editor(|editor, window, cx| {
5248 editor.add_selection_above(&Default::default(), window, cx);
5249 });
5250
5251 cx.assert_editor_state(indoc!(
5252 r#"abcˇ
5253 defˇghi
5254
5255 jk
5256 nlmo
5257 "#
5258 ));
5259
5260 cx.update_editor(|editor, window, cx| {
5261 editor.add_selection_below(&Default::default(), window, cx);
5262 });
5263
5264 cx.assert_editor_state(indoc!(
5265 r#"abc
5266 defˇghi
5267
5268 jk
5269 nlmo
5270 "#
5271 ));
5272
5273 cx.update_editor(|editor, window, cx| {
5274 editor.undo_selection(&Default::default(), window, cx);
5275 });
5276
5277 cx.assert_editor_state(indoc!(
5278 r#"abcˇ
5279 defˇghi
5280
5281 jk
5282 nlmo
5283 "#
5284 ));
5285
5286 cx.update_editor(|editor, window, cx| {
5287 editor.redo_selection(&Default::default(), window, cx);
5288 });
5289
5290 cx.assert_editor_state(indoc!(
5291 r#"abc
5292 defˇghi
5293
5294 jk
5295 nlmo
5296 "#
5297 ));
5298
5299 cx.update_editor(|editor, window, cx| {
5300 editor.add_selection_below(&Default::default(), window, cx);
5301 });
5302
5303 cx.assert_editor_state(indoc!(
5304 r#"abc
5305 defˇghi
5306
5307 jk
5308 nlmˇo
5309 "#
5310 ));
5311
5312 cx.update_editor(|editor, window, cx| {
5313 editor.add_selection_below(&Default::default(), window, cx);
5314 });
5315
5316 cx.assert_editor_state(indoc!(
5317 r#"abc
5318 defˇghi
5319
5320 jk
5321 nlmˇo
5322 "#
5323 ));
5324
5325 // change selections
5326 cx.set_state(indoc!(
5327 r#"abc
5328 def«ˇg»hi
5329
5330 jk
5331 nlmo
5332 "#
5333 ));
5334
5335 cx.update_editor(|editor, window, cx| {
5336 editor.add_selection_below(&Default::default(), window, cx);
5337 });
5338
5339 cx.assert_editor_state(indoc!(
5340 r#"abc
5341 def«ˇg»hi
5342
5343 jk
5344 nlm«ˇo»
5345 "#
5346 ));
5347
5348 cx.update_editor(|editor, window, cx| {
5349 editor.add_selection_below(&Default::default(), window, cx);
5350 });
5351
5352 cx.assert_editor_state(indoc!(
5353 r#"abc
5354 def«ˇg»hi
5355
5356 jk
5357 nlm«ˇo»
5358 "#
5359 ));
5360
5361 cx.update_editor(|editor, window, cx| {
5362 editor.add_selection_above(&Default::default(), window, cx);
5363 });
5364
5365 cx.assert_editor_state(indoc!(
5366 r#"abc
5367 def«ˇg»hi
5368
5369 jk
5370 nlmo
5371 "#
5372 ));
5373
5374 cx.update_editor(|editor, window, cx| {
5375 editor.add_selection_above(&Default::default(), window, cx);
5376 });
5377
5378 cx.assert_editor_state(indoc!(
5379 r#"abc
5380 def«ˇg»hi
5381
5382 jk
5383 nlmo
5384 "#
5385 ));
5386
5387 // Change selections again
5388 cx.set_state(indoc!(
5389 r#"a«bc
5390 defgˇ»hi
5391
5392 jk
5393 nlmo
5394 "#
5395 ));
5396
5397 cx.update_editor(|editor, window, cx| {
5398 editor.add_selection_below(&Default::default(), window, cx);
5399 });
5400
5401 cx.assert_editor_state(indoc!(
5402 r#"a«bcˇ»
5403 d«efgˇ»hi
5404
5405 j«kˇ»
5406 nlmo
5407 "#
5408 ));
5409
5410 cx.update_editor(|editor, window, cx| {
5411 editor.add_selection_below(&Default::default(), window, cx);
5412 });
5413 cx.assert_editor_state(indoc!(
5414 r#"a«bcˇ»
5415 d«efgˇ»hi
5416
5417 j«kˇ»
5418 n«lmoˇ»
5419 "#
5420 ));
5421 cx.update_editor(|editor, window, cx| {
5422 editor.add_selection_above(&Default::default(), window, cx);
5423 });
5424
5425 cx.assert_editor_state(indoc!(
5426 r#"a«bcˇ»
5427 d«efgˇ»hi
5428
5429 j«kˇ»
5430 nlmo
5431 "#
5432 ));
5433
5434 // Change selections again
5435 cx.set_state(indoc!(
5436 r#"abc
5437 d«ˇefghi
5438
5439 jk
5440 nlm»o
5441 "#
5442 ));
5443
5444 cx.update_editor(|editor, window, cx| {
5445 editor.add_selection_above(&Default::default(), window, cx);
5446 });
5447
5448 cx.assert_editor_state(indoc!(
5449 r#"a«ˇbc»
5450 d«ˇef»ghi
5451
5452 j«ˇk»
5453 n«ˇlm»o
5454 "#
5455 ));
5456
5457 cx.update_editor(|editor, window, cx| {
5458 editor.add_selection_below(&Default::default(), window, cx);
5459 });
5460
5461 cx.assert_editor_state(indoc!(
5462 r#"abc
5463 d«ˇef»ghi
5464
5465 j«ˇk»
5466 n«ˇlm»o
5467 "#
5468 ));
5469}
5470
5471#[gpui::test]
5472async fn test_select_next(cx: &mut TestAppContext) {
5473 init_test(cx, |_| {});
5474
5475 let mut cx = EditorTestContext::new(cx).await;
5476 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5477
5478 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5479 .unwrap();
5480 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5481
5482 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5483 .unwrap();
5484 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5485
5486 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5487 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5488
5489 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5490 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5491
5492 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5493 .unwrap();
5494 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5495
5496 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5497 .unwrap();
5498 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5499}
5500
5501#[gpui::test]
5502async fn test_select_all_matches(cx: &mut TestAppContext) {
5503 init_test(cx, |_| {});
5504
5505 let mut cx = EditorTestContext::new(cx).await;
5506
5507 // Test caret-only selections
5508 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5509 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5510 .unwrap();
5511 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5512
5513 // Test left-to-right selections
5514 cx.set_state("abc\n«abcˇ»\nabc");
5515 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5516 .unwrap();
5517 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5518
5519 // Test right-to-left selections
5520 cx.set_state("abc\n«ˇabc»\nabc");
5521 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5522 .unwrap();
5523 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5524
5525 // Test selecting whitespace with caret selection
5526 cx.set_state("abc\nˇ abc\nabc");
5527 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5528 .unwrap();
5529 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5530
5531 // Test selecting whitespace with left-to-right selection
5532 cx.set_state("abc\n«ˇ »abc\nabc");
5533 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5534 .unwrap();
5535 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5536
5537 // Test no matches with right-to-left selection
5538 cx.set_state("abc\n« ˇ»abc\nabc");
5539 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5540 .unwrap();
5541 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5542}
5543
5544#[gpui::test]
5545async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5546 init_test(cx, |_| {});
5547
5548 let mut cx = EditorTestContext::new(cx).await;
5549 cx.set_state(
5550 r#"let foo = 2;
5551lˇet foo = 2;
5552let fooˇ = 2;
5553let foo = 2;
5554let foo = ˇ2;"#,
5555 );
5556
5557 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5558 .unwrap();
5559 cx.assert_editor_state(
5560 r#"let foo = 2;
5561«letˇ» foo = 2;
5562let «fooˇ» = 2;
5563let foo = 2;
5564let foo = «2ˇ»;"#,
5565 );
5566
5567 // noop for multiple selections with different contents
5568 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5569 .unwrap();
5570 cx.assert_editor_state(
5571 r#"let foo = 2;
5572«letˇ» foo = 2;
5573let «fooˇ» = 2;
5574let foo = 2;
5575let foo = «2ˇ»;"#,
5576 );
5577}
5578
5579#[gpui::test]
5580async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5581 init_test(cx, |_| {});
5582
5583 let mut cx =
5584 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5585
5586 cx.assert_editor_state(indoc! {"
5587 ˇbbb
5588 ccc
5589
5590 bbb
5591 ccc
5592 "});
5593 cx.dispatch_action(SelectPrevious::default());
5594 cx.assert_editor_state(indoc! {"
5595 «bbbˇ»
5596 ccc
5597
5598 bbb
5599 ccc
5600 "});
5601 cx.dispatch_action(SelectPrevious::default());
5602 cx.assert_editor_state(indoc! {"
5603 «bbbˇ»
5604 ccc
5605
5606 «bbbˇ»
5607 ccc
5608 "});
5609}
5610
5611#[gpui::test]
5612async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5613 init_test(cx, |_| {});
5614
5615 let mut cx = EditorTestContext::new(cx).await;
5616 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5617
5618 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5619 .unwrap();
5620 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5621
5622 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5623 .unwrap();
5624 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5625
5626 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5627 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5628
5629 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5630 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5631
5632 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5633 .unwrap();
5634 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5635
5636 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5637 .unwrap();
5638 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5639
5640 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5641 .unwrap();
5642 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5643}
5644
5645#[gpui::test]
5646async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5647 init_test(cx, |_| {});
5648
5649 let mut cx = EditorTestContext::new(cx).await;
5650 cx.set_state("aˇ");
5651
5652 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5653 .unwrap();
5654 cx.assert_editor_state("«aˇ»");
5655 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5656 .unwrap();
5657 cx.assert_editor_state("«aˇ»");
5658}
5659
5660#[gpui::test]
5661async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5662 init_test(cx, |_| {});
5663
5664 let mut cx = EditorTestContext::new(cx).await;
5665 cx.set_state(
5666 r#"let foo = 2;
5667lˇet foo = 2;
5668let fooˇ = 2;
5669let foo = 2;
5670let foo = ˇ2;"#,
5671 );
5672
5673 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5674 .unwrap();
5675 cx.assert_editor_state(
5676 r#"let foo = 2;
5677«letˇ» foo = 2;
5678let «fooˇ» = 2;
5679let foo = 2;
5680let foo = «2ˇ»;"#,
5681 );
5682
5683 // noop for multiple selections with different contents
5684 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5685 .unwrap();
5686 cx.assert_editor_state(
5687 r#"let foo = 2;
5688«letˇ» foo = 2;
5689let «fooˇ» = 2;
5690let foo = 2;
5691let foo = «2ˇ»;"#,
5692 );
5693}
5694
5695#[gpui::test]
5696async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5697 init_test(cx, |_| {});
5698
5699 let mut cx = EditorTestContext::new(cx).await;
5700 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5701
5702 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5703 .unwrap();
5704 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5705
5706 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5707 .unwrap();
5708 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5709
5710 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5711 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5712
5713 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5714 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5715
5716 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5717 .unwrap();
5718 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5719
5720 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5721 .unwrap();
5722 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5723}
5724
5725#[gpui::test]
5726async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5727 init_test(cx, |_| {});
5728
5729 let language = Arc::new(Language::new(
5730 LanguageConfig::default(),
5731 Some(tree_sitter_rust::LANGUAGE.into()),
5732 ));
5733
5734 let text = r#"
5735 use mod1::mod2::{mod3, mod4};
5736
5737 fn fn_1(param1: bool, param2: &str) {
5738 let var1 = "text";
5739 }
5740 "#
5741 .unindent();
5742
5743 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5745 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5746
5747 editor
5748 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5749 .await;
5750
5751 editor.update_in(cx, |editor, window, cx| {
5752 editor.change_selections(None, window, cx, |s| {
5753 s.select_display_ranges([
5754 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5755 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5756 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5757 ]);
5758 });
5759 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5760 });
5761 editor.update(cx, |editor, cx| {
5762 assert_text_with_selections(
5763 editor,
5764 indoc! {r#"
5765 use mod1::mod2::{mod3, «mod4ˇ»};
5766
5767 fn fn_1«ˇ(param1: bool, param2: &str)» {
5768 let var1 = "«textˇ»";
5769 }
5770 "#},
5771 cx,
5772 );
5773 });
5774
5775 editor.update_in(cx, |editor, window, cx| {
5776 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5777 });
5778 editor.update(cx, |editor, cx| {
5779 assert_text_with_selections(
5780 editor,
5781 indoc! {r#"
5782 use mod1::mod2::«{mod3, mod4}ˇ»;
5783
5784 «ˇfn fn_1(param1: bool, param2: &str) {
5785 let var1 = "text";
5786 }»
5787 "#},
5788 cx,
5789 );
5790 });
5791
5792 editor.update_in(cx, |editor, window, cx| {
5793 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5794 });
5795 assert_eq!(
5796 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5797 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5798 );
5799
5800 // Trying to expand the selected syntax node one more time has no effect.
5801 editor.update_in(cx, |editor, window, cx| {
5802 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5803 });
5804 assert_eq!(
5805 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5806 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5807 );
5808
5809 editor.update_in(cx, |editor, window, cx| {
5810 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5811 });
5812 editor.update(cx, |editor, cx| {
5813 assert_text_with_selections(
5814 editor,
5815 indoc! {r#"
5816 use mod1::mod2::«{mod3, mod4}ˇ»;
5817
5818 «ˇfn fn_1(param1: bool, param2: &str) {
5819 let var1 = "text";
5820 }»
5821 "#},
5822 cx,
5823 );
5824 });
5825
5826 editor.update_in(cx, |editor, window, cx| {
5827 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5828 });
5829 editor.update(cx, |editor, cx| {
5830 assert_text_with_selections(
5831 editor,
5832 indoc! {r#"
5833 use mod1::mod2::{mod3, «mod4ˇ»};
5834
5835 fn fn_1«ˇ(param1: bool, param2: &str)» {
5836 let var1 = "«textˇ»";
5837 }
5838 "#},
5839 cx,
5840 );
5841 });
5842
5843 editor.update_in(cx, |editor, window, cx| {
5844 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5845 });
5846 editor.update(cx, |editor, cx| {
5847 assert_text_with_selections(
5848 editor,
5849 indoc! {r#"
5850 use mod1::mod2::{mod3, mo«ˇ»d4};
5851
5852 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5853 let var1 = "te«ˇ»xt";
5854 }
5855 "#},
5856 cx,
5857 );
5858 });
5859
5860 // Trying to shrink the selected syntax node one more time has no effect.
5861 editor.update_in(cx, |editor, window, cx| {
5862 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5863 });
5864 editor.update_in(cx, |editor, _, cx| {
5865 assert_text_with_selections(
5866 editor,
5867 indoc! {r#"
5868 use mod1::mod2::{mod3, mo«ˇ»d4};
5869
5870 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5871 let var1 = "te«ˇ»xt";
5872 }
5873 "#},
5874 cx,
5875 );
5876 });
5877
5878 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5879 // a fold.
5880 editor.update_in(cx, |editor, window, cx| {
5881 editor.fold_creases(
5882 vec![
5883 Crease::simple(
5884 Point::new(0, 21)..Point::new(0, 24),
5885 FoldPlaceholder::test(),
5886 ),
5887 Crease::simple(
5888 Point::new(3, 20)..Point::new(3, 22),
5889 FoldPlaceholder::test(),
5890 ),
5891 ],
5892 true,
5893 window,
5894 cx,
5895 );
5896 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5897 });
5898 editor.update(cx, |editor, cx| {
5899 assert_text_with_selections(
5900 editor,
5901 indoc! {r#"
5902 use mod1::mod2::«{mod3, mod4}ˇ»;
5903
5904 fn fn_1«ˇ(param1: bool, param2: &str)» {
5905 «let var1 = "text";ˇ»
5906 }
5907 "#},
5908 cx,
5909 );
5910 });
5911}
5912
5913#[gpui::test]
5914async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5915 init_test(cx, |_| {});
5916
5917 let base_text = r#"
5918 impl A {
5919 // this is an uncommitted comment
5920
5921 fn b() {
5922 c();
5923 }
5924
5925 // this is another uncommitted comment
5926
5927 fn d() {
5928 // e
5929 // f
5930 }
5931 }
5932
5933 fn g() {
5934 // h
5935 }
5936 "#
5937 .unindent();
5938
5939 let text = r#"
5940 ˇimpl A {
5941
5942 fn b() {
5943 c();
5944 }
5945
5946 fn d() {
5947 // e
5948 // f
5949 }
5950 }
5951
5952 fn g() {
5953 // h
5954 }
5955 "#
5956 .unindent();
5957
5958 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5959 cx.set_state(&text);
5960 cx.set_head_text(&base_text);
5961 cx.update_editor(|editor, window, cx| {
5962 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5963 });
5964
5965 cx.assert_state_with_diff(
5966 "
5967 ˇimpl A {
5968 - // this is an uncommitted comment
5969
5970 fn b() {
5971 c();
5972 }
5973
5974 - // this is another uncommitted comment
5975 -
5976 fn d() {
5977 // e
5978 // f
5979 }
5980 }
5981
5982 fn g() {
5983 // h
5984 }
5985 "
5986 .unindent(),
5987 );
5988
5989 let expected_display_text = "
5990 impl A {
5991 // this is an uncommitted comment
5992
5993 fn b() {
5994 ⋯
5995 }
5996
5997 // this is another uncommitted comment
5998
5999 fn d() {
6000 ⋯
6001 }
6002 }
6003
6004 fn g() {
6005 ⋯
6006 }
6007 "
6008 .unindent();
6009
6010 cx.update_editor(|editor, window, cx| {
6011 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6012 assert_eq!(editor.display_text(cx), expected_display_text);
6013 });
6014}
6015
6016#[gpui::test]
6017async fn test_autoindent(cx: &mut TestAppContext) {
6018 init_test(cx, |_| {});
6019
6020 let language = Arc::new(
6021 Language::new(
6022 LanguageConfig {
6023 brackets: BracketPairConfig {
6024 pairs: vec![
6025 BracketPair {
6026 start: "{".to_string(),
6027 end: "}".to_string(),
6028 close: false,
6029 surround: false,
6030 newline: true,
6031 },
6032 BracketPair {
6033 start: "(".to_string(),
6034 end: ")".to_string(),
6035 close: false,
6036 surround: false,
6037 newline: true,
6038 },
6039 ],
6040 ..Default::default()
6041 },
6042 ..Default::default()
6043 },
6044 Some(tree_sitter_rust::LANGUAGE.into()),
6045 )
6046 .with_indents_query(
6047 r#"
6048 (_ "(" ")" @end) @indent
6049 (_ "{" "}" @end) @indent
6050 "#,
6051 )
6052 .unwrap(),
6053 );
6054
6055 let text = "fn a() {}";
6056
6057 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6058 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6059 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6060 editor
6061 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6062 .await;
6063
6064 editor.update_in(cx, |editor, window, cx| {
6065 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6066 editor.newline(&Newline, window, cx);
6067 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6068 assert_eq!(
6069 editor.selections.ranges(cx),
6070 &[
6071 Point::new(1, 4)..Point::new(1, 4),
6072 Point::new(3, 4)..Point::new(3, 4),
6073 Point::new(5, 0)..Point::new(5, 0)
6074 ]
6075 );
6076 });
6077}
6078
6079#[gpui::test]
6080async fn test_autoindent_selections(cx: &mut TestAppContext) {
6081 init_test(cx, |_| {});
6082
6083 {
6084 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6085 cx.set_state(indoc! {"
6086 impl A {
6087
6088 fn b() {}
6089
6090 «fn c() {
6091
6092 }ˇ»
6093 }
6094 "});
6095
6096 cx.update_editor(|editor, window, cx| {
6097 editor.autoindent(&Default::default(), window, cx);
6098 });
6099
6100 cx.assert_editor_state(indoc! {"
6101 impl A {
6102
6103 fn b() {}
6104
6105 «fn c() {
6106
6107 }ˇ»
6108 }
6109 "});
6110 }
6111
6112 {
6113 let mut cx = EditorTestContext::new_multibuffer(
6114 cx,
6115 [indoc! { "
6116 impl A {
6117 «
6118 // a
6119 fn b(){}
6120 »
6121 «
6122 }
6123 fn c(){}
6124 »
6125 "}],
6126 );
6127
6128 let buffer = cx.update_editor(|editor, _, cx| {
6129 let buffer = editor.buffer().update(cx, |buffer, _| {
6130 buffer.all_buffers().iter().next().unwrap().clone()
6131 });
6132 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6133 buffer
6134 });
6135
6136 cx.run_until_parked();
6137 cx.update_editor(|editor, window, cx| {
6138 editor.select_all(&Default::default(), window, cx);
6139 editor.autoindent(&Default::default(), window, cx)
6140 });
6141 cx.run_until_parked();
6142
6143 cx.update(|_, cx| {
6144 pretty_assertions::assert_eq!(
6145 buffer.read(cx).text(),
6146 indoc! { "
6147 impl A {
6148
6149 // a
6150 fn b(){}
6151
6152
6153 }
6154 fn c(){}
6155
6156 " }
6157 )
6158 });
6159 }
6160}
6161
6162#[gpui::test]
6163async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6164 init_test(cx, |_| {});
6165
6166 let mut cx = EditorTestContext::new(cx).await;
6167
6168 let language = Arc::new(Language::new(
6169 LanguageConfig {
6170 brackets: BracketPairConfig {
6171 pairs: vec![
6172 BracketPair {
6173 start: "{".to_string(),
6174 end: "}".to_string(),
6175 close: true,
6176 surround: true,
6177 newline: true,
6178 },
6179 BracketPair {
6180 start: "(".to_string(),
6181 end: ")".to_string(),
6182 close: true,
6183 surround: true,
6184 newline: true,
6185 },
6186 BracketPair {
6187 start: "/*".to_string(),
6188 end: " */".to_string(),
6189 close: true,
6190 surround: true,
6191 newline: true,
6192 },
6193 BracketPair {
6194 start: "[".to_string(),
6195 end: "]".to_string(),
6196 close: false,
6197 surround: false,
6198 newline: true,
6199 },
6200 BracketPair {
6201 start: "\"".to_string(),
6202 end: "\"".to_string(),
6203 close: true,
6204 surround: true,
6205 newline: false,
6206 },
6207 BracketPair {
6208 start: "<".to_string(),
6209 end: ">".to_string(),
6210 close: false,
6211 surround: true,
6212 newline: true,
6213 },
6214 ],
6215 ..Default::default()
6216 },
6217 autoclose_before: "})]".to_string(),
6218 ..Default::default()
6219 },
6220 Some(tree_sitter_rust::LANGUAGE.into()),
6221 ));
6222
6223 cx.language_registry().add(language.clone());
6224 cx.update_buffer(|buffer, cx| {
6225 buffer.set_language(Some(language), cx);
6226 });
6227
6228 cx.set_state(
6229 &r#"
6230 🏀ˇ
6231 εˇ
6232 ❤️ˇ
6233 "#
6234 .unindent(),
6235 );
6236
6237 // autoclose multiple nested brackets at multiple cursors
6238 cx.update_editor(|editor, window, cx| {
6239 editor.handle_input("{", window, cx);
6240 editor.handle_input("{", window, cx);
6241 editor.handle_input("{", window, cx);
6242 });
6243 cx.assert_editor_state(
6244 &"
6245 🏀{{{ˇ}}}
6246 ε{{{ˇ}}}
6247 ❤️{{{ˇ}}}
6248 "
6249 .unindent(),
6250 );
6251
6252 // insert a different closing bracket
6253 cx.update_editor(|editor, window, cx| {
6254 editor.handle_input(")", window, cx);
6255 });
6256 cx.assert_editor_state(
6257 &"
6258 🏀{{{)ˇ}}}
6259 ε{{{)ˇ}}}
6260 ❤️{{{)ˇ}}}
6261 "
6262 .unindent(),
6263 );
6264
6265 // skip over the auto-closed brackets when typing a closing bracket
6266 cx.update_editor(|editor, window, cx| {
6267 editor.move_right(&MoveRight, window, cx);
6268 editor.handle_input("}", window, cx);
6269 editor.handle_input("}", window, cx);
6270 editor.handle_input("}", window, cx);
6271 });
6272 cx.assert_editor_state(
6273 &"
6274 🏀{{{)}}}}ˇ
6275 ε{{{)}}}}ˇ
6276 ❤️{{{)}}}}ˇ
6277 "
6278 .unindent(),
6279 );
6280
6281 // autoclose multi-character pairs
6282 cx.set_state(
6283 &"
6284 ˇ
6285 ˇ
6286 "
6287 .unindent(),
6288 );
6289 cx.update_editor(|editor, window, cx| {
6290 editor.handle_input("/", window, cx);
6291 editor.handle_input("*", window, cx);
6292 });
6293 cx.assert_editor_state(
6294 &"
6295 /*ˇ */
6296 /*ˇ */
6297 "
6298 .unindent(),
6299 );
6300
6301 // one cursor autocloses a multi-character pair, one cursor
6302 // does not autoclose.
6303 cx.set_state(
6304 &"
6305 /ˇ
6306 ˇ
6307 "
6308 .unindent(),
6309 );
6310 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6311 cx.assert_editor_state(
6312 &"
6313 /*ˇ */
6314 *ˇ
6315 "
6316 .unindent(),
6317 );
6318
6319 // Don't autoclose if the next character isn't whitespace and isn't
6320 // listed in the language's "autoclose_before" section.
6321 cx.set_state("ˇa b");
6322 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6323 cx.assert_editor_state("{ˇa b");
6324
6325 // Don't autoclose if `close` is false for the bracket pair
6326 cx.set_state("ˇ");
6327 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6328 cx.assert_editor_state("[ˇ");
6329
6330 // Surround with brackets if text is selected
6331 cx.set_state("«aˇ» b");
6332 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6333 cx.assert_editor_state("{«aˇ»} b");
6334
6335 // Autclose pair where the start and end characters are the same
6336 cx.set_state("aˇ");
6337 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6338 cx.assert_editor_state("a\"ˇ\"");
6339 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6340 cx.assert_editor_state("a\"\"ˇ");
6341
6342 // Don't autoclose pair if autoclose is disabled
6343 cx.set_state("ˇ");
6344 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6345 cx.assert_editor_state("<ˇ");
6346
6347 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6348 cx.set_state("«aˇ» b");
6349 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6350 cx.assert_editor_state("<«aˇ»> b");
6351}
6352
6353#[gpui::test]
6354async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6355 init_test(cx, |settings| {
6356 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6357 });
6358
6359 let mut cx = EditorTestContext::new(cx).await;
6360
6361 let language = Arc::new(Language::new(
6362 LanguageConfig {
6363 brackets: BracketPairConfig {
6364 pairs: vec![
6365 BracketPair {
6366 start: "{".to_string(),
6367 end: "}".to_string(),
6368 close: true,
6369 surround: true,
6370 newline: true,
6371 },
6372 BracketPair {
6373 start: "(".to_string(),
6374 end: ")".to_string(),
6375 close: true,
6376 surround: true,
6377 newline: true,
6378 },
6379 BracketPair {
6380 start: "[".to_string(),
6381 end: "]".to_string(),
6382 close: false,
6383 surround: false,
6384 newline: true,
6385 },
6386 ],
6387 ..Default::default()
6388 },
6389 autoclose_before: "})]".to_string(),
6390 ..Default::default()
6391 },
6392 Some(tree_sitter_rust::LANGUAGE.into()),
6393 ));
6394
6395 cx.language_registry().add(language.clone());
6396 cx.update_buffer(|buffer, cx| {
6397 buffer.set_language(Some(language), cx);
6398 });
6399
6400 cx.set_state(
6401 &"
6402 ˇ
6403 ˇ
6404 ˇ
6405 "
6406 .unindent(),
6407 );
6408
6409 // ensure only matching closing brackets are skipped over
6410 cx.update_editor(|editor, window, cx| {
6411 editor.handle_input("}", window, cx);
6412 editor.move_left(&MoveLeft, window, cx);
6413 editor.handle_input(")", window, cx);
6414 editor.move_left(&MoveLeft, window, cx);
6415 });
6416 cx.assert_editor_state(
6417 &"
6418 ˇ)}
6419 ˇ)}
6420 ˇ)}
6421 "
6422 .unindent(),
6423 );
6424
6425 // skip-over closing brackets at multiple cursors
6426 cx.update_editor(|editor, window, cx| {
6427 editor.handle_input(")", window, cx);
6428 editor.handle_input("}", window, cx);
6429 });
6430 cx.assert_editor_state(
6431 &"
6432 )}ˇ
6433 )}ˇ
6434 )}ˇ
6435 "
6436 .unindent(),
6437 );
6438
6439 // ignore non-close brackets
6440 cx.update_editor(|editor, window, cx| {
6441 editor.handle_input("]", window, cx);
6442 editor.move_left(&MoveLeft, window, cx);
6443 editor.handle_input("]", window, cx);
6444 });
6445 cx.assert_editor_state(
6446 &"
6447 )}]ˇ]
6448 )}]ˇ]
6449 )}]ˇ]
6450 "
6451 .unindent(),
6452 );
6453}
6454
6455#[gpui::test]
6456async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6457 init_test(cx, |_| {});
6458
6459 let mut cx = EditorTestContext::new(cx).await;
6460
6461 let html_language = Arc::new(
6462 Language::new(
6463 LanguageConfig {
6464 name: "HTML".into(),
6465 brackets: BracketPairConfig {
6466 pairs: vec![
6467 BracketPair {
6468 start: "<".into(),
6469 end: ">".into(),
6470 close: true,
6471 ..Default::default()
6472 },
6473 BracketPair {
6474 start: "{".into(),
6475 end: "}".into(),
6476 close: true,
6477 ..Default::default()
6478 },
6479 BracketPair {
6480 start: "(".into(),
6481 end: ")".into(),
6482 close: true,
6483 ..Default::default()
6484 },
6485 ],
6486 ..Default::default()
6487 },
6488 autoclose_before: "})]>".into(),
6489 ..Default::default()
6490 },
6491 Some(tree_sitter_html::LANGUAGE.into()),
6492 )
6493 .with_injection_query(
6494 r#"
6495 (script_element
6496 (raw_text) @injection.content
6497 (#set! injection.language "javascript"))
6498 "#,
6499 )
6500 .unwrap(),
6501 );
6502
6503 let javascript_language = Arc::new(Language::new(
6504 LanguageConfig {
6505 name: "JavaScript".into(),
6506 brackets: BracketPairConfig {
6507 pairs: vec![
6508 BracketPair {
6509 start: "/*".into(),
6510 end: " */".into(),
6511 close: true,
6512 ..Default::default()
6513 },
6514 BracketPair {
6515 start: "{".into(),
6516 end: "}".into(),
6517 close: true,
6518 ..Default::default()
6519 },
6520 BracketPair {
6521 start: "(".into(),
6522 end: ")".into(),
6523 close: true,
6524 ..Default::default()
6525 },
6526 ],
6527 ..Default::default()
6528 },
6529 autoclose_before: "})]>".into(),
6530 ..Default::default()
6531 },
6532 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6533 ));
6534
6535 cx.language_registry().add(html_language.clone());
6536 cx.language_registry().add(javascript_language.clone());
6537
6538 cx.update_buffer(|buffer, cx| {
6539 buffer.set_language(Some(html_language), cx);
6540 });
6541
6542 cx.set_state(
6543 &r#"
6544 <body>ˇ
6545 <script>
6546 var x = 1;ˇ
6547 </script>
6548 </body>ˇ
6549 "#
6550 .unindent(),
6551 );
6552
6553 // Precondition: different languages are active at different locations.
6554 cx.update_editor(|editor, window, cx| {
6555 let snapshot = editor.snapshot(window, cx);
6556 let cursors = editor.selections.ranges::<usize>(cx);
6557 let languages = cursors
6558 .iter()
6559 .map(|c| snapshot.language_at(c.start).unwrap().name())
6560 .collect::<Vec<_>>();
6561 assert_eq!(
6562 languages,
6563 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6564 );
6565 });
6566
6567 // Angle brackets autoclose in HTML, but not JavaScript.
6568 cx.update_editor(|editor, window, cx| {
6569 editor.handle_input("<", window, cx);
6570 editor.handle_input("a", window, cx);
6571 });
6572 cx.assert_editor_state(
6573 &r#"
6574 <body><aˇ>
6575 <script>
6576 var x = 1;<aˇ
6577 </script>
6578 </body><aˇ>
6579 "#
6580 .unindent(),
6581 );
6582
6583 // Curly braces and parens autoclose in both HTML and JavaScript.
6584 cx.update_editor(|editor, window, cx| {
6585 editor.handle_input(" b=", window, cx);
6586 editor.handle_input("{", window, cx);
6587 editor.handle_input("c", window, cx);
6588 editor.handle_input("(", window, cx);
6589 });
6590 cx.assert_editor_state(
6591 &r#"
6592 <body><a b={c(ˇ)}>
6593 <script>
6594 var x = 1;<a b={c(ˇ)}
6595 </script>
6596 </body><a b={c(ˇ)}>
6597 "#
6598 .unindent(),
6599 );
6600
6601 // Brackets that were already autoclosed are skipped.
6602 cx.update_editor(|editor, window, cx| {
6603 editor.handle_input(")", window, cx);
6604 editor.handle_input("d", window, cx);
6605 editor.handle_input("}", window, cx);
6606 });
6607 cx.assert_editor_state(
6608 &r#"
6609 <body><a b={c()d}ˇ>
6610 <script>
6611 var x = 1;<a b={c()d}ˇ
6612 </script>
6613 </body><a b={c()d}ˇ>
6614 "#
6615 .unindent(),
6616 );
6617 cx.update_editor(|editor, window, cx| {
6618 editor.handle_input(">", window, cx);
6619 });
6620 cx.assert_editor_state(
6621 &r#"
6622 <body><a b={c()d}>ˇ
6623 <script>
6624 var x = 1;<a b={c()d}>ˇ
6625 </script>
6626 </body><a b={c()d}>ˇ
6627 "#
6628 .unindent(),
6629 );
6630
6631 // Reset
6632 cx.set_state(
6633 &r#"
6634 <body>ˇ
6635 <script>
6636 var x = 1;ˇ
6637 </script>
6638 </body>ˇ
6639 "#
6640 .unindent(),
6641 );
6642
6643 cx.update_editor(|editor, window, cx| {
6644 editor.handle_input("<", window, cx);
6645 });
6646 cx.assert_editor_state(
6647 &r#"
6648 <body><ˇ>
6649 <script>
6650 var x = 1;<ˇ
6651 </script>
6652 </body><ˇ>
6653 "#
6654 .unindent(),
6655 );
6656
6657 // When backspacing, the closing angle brackets are removed.
6658 cx.update_editor(|editor, window, cx| {
6659 editor.backspace(&Backspace, window, cx);
6660 });
6661 cx.assert_editor_state(
6662 &r#"
6663 <body>ˇ
6664 <script>
6665 var x = 1;ˇ
6666 </script>
6667 </body>ˇ
6668 "#
6669 .unindent(),
6670 );
6671
6672 // Block comments autoclose in JavaScript, but not HTML.
6673 cx.update_editor(|editor, window, cx| {
6674 editor.handle_input("/", window, cx);
6675 editor.handle_input("*", window, cx);
6676 });
6677 cx.assert_editor_state(
6678 &r#"
6679 <body>/*ˇ
6680 <script>
6681 var x = 1;/*ˇ */
6682 </script>
6683 </body>/*ˇ
6684 "#
6685 .unindent(),
6686 );
6687}
6688
6689#[gpui::test]
6690async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6691 init_test(cx, |_| {});
6692
6693 let mut cx = EditorTestContext::new(cx).await;
6694
6695 let rust_language = Arc::new(
6696 Language::new(
6697 LanguageConfig {
6698 name: "Rust".into(),
6699 brackets: serde_json::from_value(json!([
6700 { "start": "{", "end": "}", "close": true, "newline": true },
6701 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6702 ]))
6703 .unwrap(),
6704 autoclose_before: "})]>".into(),
6705 ..Default::default()
6706 },
6707 Some(tree_sitter_rust::LANGUAGE.into()),
6708 )
6709 .with_override_query("(string_literal) @string")
6710 .unwrap(),
6711 );
6712
6713 cx.language_registry().add(rust_language.clone());
6714 cx.update_buffer(|buffer, cx| {
6715 buffer.set_language(Some(rust_language), cx);
6716 });
6717
6718 cx.set_state(
6719 &r#"
6720 let x = ˇ
6721 "#
6722 .unindent(),
6723 );
6724
6725 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6726 cx.update_editor(|editor, window, cx| {
6727 editor.handle_input("\"", window, cx);
6728 });
6729 cx.assert_editor_state(
6730 &r#"
6731 let x = "ˇ"
6732 "#
6733 .unindent(),
6734 );
6735
6736 // Inserting another quotation mark. The cursor moves across the existing
6737 // automatically-inserted quotation mark.
6738 cx.update_editor(|editor, window, cx| {
6739 editor.handle_input("\"", window, cx);
6740 });
6741 cx.assert_editor_state(
6742 &r#"
6743 let x = ""ˇ
6744 "#
6745 .unindent(),
6746 );
6747
6748 // Reset
6749 cx.set_state(
6750 &r#"
6751 let x = ˇ
6752 "#
6753 .unindent(),
6754 );
6755
6756 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6757 cx.update_editor(|editor, window, cx| {
6758 editor.handle_input("\"", window, cx);
6759 editor.handle_input(" ", window, cx);
6760 editor.move_left(&Default::default(), window, cx);
6761 editor.handle_input("\\", window, cx);
6762 editor.handle_input("\"", window, cx);
6763 });
6764 cx.assert_editor_state(
6765 &r#"
6766 let x = "\"ˇ "
6767 "#
6768 .unindent(),
6769 );
6770
6771 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6772 // mark. Nothing is inserted.
6773 cx.update_editor(|editor, window, cx| {
6774 editor.move_right(&Default::default(), window, cx);
6775 editor.handle_input("\"", window, cx);
6776 });
6777 cx.assert_editor_state(
6778 &r#"
6779 let x = "\" "ˇ
6780 "#
6781 .unindent(),
6782 );
6783}
6784
6785#[gpui::test]
6786async fn test_surround_with_pair(cx: &mut TestAppContext) {
6787 init_test(cx, |_| {});
6788
6789 let language = Arc::new(Language::new(
6790 LanguageConfig {
6791 brackets: BracketPairConfig {
6792 pairs: vec![
6793 BracketPair {
6794 start: "{".to_string(),
6795 end: "}".to_string(),
6796 close: true,
6797 surround: true,
6798 newline: true,
6799 },
6800 BracketPair {
6801 start: "/* ".to_string(),
6802 end: "*/".to_string(),
6803 close: true,
6804 surround: true,
6805 ..Default::default()
6806 },
6807 ],
6808 ..Default::default()
6809 },
6810 ..Default::default()
6811 },
6812 Some(tree_sitter_rust::LANGUAGE.into()),
6813 ));
6814
6815 let text = r#"
6816 a
6817 b
6818 c
6819 "#
6820 .unindent();
6821
6822 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6823 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6824 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6825 editor
6826 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6827 .await;
6828
6829 editor.update_in(cx, |editor, window, cx| {
6830 editor.change_selections(None, window, cx, |s| {
6831 s.select_display_ranges([
6832 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6833 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6834 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6835 ])
6836 });
6837
6838 editor.handle_input("{", window, cx);
6839 editor.handle_input("{", window, cx);
6840 editor.handle_input("{", window, cx);
6841 assert_eq!(
6842 editor.text(cx),
6843 "
6844 {{{a}}}
6845 {{{b}}}
6846 {{{c}}}
6847 "
6848 .unindent()
6849 );
6850 assert_eq!(
6851 editor.selections.display_ranges(cx),
6852 [
6853 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6854 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6855 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6856 ]
6857 );
6858
6859 editor.undo(&Undo, window, cx);
6860 editor.undo(&Undo, window, cx);
6861 editor.undo(&Undo, window, cx);
6862 assert_eq!(
6863 editor.text(cx),
6864 "
6865 a
6866 b
6867 c
6868 "
6869 .unindent()
6870 );
6871 assert_eq!(
6872 editor.selections.display_ranges(cx),
6873 [
6874 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6875 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6876 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6877 ]
6878 );
6879
6880 // Ensure inserting the first character of a multi-byte bracket pair
6881 // doesn't surround the selections with the bracket.
6882 editor.handle_input("/", window, cx);
6883 assert_eq!(
6884 editor.text(cx),
6885 "
6886 /
6887 /
6888 /
6889 "
6890 .unindent()
6891 );
6892 assert_eq!(
6893 editor.selections.display_ranges(cx),
6894 [
6895 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6896 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6897 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6898 ]
6899 );
6900
6901 editor.undo(&Undo, window, cx);
6902 assert_eq!(
6903 editor.text(cx),
6904 "
6905 a
6906 b
6907 c
6908 "
6909 .unindent()
6910 );
6911 assert_eq!(
6912 editor.selections.display_ranges(cx),
6913 [
6914 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6915 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6916 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6917 ]
6918 );
6919
6920 // Ensure inserting the last character of a multi-byte bracket pair
6921 // doesn't surround the selections with the bracket.
6922 editor.handle_input("*", window, cx);
6923 assert_eq!(
6924 editor.text(cx),
6925 "
6926 *
6927 *
6928 *
6929 "
6930 .unindent()
6931 );
6932 assert_eq!(
6933 editor.selections.display_ranges(cx),
6934 [
6935 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6936 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6937 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6938 ]
6939 );
6940 });
6941}
6942
6943#[gpui::test]
6944async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6945 init_test(cx, |_| {});
6946
6947 let language = Arc::new(Language::new(
6948 LanguageConfig {
6949 brackets: BracketPairConfig {
6950 pairs: vec![BracketPair {
6951 start: "{".to_string(),
6952 end: "}".to_string(),
6953 close: true,
6954 surround: true,
6955 newline: true,
6956 }],
6957 ..Default::default()
6958 },
6959 autoclose_before: "}".to_string(),
6960 ..Default::default()
6961 },
6962 Some(tree_sitter_rust::LANGUAGE.into()),
6963 ));
6964
6965 let text = r#"
6966 a
6967 b
6968 c
6969 "#
6970 .unindent();
6971
6972 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6973 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6974 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6975 editor
6976 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6977 .await;
6978
6979 editor.update_in(cx, |editor, window, cx| {
6980 editor.change_selections(None, window, cx, |s| {
6981 s.select_ranges([
6982 Point::new(0, 1)..Point::new(0, 1),
6983 Point::new(1, 1)..Point::new(1, 1),
6984 Point::new(2, 1)..Point::new(2, 1),
6985 ])
6986 });
6987
6988 editor.handle_input("{", window, cx);
6989 editor.handle_input("{", window, cx);
6990 editor.handle_input("_", window, cx);
6991 assert_eq!(
6992 editor.text(cx),
6993 "
6994 a{{_}}
6995 b{{_}}
6996 c{{_}}
6997 "
6998 .unindent()
6999 );
7000 assert_eq!(
7001 editor.selections.ranges::<Point>(cx),
7002 [
7003 Point::new(0, 4)..Point::new(0, 4),
7004 Point::new(1, 4)..Point::new(1, 4),
7005 Point::new(2, 4)..Point::new(2, 4)
7006 ]
7007 );
7008
7009 editor.backspace(&Default::default(), window, cx);
7010 editor.backspace(&Default::default(), window, cx);
7011 assert_eq!(
7012 editor.text(cx),
7013 "
7014 a{}
7015 b{}
7016 c{}
7017 "
7018 .unindent()
7019 );
7020 assert_eq!(
7021 editor.selections.ranges::<Point>(cx),
7022 [
7023 Point::new(0, 2)..Point::new(0, 2),
7024 Point::new(1, 2)..Point::new(1, 2),
7025 Point::new(2, 2)..Point::new(2, 2)
7026 ]
7027 );
7028
7029 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7030 assert_eq!(
7031 editor.text(cx),
7032 "
7033 a
7034 b
7035 c
7036 "
7037 .unindent()
7038 );
7039 assert_eq!(
7040 editor.selections.ranges::<Point>(cx),
7041 [
7042 Point::new(0, 1)..Point::new(0, 1),
7043 Point::new(1, 1)..Point::new(1, 1),
7044 Point::new(2, 1)..Point::new(2, 1)
7045 ]
7046 );
7047 });
7048}
7049
7050#[gpui::test]
7051async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7052 init_test(cx, |settings| {
7053 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7054 });
7055
7056 let mut cx = EditorTestContext::new(cx).await;
7057
7058 let language = Arc::new(Language::new(
7059 LanguageConfig {
7060 brackets: BracketPairConfig {
7061 pairs: vec![
7062 BracketPair {
7063 start: "{".to_string(),
7064 end: "}".to_string(),
7065 close: true,
7066 surround: true,
7067 newline: true,
7068 },
7069 BracketPair {
7070 start: "(".to_string(),
7071 end: ")".to_string(),
7072 close: true,
7073 surround: true,
7074 newline: true,
7075 },
7076 BracketPair {
7077 start: "[".to_string(),
7078 end: "]".to_string(),
7079 close: false,
7080 surround: true,
7081 newline: true,
7082 },
7083 ],
7084 ..Default::default()
7085 },
7086 autoclose_before: "})]".to_string(),
7087 ..Default::default()
7088 },
7089 Some(tree_sitter_rust::LANGUAGE.into()),
7090 ));
7091
7092 cx.language_registry().add(language.clone());
7093 cx.update_buffer(|buffer, cx| {
7094 buffer.set_language(Some(language), cx);
7095 });
7096
7097 cx.set_state(
7098 &"
7099 {(ˇ)}
7100 [[ˇ]]
7101 {(ˇ)}
7102 "
7103 .unindent(),
7104 );
7105
7106 cx.update_editor(|editor, window, cx| {
7107 editor.backspace(&Default::default(), window, cx);
7108 editor.backspace(&Default::default(), window, cx);
7109 });
7110
7111 cx.assert_editor_state(
7112 &"
7113 ˇ
7114 ˇ]]
7115 ˇ
7116 "
7117 .unindent(),
7118 );
7119
7120 cx.update_editor(|editor, window, cx| {
7121 editor.handle_input("{", window, cx);
7122 editor.handle_input("{", window, cx);
7123 editor.move_right(&MoveRight, window, cx);
7124 editor.move_right(&MoveRight, window, cx);
7125 editor.move_left(&MoveLeft, window, cx);
7126 editor.move_left(&MoveLeft, window, cx);
7127 editor.backspace(&Default::default(), window, cx);
7128 });
7129
7130 cx.assert_editor_state(
7131 &"
7132 {ˇ}
7133 {ˇ}]]
7134 {ˇ}
7135 "
7136 .unindent(),
7137 );
7138
7139 cx.update_editor(|editor, window, cx| {
7140 editor.backspace(&Default::default(), window, cx);
7141 });
7142
7143 cx.assert_editor_state(
7144 &"
7145 ˇ
7146 ˇ]]
7147 ˇ
7148 "
7149 .unindent(),
7150 );
7151}
7152
7153#[gpui::test]
7154async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7155 init_test(cx, |_| {});
7156
7157 let language = Arc::new(Language::new(
7158 LanguageConfig::default(),
7159 Some(tree_sitter_rust::LANGUAGE.into()),
7160 ));
7161
7162 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7163 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7164 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7165 editor
7166 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7167 .await;
7168
7169 editor.update_in(cx, |editor, window, cx| {
7170 editor.set_auto_replace_emoji_shortcode(true);
7171
7172 editor.handle_input("Hello ", window, cx);
7173 editor.handle_input(":wave", window, cx);
7174 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7175
7176 editor.handle_input(":", window, cx);
7177 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7178
7179 editor.handle_input(" :smile", window, cx);
7180 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7181
7182 editor.handle_input(":", window, cx);
7183 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7184
7185 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7186 editor.handle_input(":wave", window, cx);
7187 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7188
7189 editor.handle_input(":", window, cx);
7190 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7191
7192 editor.handle_input(":1", window, cx);
7193 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7194
7195 editor.handle_input(":", window, cx);
7196 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7197
7198 // Ensure shortcode does not get replaced when it is part of a word
7199 editor.handle_input(" Test:wave", window, cx);
7200 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7201
7202 editor.handle_input(":", window, cx);
7203 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7204
7205 editor.set_auto_replace_emoji_shortcode(false);
7206
7207 // Ensure shortcode does not get replaced when auto replace is off
7208 editor.handle_input(" :wave", window, cx);
7209 assert_eq!(
7210 editor.text(cx),
7211 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7212 );
7213
7214 editor.handle_input(":", window, cx);
7215 assert_eq!(
7216 editor.text(cx),
7217 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7218 );
7219 });
7220}
7221
7222#[gpui::test]
7223async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7224 init_test(cx, |_| {});
7225
7226 let (text, insertion_ranges) = marked_text_ranges(
7227 indoc! {"
7228 ˇ
7229 "},
7230 false,
7231 );
7232
7233 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7234 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7235
7236 _ = editor.update_in(cx, |editor, window, cx| {
7237 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7238
7239 editor
7240 .insert_snippet(&insertion_ranges, snippet, window, cx)
7241 .unwrap();
7242
7243 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7244 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7245 assert_eq!(editor.text(cx), expected_text);
7246 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7247 }
7248
7249 assert(
7250 editor,
7251 cx,
7252 indoc! {"
7253 type «» =•
7254 "},
7255 );
7256
7257 assert!(editor.context_menu_visible(), "There should be a matches");
7258 });
7259}
7260
7261#[gpui::test]
7262async fn test_snippets(cx: &mut TestAppContext) {
7263 init_test(cx, |_| {});
7264
7265 let (text, insertion_ranges) = marked_text_ranges(
7266 indoc! {"
7267 a.ˇ b
7268 a.ˇ b
7269 a.ˇ b
7270 "},
7271 false,
7272 );
7273
7274 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7275 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7276
7277 editor.update_in(cx, |editor, window, cx| {
7278 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7279
7280 editor
7281 .insert_snippet(&insertion_ranges, snippet, window, cx)
7282 .unwrap();
7283
7284 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7285 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7286 assert_eq!(editor.text(cx), expected_text);
7287 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7288 }
7289
7290 assert(
7291 editor,
7292 cx,
7293 indoc! {"
7294 a.f(«one», two, «three») b
7295 a.f(«one», two, «three») b
7296 a.f(«one», two, «three») b
7297 "},
7298 );
7299
7300 // Can't move earlier than the first tab stop
7301 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7302 assert(
7303 editor,
7304 cx,
7305 indoc! {"
7306 a.f(«one», two, «three») b
7307 a.f(«one», two, «three») b
7308 a.f(«one», two, «three») b
7309 "},
7310 );
7311
7312 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7313 assert(
7314 editor,
7315 cx,
7316 indoc! {"
7317 a.f(one, «two», three) b
7318 a.f(one, «two», three) b
7319 a.f(one, «two», three) b
7320 "},
7321 );
7322
7323 editor.move_to_prev_snippet_tabstop(window, cx);
7324 assert(
7325 editor,
7326 cx,
7327 indoc! {"
7328 a.f(«one», two, «three») b
7329 a.f(«one», two, «three») b
7330 a.f(«one», two, «three») b
7331 "},
7332 );
7333
7334 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7335 assert(
7336 editor,
7337 cx,
7338 indoc! {"
7339 a.f(one, «two», three) b
7340 a.f(one, «two», three) b
7341 a.f(one, «two», three) b
7342 "},
7343 );
7344 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7345 assert(
7346 editor,
7347 cx,
7348 indoc! {"
7349 a.f(one, two, three)ˇ b
7350 a.f(one, two, three)ˇ b
7351 a.f(one, two, three)ˇ b
7352 "},
7353 );
7354
7355 // As soon as the last tab stop is reached, snippet state is gone
7356 editor.move_to_prev_snippet_tabstop(window, cx);
7357 assert(
7358 editor,
7359 cx,
7360 indoc! {"
7361 a.f(one, two, three)ˇ b
7362 a.f(one, two, three)ˇ b
7363 a.f(one, two, three)ˇ b
7364 "},
7365 );
7366 });
7367}
7368
7369#[gpui::test]
7370async fn test_document_format_during_save(cx: &mut TestAppContext) {
7371 init_test(cx, |_| {});
7372
7373 let fs = FakeFs::new(cx.executor());
7374 fs.insert_file(path!("/file.rs"), Default::default()).await;
7375
7376 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7377
7378 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7379 language_registry.add(rust_lang());
7380 let mut fake_servers = language_registry.register_fake_lsp(
7381 "Rust",
7382 FakeLspAdapter {
7383 capabilities: lsp::ServerCapabilities {
7384 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7385 ..Default::default()
7386 },
7387 ..Default::default()
7388 },
7389 );
7390
7391 let buffer = project
7392 .update(cx, |project, cx| {
7393 project.open_local_buffer(path!("/file.rs"), cx)
7394 })
7395 .await
7396 .unwrap();
7397
7398 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7399 let (editor, cx) = cx.add_window_view(|window, cx| {
7400 build_editor_with_project(project.clone(), buffer, window, cx)
7401 });
7402 editor.update_in(cx, |editor, window, cx| {
7403 editor.set_text("one\ntwo\nthree\n", window, cx)
7404 });
7405 assert!(cx.read(|cx| editor.is_dirty(cx)));
7406
7407 cx.executor().start_waiting();
7408 let fake_server = fake_servers.next().await.unwrap();
7409
7410 let save = editor
7411 .update_in(cx, |editor, window, cx| {
7412 editor.save(true, project.clone(), window, cx)
7413 })
7414 .unwrap();
7415 fake_server
7416 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7417 assert_eq!(
7418 params.text_document.uri,
7419 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7420 );
7421 assert_eq!(params.options.tab_size, 4);
7422 Ok(Some(vec![lsp::TextEdit::new(
7423 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7424 ", ".to_string(),
7425 )]))
7426 })
7427 .next()
7428 .await;
7429 cx.executor().start_waiting();
7430 save.await;
7431
7432 assert_eq!(
7433 editor.update(cx, |editor, cx| editor.text(cx)),
7434 "one, two\nthree\n"
7435 );
7436 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7437
7438 editor.update_in(cx, |editor, window, cx| {
7439 editor.set_text("one\ntwo\nthree\n", window, cx)
7440 });
7441 assert!(cx.read(|cx| editor.is_dirty(cx)));
7442
7443 // Ensure we can still save even if formatting hangs.
7444 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7445 assert_eq!(
7446 params.text_document.uri,
7447 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7448 );
7449 futures::future::pending::<()>().await;
7450 unreachable!()
7451 });
7452 let save = editor
7453 .update_in(cx, |editor, window, cx| {
7454 editor.save(true, project.clone(), window, cx)
7455 })
7456 .unwrap();
7457 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7458 cx.executor().start_waiting();
7459 save.await;
7460 assert_eq!(
7461 editor.update(cx, |editor, cx| editor.text(cx)),
7462 "one\ntwo\nthree\n"
7463 );
7464 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7465
7466 // For non-dirty buffer, no formatting request should be sent
7467 let save = editor
7468 .update_in(cx, |editor, window, cx| {
7469 editor.save(true, project.clone(), window, cx)
7470 })
7471 .unwrap();
7472 let _pending_format_request = fake_server
7473 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7474 panic!("Should not be invoked on non-dirty buffer");
7475 })
7476 .next();
7477 cx.executor().start_waiting();
7478 save.await;
7479
7480 // Set rust language override and assert overridden tabsize is sent to language server
7481 update_test_language_settings(cx, |settings| {
7482 settings.languages.insert(
7483 "Rust".into(),
7484 LanguageSettingsContent {
7485 tab_size: NonZeroU32::new(8),
7486 ..Default::default()
7487 },
7488 );
7489 });
7490
7491 editor.update_in(cx, |editor, window, cx| {
7492 editor.set_text("somehting_new\n", window, cx)
7493 });
7494 assert!(cx.read(|cx| editor.is_dirty(cx)));
7495 let save = editor
7496 .update_in(cx, |editor, window, cx| {
7497 editor.save(true, project.clone(), window, cx)
7498 })
7499 .unwrap();
7500 fake_server
7501 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7502 assert_eq!(
7503 params.text_document.uri,
7504 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7505 );
7506 assert_eq!(params.options.tab_size, 8);
7507 Ok(Some(vec![]))
7508 })
7509 .next()
7510 .await;
7511 cx.executor().start_waiting();
7512 save.await;
7513}
7514
7515#[gpui::test]
7516async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7517 init_test(cx, |_| {});
7518
7519 let cols = 4;
7520 let rows = 10;
7521 let sample_text_1 = sample_text(rows, cols, 'a');
7522 assert_eq!(
7523 sample_text_1,
7524 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7525 );
7526 let sample_text_2 = sample_text(rows, cols, 'l');
7527 assert_eq!(
7528 sample_text_2,
7529 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7530 );
7531 let sample_text_3 = sample_text(rows, cols, 'v');
7532 assert_eq!(
7533 sample_text_3,
7534 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7535 );
7536
7537 let fs = FakeFs::new(cx.executor());
7538 fs.insert_tree(
7539 path!("/a"),
7540 json!({
7541 "main.rs": sample_text_1,
7542 "other.rs": sample_text_2,
7543 "lib.rs": sample_text_3,
7544 }),
7545 )
7546 .await;
7547
7548 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7549 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7550 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7551
7552 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7553 language_registry.add(rust_lang());
7554 let mut fake_servers = language_registry.register_fake_lsp(
7555 "Rust",
7556 FakeLspAdapter {
7557 capabilities: lsp::ServerCapabilities {
7558 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7559 ..Default::default()
7560 },
7561 ..Default::default()
7562 },
7563 );
7564
7565 let worktree = project.update(cx, |project, cx| {
7566 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7567 assert_eq!(worktrees.len(), 1);
7568 worktrees.pop().unwrap()
7569 });
7570 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7571
7572 let buffer_1 = project
7573 .update(cx, |project, cx| {
7574 project.open_buffer((worktree_id, "main.rs"), cx)
7575 })
7576 .await
7577 .unwrap();
7578 let buffer_2 = project
7579 .update(cx, |project, cx| {
7580 project.open_buffer((worktree_id, "other.rs"), cx)
7581 })
7582 .await
7583 .unwrap();
7584 let buffer_3 = project
7585 .update(cx, |project, cx| {
7586 project.open_buffer((worktree_id, "lib.rs"), cx)
7587 })
7588 .await
7589 .unwrap();
7590
7591 let multi_buffer = cx.new(|cx| {
7592 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7593 multi_buffer.push_excerpts(
7594 buffer_1.clone(),
7595 [
7596 ExcerptRange {
7597 context: Point::new(0, 0)..Point::new(3, 0),
7598 primary: None,
7599 },
7600 ExcerptRange {
7601 context: Point::new(5, 0)..Point::new(7, 0),
7602 primary: None,
7603 },
7604 ExcerptRange {
7605 context: Point::new(9, 0)..Point::new(10, 4),
7606 primary: None,
7607 },
7608 ],
7609 cx,
7610 );
7611 multi_buffer.push_excerpts(
7612 buffer_2.clone(),
7613 [
7614 ExcerptRange {
7615 context: Point::new(0, 0)..Point::new(3, 0),
7616 primary: None,
7617 },
7618 ExcerptRange {
7619 context: Point::new(5, 0)..Point::new(7, 0),
7620 primary: None,
7621 },
7622 ExcerptRange {
7623 context: Point::new(9, 0)..Point::new(10, 4),
7624 primary: None,
7625 },
7626 ],
7627 cx,
7628 );
7629 multi_buffer.push_excerpts(
7630 buffer_3.clone(),
7631 [
7632 ExcerptRange {
7633 context: Point::new(0, 0)..Point::new(3, 0),
7634 primary: None,
7635 },
7636 ExcerptRange {
7637 context: Point::new(5, 0)..Point::new(7, 0),
7638 primary: None,
7639 },
7640 ExcerptRange {
7641 context: Point::new(9, 0)..Point::new(10, 4),
7642 primary: None,
7643 },
7644 ],
7645 cx,
7646 );
7647 multi_buffer
7648 });
7649 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7650 Editor::new(
7651 EditorMode::Full,
7652 multi_buffer,
7653 Some(project.clone()),
7654 true,
7655 window,
7656 cx,
7657 )
7658 });
7659
7660 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7661 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7662 s.select_ranges(Some(1..2))
7663 });
7664 editor.insert("|one|two|three|", window, cx);
7665 });
7666 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7667 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7668 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7669 s.select_ranges(Some(60..70))
7670 });
7671 editor.insert("|four|five|six|", window, cx);
7672 });
7673 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7674
7675 // First two buffers should be edited, but not the third one.
7676 assert_eq!(
7677 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7678 "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}",
7679 );
7680 buffer_1.update(cx, |buffer, _| {
7681 assert!(buffer.is_dirty());
7682 assert_eq!(
7683 buffer.text(),
7684 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7685 )
7686 });
7687 buffer_2.update(cx, |buffer, _| {
7688 assert!(buffer.is_dirty());
7689 assert_eq!(
7690 buffer.text(),
7691 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7692 )
7693 });
7694 buffer_3.update(cx, |buffer, _| {
7695 assert!(!buffer.is_dirty());
7696 assert_eq!(buffer.text(), sample_text_3,)
7697 });
7698 cx.executor().run_until_parked();
7699
7700 cx.executor().start_waiting();
7701 let save = multi_buffer_editor
7702 .update_in(cx, |editor, window, cx| {
7703 editor.save(true, project.clone(), window, cx)
7704 })
7705 .unwrap();
7706
7707 let fake_server = fake_servers.next().await.unwrap();
7708 fake_server
7709 .server
7710 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7711 Ok(Some(vec![lsp::TextEdit::new(
7712 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7713 format!("[{} formatted]", params.text_document.uri),
7714 )]))
7715 })
7716 .detach();
7717 save.await;
7718
7719 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7720 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7721 assert_eq!(
7722 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7723 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}"),
7724 );
7725 buffer_1.update(cx, |buffer, _| {
7726 assert!(!buffer.is_dirty());
7727 assert_eq!(
7728 buffer.text(),
7729 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7730 )
7731 });
7732 buffer_2.update(cx, |buffer, _| {
7733 assert!(!buffer.is_dirty());
7734 assert_eq!(
7735 buffer.text(),
7736 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7737 )
7738 });
7739 buffer_3.update(cx, |buffer, _| {
7740 assert!(!buffer.is_dirty());
7741 assert_eq!(buffer.text(), sample_text_3,)
7742 });
7743}
7744
7745#[gpui::test]
7746async fn test_range_format_during_save(cx: &mut TestAppContext) {
7747 init_test(cx, |_| {});
7748
7749 let fs = FakeFs::new(cx.executor());
7750 fs.insert_file(path!("/file.rs"), Default::default()).await;
7751
7752 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7753
7754 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7755 language_registry.add(rust_lang());
7756 let mut fake_servers = language_registry.register_fake_lsp(
7757 "Rust",
7758 FakeLspAdapter {
7759 capabilities: lsp::ServerCapabilities {
7760 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7761 ..Default::default()
7762 },
7763 ..Default::default()
7764 },
7765 );
7766
7767 let buffer = project
7768 .update(cx, |project, cx| {
7769 project.open_local_buffer(path!("/file.rs"), cx)
7770 })
7771 .await
7772 .unwrap();
7773
7774 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7775 let (editor, cx) = cx.add_window_view(|window, cx| {
7776 build_editor_with_project(project.clone(), buffer, window, cx)
7777 });
7778 editor.update_in(cx, |editor, window, cx| {
7779 editor.set_text("one\ntwo\nthree\n", window, cx)
7780 });
7781 assert!(cx.read(|cx| editor.is_dirty(cx)));
7782
7783 cx.executor().start_waiting();
7784 let fake_server = fake_servers.next().await.unwrap();
7785
7786 let save = editor
7787 .update_in(cx, |editor, window, cx| {
7788 editor.save(true, project.clone(), window, cx)
7789 })
7790 .unwrap();
7791 fake_server
7792 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7793 assert_eq!(
7794 params.text_document.uri,
7795 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7796 );
7797 assert_eq!(params.options.tab_size, 4);
7798 Ok(Some(vec![lsp::TextEdit::new(
7799 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7800 ", ".to_string(),
7801 )]))
7802 })
7803 .next()
7804 .await;
7805 cx.executor().start_waiting();
7806 save.await;
7807 assert_eq!(
7808 editor.update(cx, |editor, cx| editor.text(cx)),
7809 "one, two\nthree\n"
7810 );
7811 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7812
7813 editor.update_in(cx, |editor, window, cx| {
7814 editor.set_text("one\ntwo\nthree\n", window, cx)
7815 });
7816 assert!(cx.read(|cx| editor.is_dirty(cx)));
7817
7818 // Ensure we can still save even if formatting hangs.
7819 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7820 move |params, _| async move {
7821 assert_eq!(
7822 params.text_document.uri,
7823 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7824 );
7825 futures::future::pending::<()>().await;
7826 unreachable!()
7827 },
7828 );
7829 let save = editor
7830 .update_in(cx, |editor, window, cx| {
7831 editor.save(true, project.clone(), window, cx)
7832 })
7833 .unwrap();
7834 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7835 cx.executor().start_waiting();
7836 save.await;
7837 assert_eq!(
7838 editor.update(cx, |editor, cx| editor.text(cx)),
7839 "one\ntwo\nthree\n"
7840 );
7841 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7842
7843 // For non-dirty buffer, no formatting request should be sent
7844 let save = editor
7845 .update_in(cx, |editor, window, cx| {
7846 editor.save(true, project.clone(), window, cx)
7847 })
7848 .unwrap();
7849 let _pending_format_request = fake_server
7850 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7851 panic!("Should not be invoked on non-dirty buffer");
7852 })
7853 .next();
7854 cx.executor().start_waiting();
7855 save.await;
7856
7857 // Set Rust language override and assert overridden tabsize is sent to language server
7858 update_test_language_settings(cx, |settings| {
7859 settings.languages.insert(
7860 "Rust".into(),
7861 LanguageSettingsContent {
7862 tab_size: NonZeroU32::new(8),
7863 ..Default::default()
7864 },
7865 );
7866 });
7867
7868 editor.update_in(cx, |editor, window, cx| {
7869 editor.set_text("somehting_new\n", window, cx)
7870 });
7871 assert!(cx.read(|cx| editor.is_dirty(cx)));
7872 let save = editor
7873 .update_in(cx, |editor, window, cx| {
7874 editor.save(true, project.clone(), window, cx)
7875 })
7876 .unwrap();
7877 fake_server
7878 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7879 assert_eq!(
7880 params.text_document.uri,
7881 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7882 );
7883 assert_eq!(params.options.tab_size, 8);
7884 Ok(Some(vec![]))
7885 })
7886 .next()
7887 .await;
7888 cx.executor().start_waiting();
7889 save.await;
7890}
7891
7892#[gpui::test]
7893async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7894 init_test(cx, |settings| {
7895 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7896 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7897 ))
7898 });
7899
7900 let fs = FakeFs::new(cx.executor());
7901 fs.insert_file(path!("/file.rs"), Default::default()).await;
7902
7903 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7904
7905 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7906 language_registry.add(Arc::new(Language::new(
7907 LanguageConfig {
7908 name: "Rust".into(),
7909 matcher: LanguageMatcher {
7910 path_suffixes: vec!["rs".to_string()],
7911 ..Default::default()
7912 },
7913 ..LanguageConfig::default()
7914 },
7915 Some(tree_sitter_rust::LANGUAGE.into()),
7916 )));
7917 update_test_language_settings(cx, |settings| {
7918 // Enable Prettier formatting for the same buffer, and ensure
7919 // LSP is called instead of Prettier.
7920 settings.defaults.prettier = Some(PrettierSettings {
7921 allowed: true,
7922 ..PrettierSettings::default()
7923 });
7924 });
7925 let mut fake_servers = language_registry.register_fake_lsp(
7926 "Rust",
7927 FakeLspAdapter {
7928 capabilities: lsp::ServerCapabilities {
7929 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7930 ..Default::default()
7931 },
7932 ..Default::default()
7933 },
7934 );
7935
7936 let buffer = project
7937 .update(cx, |project, cx| {
7938 project.open_local_buffer(path!("/file.rs"), cx)
7939 })
7940 .await
7941 .unwrap();
7942
7943 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7944 let (editor, cx) = cx.add_window_view(|window, cx| {
7945 build_editor_with_project(project.clone(), buffer, window, cx)
7946 });
7947 editor.update_in(cx, |editor, window, cx| {
7948 editor.set_text("one\ntwo\nthree\n", window, cx)
7949 });
7950
7951 cx.executor().start_waiting();
7952 let fake_server = fake_servers.next().await.unwrap();
7953
7954 let format = editor
7955 .update_in(cx, |editor, window, cx| {
7956 editor.perform_format(
7957 project.clone(),
7958 FormatTrigger::Manual,
7959 FormatTarget::Buffers,
7960 window,
7961 cx,
7962 )
7963 })
7964 .unwrap();
7965 fake_server
7966 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7967 assert_eq!(
7968 params.text_document.uri,
7969 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7970 );
7971 assert_eq!(params.options.tab_size, 4);
7972 Ok(Some(vec![lsp::TextEdit::new(
7973 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7974 ", ".to_string(),
7975 )]))
7976 })
7977 .next()
7978 .await;
7979 cx.executor().start_waiting();
7980 format.await;
7981 assert_eq!(
7982 editor.update(cx, |editor, cx| editor.text(cx)),
7983 "one, two\nthree\n"
7984 );
7985
7986 editor.update_in(cx, |editor, window, cx| {
7987 editor.set_text("one\ntwo\nthree\n", window, cx)
7988 });
7989 // Ensure we don't lock if formatting hangs.
7990 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7991 assert_eq!(
7992 params.text_document.uri,
7993 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7994 );
7995 futures::future::pending::<()>().await;
7996 unreachable!()
7997 });
7998 let format = editor
7999 .update_in(cx, |editor, window, cx| {
8000 editor.perform_format(
8001 project,
8002 FormatTrigger::Manual,
8003 FormatTarget::Buffers,
8004 window,
8005 cx,
8006 )
8007 })
8008 .unwrap();
8009 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8010 cx.executor().start_waiting();
8011 format.await;
8012 assert_eq!(
8013 editor.update(cx, |editor, cx| editor.text(cx)),
8014 "one\ntwo\nthree\n"
8015 );
8016}
8017
8018#[gpui::test]
8019async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8020 init_test(cx, |settings| {
8021 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8022 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8023 ))
8024 });
8025
8026 let fs = FakeFs::new(cx.executor());
8027 fs.insert_file(path!("/file.ts"), Default::default()).await;
8028
8029 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8030
8031 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8032 language_registry.add(Arc::new(Language::new(
8033 LanguageConfig {
8034 name: "TypeScript".into(),
8035 matcher: LanguageMatcher {
8036 path_suffixes: vec!["ts".to_string()],
8037 ..Default::default()
8038 },
8039 ..LanguageConfig::default()
8040 },
8041 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8042 )));
8043 update_test_language_settings(cx, |settings| {
8044 settings.defaults.prettier = Some(PrettierSettings {
8045 allowed: true,
8046 ..PrettierSettings::default()
8047 });
8048 });
8049 let mut fake_servers = language_registry.register_fake_lsp(
8050 "TypeScript",
8051 FakeLspAdapter {
8052 capabilities: lsp::ServerCapabilities {
8053 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8054 ..Default::default()
8055 },
8056 ..Default::default()
8057 },
8058 );
8059
8060 let buffer = project
8061 .update(cx, |project, cx| {
8062 project.open_local_buffer(path!("/file.ts"), cx)
8063 })
8064 .await
8065 .unwrap();
8066
8067 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8068 let (editor, cx) = cx.add_window_view(|window, cx| {
8069 build_editor_with_project(project.clone(), buffer, window, cx)
8070 });
8071 editor.update_in(cx, |editor, window, cx| {
8072 editor.set_text(
8073 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8074 window,
8075 cx,
8076 )
8077 });
8078
8079 cx.executor().start_waiting();
8080 let fake_server = fake_servers.next().await.unwrap();
8081
8082 let format = editor
8083 .update_in(cx, |editor, window, cx| {
8084 editor.perform_code_action_kind(
8085 project.clone(),
8086 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8087 window,
8088 cx,
8089 )
8090 })
8091 .unwrap();
8092 fake_server
8093 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8094 assert_eq!(
8095 params.text_document.uri,
8096 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8097 );
8098 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8099 lsp::CodeAction {
8100 title: "Organize Imports".to_string(),
8101 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8102 edit: Some(lsp::WorkspaceEdit {
8103 changes: Some(
8104 [(
8105 params.text_document.uri.clone(),
8106 vec![lsp::TextEdit::new(
8107 lsp::Range::new(
8108 lsp::Position::new(1, 0),
8109 lsp::Position::new(2, 0),
8110 ),
8111 "".to_string(),
8112 )],
8113 )]
8114 .into_iter()
8115 .collect(),
8116 ),
8117 ..Default::default()
8118 }),
8119 ..Default::default()
8120 },
8121 )]))
8122 })
8123 .next()
8124 .await;
8125 cx.executor().start_waiting();
8126 format.await;
8127 assert_eq!(
8128 editor.update(cx, |editor, cx| editor.text(cx)),
8129 "import { a } from 'module';\n\nconst x = a;\n"
8130 );
8131
8132 editor.update_in(cx, |editor, window, cx| {
8133 editor.set_text(
8134 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8135 window,
8136 cx,
8137 )
8138 });
8139 // Ensure we don't lock if code action hangs.
8140 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8141 move |params, _| async move {
8142 assert_eq!(
8143 params.text_document.uri,
8144 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8145 );
8146 futures::future::pending::<()>().await;
8147 unreachable!()
8148 },
8149 );
8150 let format = editor
8151 .update_in(cx, |editor, window, cx| {
8152 editor.perform_code_action_kind(
8153 project,
8154 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8155 window,
8156 cx,
8157 )
8158 })
8159 .unwrap();
8160 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8161 cx.executor().start_waiting();
8162 format.await;
8163 assert_eq!(
8164 editor.update(cx, |editor, cx| editor.text(cx)),
8165 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8166 );
8167}
8168
8169#[gpui::test]
8170async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8171 init_test(cx, |_| {});
8172
8173 let mut cx = EditorLspTestContext::new_rust(
8174 lsp::ServerCapabilities {
8175 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8176 ..Default::default()
8177 },
8178 cx,
8179 )
8180 .await;
8181
8182 cx.set_state(indoc! {"
8183 one.twoˇ
8184 "});
8185
8186 // The format request takes a long time. When it completes, it inserts
8187 // a newline and an indent before the `.`
8188 cx.lsp
8189 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8190 let executor = cx.background_executor().clone();
8191 async move {
8192 executor.timer(Duration::from_millis(100)).await;
8193 Ok(Some(vec![lsp::TextEdit {
8194 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8195 new_text: "\n ".into(),
8196 }]))
8197 }
8198 });
8199
8200 // Submit a format request.
8201 let format_1 = cx
8202 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8203 .unwrap();
8204 cx.executor().run_until_parked();
8205
8206 // Submit a second format request.
8207 let format_2 = cx
8208 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8209 .unwrap();
8210 cx.executor().run_until_parked();
8211
8212 // Wait for both format requests to complete
8213 cx.executor().advance_clock(Duration::from_millis(200));
8214 cx.executor().start_waiting();
8215 format_1.await.unwrap();
8216 cx.executor().start_waiting();
8217 format_2.await.unwrap();
8218
8219 // The formatting edits only happens once.
8220 cx.assert_editor_state(indoc! {"
8221 one
8222 .twoˇ
8223 "});
8224}
8225
8226#[gpui::test]
8227async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8228 init_test(cx, |settings| {
8229 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8230 });
8231
8232 let mut cx = EditorLspTestContext::new_rust(
8233 lsp::ServerCapabilities {
8234 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8235 ..Default::default()
8236 },
8237 cx,
8238 )
8239 .await;
8240
8241 // Set up a buffer white some trailing whitespace and no trailing newline.
8242 cx.set_state(
8243 &[
8244 "one ", //
8245 "twoˇ", //
8246 "three ", //
8247 "four", //
8248 ]
8249 .join("\n"),
8250 );
8251
8252 // Submit a format request.
8253 let format = cx
8254 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8255 .unwrap();
8256
8257 // Record which buffer changes have been sent to the language server
8258 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8259 cx.lsp
8260 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8261 let buffer_changes = buffer_changes.clone();
8262 move |params, _| {
8263 buffer_changes.lock().extend(
8264 params
8265 .content_changes
8266 .into_iter()
8267 .map(|e| (e.range.unwrap(), e.text)),
8268 );
8269 }
8270 });
8271
8272 // Handle formatting requests to the language server.
8273 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8274 let buffer_changes = buffer_changes.clone();
8275 move |_, _| {
8276 // When formatting is requested, trailing whitespace has already been stripped,
8277 // and the trailing newline has already been added.
8278 assert_eq!(
8279 &buffer_changes.lock()[1..],
8280 &[
8281 (
8282 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8283 "".into()
8284 ),
8285 (
8286 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8287 "".into()
8288 ),
8289 (
8290 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8291 "\n".into()
8292 ),
8293 ]
8294 );
8295
8296 // Insert blank lines between each line of the buffer.
8297 async move {
8298 Ok(Some(vec![
8299 lsp::TextEdit {
8300 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8301 new_text: "\n".into(),
8302 },
8303 lsp::TextEdit {
8304 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8305 new_text: "\n".into(),
8306 },
8307 ]))
8308 }
8309 }
8310 });
8311
8312 // After formatting the buffer, the trailing whitespace is stripped,
8313 // a newline is appended, and the edits provided by the language server
8314 // have been applied.
8315 format.await.unwrap();
8316 cx.assert_editor_state(
8317 &[
8318 "one", //
8319 "", //
8320 "twoˇ", //
8321 "", //
8322 "three", //
8323 "four", //
8324 "", //
8325 ]
8326 .join("\n"),
8327 );
8328
8329 // Undoing the formatting undoes the trailing whitespace removal, the
8330 // trailing newline, and the LSP edits.
8331 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8332 cx.assert_editor_state(
8333 &[
8334 "one ", //
8335 "twoˇ", //
8336 "three ", //
8337 "four", //
8338 ]
8339 .join("\n"),
8340 );
8341}
8342
8343#[gpui::test]
8344async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8345 cx: &mut TestAppContext,
8346) {
8347 init_test(cx, |_| {});
8348
8349 cx.update(|cx| {
8350 cx.update_global::<SettingsStore, _>(|settings, cx| {
8351 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8352 settings.auto_signature_help = Some(true);
8353 });
8354 });
8355 });
8356
8357 let mut cx = EditorLspTestContext::new_rust(
8358 lsp::ServerCapabilities {
8359 signature_help_provider: Some(lsp::SignatureHelpOptions {
8360 ..Default::default()
8361 }),
8362 ..Default::default()
8363 },
8364 cx,
8365 )
8366 .await;
8367
8368 let language = Language::new(
8369 LanguageConfig {
8370 name: "Rust".into(),
8371 brackets: BracketPairConfig {
8372 pairs: vec![
8373 BracketPair {
8374 start: "{".to_string(),
8375 end: "}".to_string(),
8376 close: true,
8377 surround: true,
8378 newline: true,
8379 },
8380 BracketPair {
8381 start: "(".to_string(),
8382 end: ")".to_string(),
8383 close: true,
8384 surround: true,
8385 newline: true,
8386 },
8387 BracketPair {
8388 start: "/*".to_string(),
8389 end: " */".to_string(),
8390 close: true,
8391 surround: true,
8392 newline: true,
8393 },
8394 BracketPair {
8395 start: "[".to_string(),
8396 end: "]".to_string(),
8397 close: false,
8398 surround: false,
8399 newline: true,
8400 },
8401 BracketPair {
8402 start: "\"".to_string(),
8403 end: "\"".to_string(),
8404 close: true,
8405 surround: true,
8406 newline: false,
8407 },
8408 BracketPair {
8409 start: "<".to_string(),
8410 end: ">".to_string(),
8411 close: false,
8412 surround: true,
8413 newline: true,
8414 },
8415 ],
8416 ..Default::default()
8417 },
8418 autoclose_before: "})]".to_string(),
8419 ..Default::default()
8420 },
8421 Some(tree_sitter_rust::LANGUAGE.into()),
8422 );
8423 let language = Arc::new(language);
8424
8425 cx.language_registry().add(language.clone());
8426 cx.update_buffer(|buffer, cx| {
8427 buffer.set_language(Some(language), cx);
8428 });
8429
8430 cx.set_state(
8431 &r#"
8432 fn main() {
8433 sampleˇ
8434 }
8435 "#
8436 .unindent(),
8437 );
8438
8439 cx.update_editor(|editor, window, cx| {
8440 editor.handle_input("(", window, cx);
8441 });
8442 cx.assert_editor_state(
8443 &"
8444 fn main() {
8445 sample(ˇ)
8446 }
8447 "
8448 .unindent(),
8449 );
8450
8451 let mocked_response = lsp::SignatureHelp {
8452 signatures: vec![lsp::SignatureInformation {
8453 label: "fn sample(param1: u8, param2: u8)".to_string(),
8454 documentation: None,
8455 parameters: Some(vec![
8456 lsp::ParameterInformation {
8457 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8458 documentation: None,
8459 },
8460 lsp::ParameterInformation {
8461 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8462 documentation: None,
8463 },
8464 ]),
8465 active_parameter: None,
8466 }],
8467 active_signature: Some(0),
8468 active_parameter: Some(0),
8469 };
8470 handle_signature_help_request(&mut cx, mocked_response).await;
8471
8472 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8473 .await;
8474
8475 cx.editor(|editor, _, _| {
8476 let signature_help_state = editor.signature_help_state.popover().cloned();
8477 assert_eq!(
8478 signature_help_state.unwrap().label,
8479 "param1: u8, param2: u8"
8480 );
8481 });
8482}
8483
8484#[gpui::test]
8485async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8486 init_test(cx, |_| {});
8487
8488 cx.update(|cx| {
8489 cx.update_global::<SettingsStore, _>(|settings, cx| {
8490 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8491 settings.auto_signature_help = Some(false);
8492 settings.show_signature_help_after_edits = Some(false);
8493 });
8494 });
8495 });
8496
8497 let mut cx = EditorLspTestContext::new_rust(
8498 lsp::ServerCapabilities {
8499 signature_help_provider: Some(lsp::SignatureHelpOptions {
8500 ..Default::default()
8501 }),
8502 ..Default::default()
8503 },
8504 cx,
8505 )
8506 .await;
8507
8508 let language = Language::new(
8509 LanguageConfig {
8510 name: "Rust".into(),
8511 brackets: BracketPairConfig {
8512 pairs: vec![
8513 BracketPair {
8514 start: "{".to_string(),
8515 end: "}".to_string(),
8516 close: true,
8517 surround: true,
8518 newline: true,
8519 },
8520 BracketPair {
8521 start: "(".to_string(),
8522 end: ")".to_string(),
8523 close: true,
8524 surround: true,
8525 newline: true,
8526 },
8527 BracketPair {
8528 start: "/*".to_string(),
8529 end: " */".to_string(),
8530 close: true,
8531 surround: true,
8532 newline: true,
8533 },
8534 BracketPair {
8535 start: "[".to_string(),
8536 end: "]".to_string(),
8537 close: false,
8538 surround: false,
8539 newline: true,
8540 },
8541 BracketPair {
8542 start: "\"".to_string(),
8543 end: "\"".to_string(),
8544 close: true,
8545 surround: true,
8546 newline: false,
8547 },
8548 BracketPair {
8549 start: "<".to_string(),
8550 end: ">".to_string(),
8551 close: false,
8552 surround: true,
8553 newline: true,
8554 },
8555 ],
8556 ..Default::default()
8557 },
8558 autoclose_before: "})]".to_string(),
8559 ..Default::default()
8560 },
8561 Some(tree_sitter_rust::LANGUAGE.into()),
8562 );
8563 let language = Arc::new(language);
8564
8565 cx.language_registry().add(language.clone());
8566 cx.update_buffer(|buffer, cx| {
8567 buffer.set_language(Some(language), cx);
8568 });
8569
8570 // Ensure that signature_help is not called when no signature help is enabled.
8571 cx.set_state(
8572 &r#"
8573 fn main() {
8574 sampleˇ
8575 }
8576 "#
8577 .unindent(),
8578 );
8579 cx.update_editor(|editor, window, cx| {
8580 editor.handle_input("(", window, cx);
8581 });
8582 cx.assert_editor_state(
8583 &"
8584 fn main() {
8585 sample(ˇ)
8586 }
8587 "
8588 .unindent(),
8589 );
8590 cx.editor(|editor, _, _| {
8591 assert!(editor.signature_help_state.task().is_none());
8592 });
8593
8594 let mocked_response = lsp::SignatureHelp {
8595 signatures: vec![lsp::SignatureInformation {
8596 label: "fn sample(param1: u8, param2: u8)".to_string(),
8597 documentation: None,
8598 parameters: Some(vec![
8599 lsp::ParameterInformation {
8600 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8601 documentation: None,
8602 },
8603 lsp::ParameterInformation {
8604 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8605 documentation: None,
8606 },
8607 ]),
8608 active_parameter: None,
8609 }],
8610 active_signature: Some(0),
8611 active_parameter: Some(0),
8612 };
8613
8614 // Ensure that signature_help is called when enabled afte edits
8615 cx.update(|_, cx| {
8616 cx.update_global::<SettingsStore, _>(|settings, cx| {
8617 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8618 settings.auto_signature_help = Some(false);
8619 settings.show_signature_help_after_edits = Some(true);
8620 });
8621 });
8622 });
8623 cx.set_state(
8624 &r#"
8625 fn main() {
8626 sampleˇ
8627 }
8628 "#
8629 .unindent(),
8630 );
8631 cx.update_editor(|editor, window, cx| {
8632 editor.handle_input("(", window, cx);
8633 });
8634 cx.assert_editor_state(
8635 &"
8636 fn main() {
8637 sample(ˇ)
8638 }
8639 "
8640 .unindent(),
8641 );
8642 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8643 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8644 .await;
8645 cx.update_editor(|editor, _, _| {
8646 let signature_help_state = editor.signature_help_state.popover().cloned();
8647 assert!(signature_help_state.is_some());
8648 assert_eq!(
8649 signature_help_state.unwrap().label,
8650 "param1: u8, param2: u8"
8651 );
8652 editor.signature_help_state = SignatureHelpState::default();
8653 });
8654
8655 // Ensure that signature_help is called when auto signature help override is enabled
8656 cx.update(|_, cx| {
8657 cx.update_global::<SettingsStore, _>(|settings, cx| {
8658 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8659 settings.auto_signature_help = Some(true);
8660 settings.show_signature_help_after_edits = Some(false);
8661 });
8662 });
8663 });
8664 cx.set_state(
8665 &r#"
8666 fn main() {
8667 sampleˇ
8668 }
8669 "#
8670 .unindent(),
8671 );
8672 cx.update_editor(|editor, window, cx| {
8673 editor.handle_input("(", window, cx);
8674 });
8675 cx.assert_editor_state(
8676 &"
8677 fn main() {
8678 sample(ˇ)
8679 }
8680 "
8681 .unindent(),
8682 );
8683 handle_signature_help_request(&mut cx, mocked_response).await;
8684 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8685 .await;
8686 cx.editor(|editor, _, _| {
8687 let signature_help_state = editor.signature_help_state.popover().cloned();
8688 assert!(signature_help_state.is_some());
8689 assert_eq!(
8690 signature_help_state.unwrap().label,
8691 "param1: u8, param2: u8"
8692 );
8693 });
8694}
8695
8696#[gpui::test]
8697async fn test_signature_help(cx: &mut TestAppContext) {
8698 init_test(cx, |_| {});
8699 cx.update(|cx| {
8700 cx.update_global::<SettingsStore, _>(|settings, cx| {
8701 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8702 settings.auto_signature_help = Some(true);
8703 });
8704 });
8705 });
8706
8707 let mut cx = EditorLspTestContext::new_rust(
8708 lsp::ServerCapabilities {
8709 signature_help_provider: Some(lsp::SignatureHelpOptions {
8710 ..Default::default()
8711 }),
8712 ..Default::default()
8713 },
8714 cx,
8715 )
8716 .await;
8717
8718 // A test that directly calls `show_signature_help`
8719 cx.update_editor(|editor, window, cx| {
8720 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8721 });
8722
8723 let mocked_response = lsp::SignatureHelp {
8724 signatures: vec![lsp::SignatureInformation {
8725 label: "fn sample(param1: u8, param2: u8)".to_string(),
8726 documentation: None,
8727 parameters: Some(vec![
8728 lsp::ParameterInformation {
8729 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8730 documentation: None,
8731 },
8732 lsp::ParameterInformation {
8733 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8734 documentation: None,
8735 },
8736 ]),
8737 active_parameter: None,
8738 }],
8739 active_signature: Some(0),
8740 active_parameter: Some(0),
8741 };
8742 handle_signature_help_request(&mut cx, mocked_response).await;
8743
8744 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8745 .await;
8746
8747 cx.editor(|editor, _, _| {
8748 let signature_help_state = editor.signature_help_state.popover().cloned();
8749 assert!(signature_help_state.is_some());
8750 assert_eq!(
8751 signature_help_state.unwrap().label,
8752 "param1: u8, param2: u8"
8753 );
8754 });
8755
8756 // When exiting outside from inside the brackets, `signature_help` is closed.
8757 cx.set_state(indoc! {"
8758 fn main() {
8759 sample(ˇ);
8760 }
8761
8762 fn sample(param1: u8, param2: u8) {}
8763 "});
8764
8765 cx.update_editor(|editor, window, cx| {
8766 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8767 });
8768
8769 let mocked_response = lsp::SignatureHelp {
8770 signatures: Vec::new(),
8771 active_signature: None,
8772 active_parameter: None,
8773 };
8774 handle_signature_help_request(&mut cx, mocked_response).await;
8775
8776 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8777 .await;
8778
8779 cx.editor(|editor, _, _| {
8780 assert!(!editor.signature_help_state.is_shown());
8781 });
8782
8783 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8784 cx.set_state(indoc! {"
8785 fn main() {
8786 sample(ˇ);
8787 }
8788
8789 fn sample(param1: u8, param2: u8) {}
8790 "});
8791
8792 let mocked_response = lsp::SignatureHelp {
8793 signatures: vec![lsp::SignatureInformation {
8794 label: "fn sample(param1: u8, param2: u8)".to_string(),
8795 documentation: None,
8796 parameters: Some(vec![
8797 lsp::ParameterInformation {
8798 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8799 documentation: None,
8800 },
8801 lsp::ParameterInformation {
8802 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8803 documentation: None,
8804 },
8805 ]),
8806 active_parameter: None,
8807 }],
8808 active_signature: Some(0),
8809 active_parameter: Some(0),
8810 };
8811 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8812 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8813 .await;
8814 cx.editor(|editor, _, _| {
8815 assert!(editor.signature_help_state.is_shown());
8816 });
8817
8818 // Restore the popover with more parameter input
8819 cx.set_state(indoc! {"
8820 fn main() {
8821 sample(param1, param2ˇ);
8822 }
8823
8824 fn sample(param1: u8, param2: u8) {}
8825 "});
8826
8827 let mocked_response = lsp::SignatureHelp {
8828 signatures: vec![lsp::SignatureInformation {
8829 label: "fn sample(param1: u8, param2: u8)".to_string(),
8830 documentation: None,
8831 parameters: Some(vec![
8832 lsp::ParameterInformation {
8833 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8834 documentation: None,
8835 },
8836 lsp::ParameterInformation {
8837 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8838 documentation: None,
8839 },
8840 ]),
8841 active_parameter: None,
8842 }],
8843 active_signature: Some(0),
8844 active_parameter: Some(1),
8845 };
8846 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8847 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8848 .await;
8849
8850 // When selecting a range, the popover is gone.
8851 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8852 cx.update_editor(|editor, window, cx| {
8853 editor.change_selections(None, window, cx, |s| {
8854 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8855 })
8856 });
8857 cx.assert_editor_state(indoc! {"
8858 fn main() {
8859 sample(param1, «ˇparam2»);
8860 }
8861
8862 fn sample(param1: u8, param2: u8) {}
8863 "});
8864 cx.editor(|editor, _, _| {
8865 assert!(!editor.signature_help_state.is_shown());
8866 });
8867
8868 // When unselecting again, the popover is back if within the brackets.
8869 cx.update_editor(|editor, window, cx| {
8870 editor.change_selections(None, window, cx, |s| {
8871 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8872 })
8873 });
8874 cx.assert_editor_state(indoc! {"
8875 fn main() {
8876 sample(param1, ˇparam2);
8877 }
8878
8879 fn sample(param1: u8, param2: u8) {}
8880 "});
8881 handle_signature_help_request(&mut cx, mocked_response).await;
8882 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8883 .await;
8884 cx.editor(|editor, _, _| {
8885 assert!(editor.signature_help_state.is_shown());
8886 });
8887
8888 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8889 cx.update_editor(|editor, window, cx| {
8890 editor.change_selections(None, window, cx, |s| {
8891 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8892 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8893 })
8894 });
8895 cx.assert_editor_state(indoc! {"
8896 fn main() {
8897 sample(param1, ˇparam2);
8898 }
8899
8900 fn sample(param1: u8, param2: u8) {}
8901 "});
8902
8903 let mocked_response = lsp::SignatureHelp {
8904 signatures: vec![lsp::SignatureInformation {
8905 label: "fn sample(param1: u8, param2: u8)".to_string(),
8906 documentation: None,
8907 parameters: Some(vec![
8908 lsp::ParameterInformation {
8909 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8910 documentation: None,
8911 },
8912 lsp::ParameterInformation {
8913 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8914 documentation: None,
8915 },
8916 ]),
8917 active_parameter: None,
8918 }],
8919 active_signature: Some(0),
8920 active_parameter: Some(1),
8921 };
8922 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8923 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8924 .await;
8925 cx.update_editor(|editor, _, cx| {
8926 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8927 });
8928 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8929 .await;
8930 cx.update_editor(|editor, window, cx| {
8931 editor.change_selections(None, window, cx, |s| {
8932 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8933 })
8934 });
8935 cx.assert_editor_state(indoc! {"
8936 fn main() {
8937 sample(param1, «ˇparam2»);
8938 }
8939
8940 fn sample(param1: u8, param2: u8) {}
8941 "});
8942 cx.update_editor(|editor, window, cx| {
8943 editor.change_selections(None, window, cx, |s| {
8944 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8945 })
8946 });
8947 cx.assert_editor_state(indoc! {"
8948 fn main() {
8949 sample(param1, ˇparam2);
8950 }
8951
8952 fn sample(param1: u8, param2: u8) {}
8953 "});
8954 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8955 .await;
8956}
8957
8958#[gpui::test]
8959async fn test_completion(cx: &mut TestAppContext) {
8960 init_test(cx, |_| {});
8961
8962 let mut cx = EditorLspTestContext::new_rust(
8963 lsp::ServerCapabilities {
8964 completion_provider: Some(lsp::CompletionOptions {
8965 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8966 resolve_provider: Some(true),
8967 ..Default::default()
8968 }),
8969 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8970 ..Default::default()
8971 },
8972 cx,
8973 )
8974 .await;
8975 let counter = Arc::new(AtomicUsize::new(0));
8976
8977 cx.set_state(indoc! {"
8978 oneˇ
8979 two
8980 three
8981 "});
8982 cx.simulate_keystroke(".");
8983 handle_completion_request(
8984 &mut cx,
8985 indoc! {"
8986 one.|<>
8987 two
8988 three
8989 "},
8990 vec!["first_completion", "second_completion"],
8991 counter.clone(),
8992 )
8993 .await;
8994 cx.condition(|editor, _| editor.context_menu_visible())
8995 .await;
8996 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8997
8998 let _handler = handle_signature_help_request(
8999 &mut cx,
9000 lsp::SignatureHelp {
9001 signatures: vec![lsp::SignatureInformation {
9002 label: "test signature".to_string(),
9003 documentation: None,
9004 parameters: Some(vec![lsp::ParameterInformation {
9005 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9006 documentation: None,
9007 }]),
9008 active_parameter: None,
9009 }],
9010 active_signature: None,
9011 active_parameter: None,
9012 },
9013 );
9014 cx.update_editor(|editor, window, cx| {
9015 assert!(
9016 !editor.signature_help_state.is_shown(),
9017 "No signature help was called for"
9018 );
9019 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9020 });
9021 cx.run_until_parked();
9022 cx.update_editor(|editor, _, _| {
9023 assert!(
9024 !editor.signature_help_state.is_shown(),
9025 "No signature help should be shown when completions menu is open"
9026 );
9027 });
9028
9029 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9030 editor.context_menu_next(&Default::default(), window, cx);
9031 editor
9032 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9033 .unwrap()
9034 });
9035 cx.assert_editor_state(indoc! {"
9036 one.second_completionˇ
9037 two
9038 three
9039 "});
9040
9041 handle_resolve_completion_request(
9042 &mut cx,
9043 Some(vec![
9044 (
9045 //This overlaps with the primary completion edit which is
9046 //misbehavior from the LSP spec, test that we filter it out
9047 indoc! {"
9048 one.second_ˇcompletion
9049 two
9050 threeˇ
9051 "},
9052 "overlapping additional edit",
9053 ),
9054 (
9055 indoc! {"
9056 one.second_completion
9057 two
9058 threeˇ
9059 "},
9060 "\nadditional edit",
9061 ),
9062 ]),
9063 )
9064 .await;
9065 apply_additional_edits.await.unwrap();
9066 cx.assert_editor_state(indoc! {"
9067 one.second_completionˇ
9068 two
9069 three
9070 additional edit
9071 "});
9072
9073 cx.set_state(indoc! {"
9074 one.second_completion
9075 twoˇ
9076 threeˇ
9077 additional edit
9078 "});
9079 cx.simulate_keystroke(" ");
9080 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9081 cx.simulate_keystroke("s");
9082 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9083
9084 cx.assert_editor_state(indoc! {"
9085 one.second_completion
9086 two sˇ
9087 three sˇ
9088 additional edit
9089 "});
9090 handle_completion_request(
9091 &mut cx,
9092 indoc! {"
9093 one.second_completion
9094 two s
9095 three <s|>
9096 additional edit
9097 "},
9098 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9099 counter.clone(),
9100 )
9101 .await;
9102 cx.condition(|editor, _| editor.context_menu_visible())
9103 .await;
9104 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9105
9106 cx.simulate_keystroke("i");
9107
9108 handle_completion_request(
9109 &mut cx,
9110 indoc! {"
9111 one.second_completion
9112 two si
9113 three <si|>
9114 additional edit
9115 "},
9116 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9117 counter.clone(),
9118 )
9119 .await;
9120 cx.condition(|editor, _| editor.context_menu_visible())
9121 .await;
9122 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9123
9124 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9125 editor
9126 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9127 .unwrap()
9128 });
9129 cx.assert_editor_state(indoc! {"
9130 one.second_completion
9131 two sixth_completionˇ
9132 three sixth_completionˇ
9133 additional edit
9134 "});
9135
9136 apply_additional_edits.await.unwrap();
9137
9138 update_test_language_settings(&mut cx, |settings| {
9139 settings.defaults.show_completions_on_input = Some(false);
9140 });
9141 cx.set_state("editorˇ");
9142 cx.simulate_keystroke(".");
9143 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9144 cx.simulate_keystroke("c");
9145 cx.simulate_keystroke("l");
9146 cx.simulate_keystroke("o");
9147 cx.assert_editor_state("editor.cloˇ");
9148 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9149 cx.update_editor(|editor, window, cx| {
9150 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9151 });
9152 handle_completion_request(
9153 &mut cx,
9154 "editor.<clo|>",
9155 vec!["close", "clobber"],
9156 counter.clone(),
9157 )
9158 .await;
9159 cx.condition(|editor, _| editor.context_menu_visible())
9160 .await;
9161 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9162
9163 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9164 editor
9165 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9166 .unwrap()
9167 });
9168 cx.assert_editor_state("editor.closeˇ");
9169 handle_resolve_completion_request(&mut cx, None).await;
9170 apply_additional_edits.await.unwrap();
9171}
9172
9173#[gpui::test]
9174async fn test_words_completion(cx: &mut TestAppContext) {
9175 let lsp_fetch_timeout_ms = 10;
9176 init_test(cx, |language_settings| {
9177 language_settings.defaults.completions = Some(CompletionSettings {
9178 words: WordsCompletionMode::Fallback,
9179 lsp: true,
9180 lsp_fetch_timeout_ms: 10,
9181 });
9182 });
9183
9184 let mut cx = EditorLspTestContext::new_rust(
9185 lsp::ServerCapabilities {
9186 completion_provider: Some(lsp::CompletionOptions {
9187 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9188 ..lsp::CompletionOptions::default()
9189 }),
9190 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9191 ..lsp::ServerCapabilities::default()
9192 },
9193 cx,
9194 )
9195 .await;
9196
9197 let throttle_completions = Arc::new(AtomicBool::new(false));
9198
9199 let lsp_throttle_completions = throttle_completions.clone();
9200 let _completion_requests_handler =
9201 cx.lsp
9202 .server
9203 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9204 let lsp_throttle_completions = lsp_throttle_completions.clone();
9205 async move {
9206 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9207 cx.background_executor()
9208 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9209 .await;
9210 }
9211 Ok(Some(lsp::CompletionResponse::Array(vec![
9212 lsp::CompletionItem {
9213 label: "first".into(),
9214 ..Default::default()
9215 },
9216 lsp::CompletionItem {
9217 label: "last".into(),
9218 ..Default::default()
9219 },
9220 ])))
9221 }
9222 });
9223
9224 cx.set_state(indoc! {"
9225 oneˇ
9226 two
9227 three
9228 "});
9229 cx.simulate_keystroke(".");
9230 cx.executor().run_until_parked();
9231 cx.condition(|editor, _| editor.context_menu_visible())
9232 .await;
9233 cx.update_editor(|editor, window, cx| {
9234 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9235 {
9236 assert_eq!(
9237 completion_menu_entries(&menu),
9238 &["first", "last"],
9239 "When LSP server is fast to reply, no fallback word completions are used"
9240 );
9241 } else {
9242 panic!("expected completion menu to be open");
9243 }
9244 editor.cancel(&Cancel, window, cx);
9245 });
9246 cx.executor().run_until_parked();
9247 cx.condition(|editor, _| !editor.context_menu_visible())
9248 .await;
9249
9250 throttle_completions.store(true, atomic::Ordering::Release);
9251 cx.simulate_keystroke(".");
9252 cx.executor()
9253 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9254 cx.executor().run_until_parked();
9255 cx.condition(|editor, _| editor.context_menu_visible())
9256 .await;
9257 cx.update_editor(|editor, _, _| {
9258 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9259 {
9260 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9261 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9262 } else {
9263 panic!("expected completion menu to be open");
9264 }
9265 });
9266}
9267
9268#[gpui::test]
9269async fn test_multiline_completion(cx: &mut TestAppContext) {
9270 init_test(cx, |_| {});
9271
9272 let fs = FakeFs::new(cx.executor());
9273 fs.insert_tree(
9274 path!("/a"),
9275 json!({
9276 "main.ts": "a",
9277 }),
9278 )
9279 .await;
9280
9281 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9282 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9283 let typescript_language = Arc::new(Language::new(
9284 LanguageConfig {
9285 name: "TypeScript".into(),
9286 matcher: LanguageMatcher {
9287 path_suffixes: vec!["ts".to_string()],
9288 ..LanguageMatcher::default()
9289 },
9290 line_comments: vec!["// ".into()],
9291 ..LanguageConfig::default()
9292 },
9293 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9294 ));
9295 language_registry.add(typescript_language.clone());
9296 let mut fake_servers = language_registry.register_fake_lsp(
9297 "TypeScript",
9298 FakeLspAdapter {
9299 capabilities: lsp::ServerCapabilities {
9300 completion_provider: Some(lsp::CompletionOptions {
9301 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9302 ..lsp::CompletionOptions::default()
9303 }),
9304 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9305 ..lsp::ServerCapabilities::default()
9306 },
9307 // Emulate vtsls label generation
9308 label_for_completion: Some(Box::new(|item, _| {
9309 let text = if let Some(description) = item
9310 .label_details
9311 .as_ref()
9312 .and_then(|label_details| label_details.description.as_ref())
9313 {
9314 format!("{} {}", item.label, description)
9315 } else if let Some(detail) = &item.detail {
9316 format!("{} {}", item.label, detail)
9317 } else {
9318 item.label.clone()
9319 };
9320 let len = text.len();
9321 Some(language::CodeLabel {
9322 text,
9323 runs: Vec::new(),
9324 filter_range: 0..len,
9325 })
9326 })),
9327 ..FakeLspAdapter::default()
9328 },
9329 );
9330 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9331 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9332 let worktree_id = workspace
9333 .update(cx, |workspace, _window, cx| {
9334 workspace.project().update(cx, |project, cx| {
9335 project.worktrees(cx).next().unwrap().read(cx).id()
9336 })
9337 })
9338 .unwrap();
9339 let _buffer = project
9340 .update(cx, |project, cx| {
9341 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9342 })
9343 .await
9344 .unwrap();
9345 let editor = workspace
9346 .update(cx, |workspace, window, cx| {
9347 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9348 })
9349 .unwrap()
9350 .await
9351 .unwrap()
9352 .downcast::<Editor>()
9353 .unwrap();
9354 let fake_server = fake_servers.next().await.unwrap();
9355
9356 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9357 let multiline_label_2 = "a\nb\nc\n";
9358 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9359 let multiline_description = "d\ne\nf\n";
9360 let multiline_detail_2 = "g\nh\ni\n";
9361
9362 let mut completion_handle =
9363 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9364 Ok(Some(lsp::CompletionResponse::Array(vec![
9365 lsp::CompletionItem {
9366 label: multiline_label.to_string(),
9367 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9368 range: lsp::Range {
9369 start: lsp::Position {
9370 line: params.text_document_position.position.line,
9371 character: params.text_document_position.position.character,
9372 },
9373 end: lsp::Position {
9374 line: params.text_document_position.position.line,
9375 character: params.text_document_position.position.character,
9376 },
9377 },
9378 new_text: "new_text_1".to_string(),
9379 })),
9380 ..lsp::CompletionItem::default()
9381 },
9382 lsp::CompletionItem {
9383 label: "single line label 1".to_string(),
9384 detail: Some(multiline_detail.to_string()),
9385 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9386 range: lsp::Range {
9387 start: lsp::Position {
9388 line: params.text_document_position.position.line,
9389 character: params.text_document_position.position.character,
9390 },
9391 end: lsp::Position {
9392 line: params.text_document_position.position.line,
9393 character: params.text_document_position.position.character,
9394 },
9395 },
9396 new_text: "new_text_2".to_string(),
9397 })),
9398 ..lsp::CompletionItem::default()
9399 },
9400 lsp::CompletionItem {
9401 label: "single line label 2".to_string(),
9402 label_details: Some(lsp::CompletionItemLabelDetails {
9403 description: Some(multiline_description.to_string()),
9404 detail: None,
9405 }),
9406 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9407 range: lsp::Range {
9408 start: lsp::Position {
9409 line: params.text_document_position.position.line,
9410 character: params.text_document_position.position.character,
9411 },
9412 end: lsp::Position {
9413 line: params.text_document_position.position.line,
9414 character: params.text_document_position.position.character,
9415 },
9416 },
9417 new_text: "new_text_2".to_string(),
9418 })),
9419 ..lsp::CompletionItem::default()
9420 },
9421 lsp::CompletionItem {
9422 label: multiline_label_2.to_string(),
9423 detail: Some(multiline_detail_2.to_string()),
9424 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9425 range: lsp::Range {
9426 start: lsp::Position {
9427 line: params.text_document_position.position.line,
9428 character: params.text_document_position.position.character,
9429 },
9430 end: lsp::Position {
9431 line: params.text_document_position.position.line,
9432 character: params.text_document_position.position.character,
9433 },
9434 },
9435 new_text: "new_text_3".to_string(),
9436 })),
9437 ..lsp::CompletionItem::default()
9438 },
9439 lsp::CompletionItem {
9440 label: "Label with many spaces and \t but without newlines".to_string(),
9441 detail: Some(
9442 "Details with many spaces and \t but without newlines".to_string(),
9443 ),
9444 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9445 range: lsp::Range {
9446 start: lsp::Position {
9447 line: params.text_document_position.position.line,
9448 character: params.text_document_position.position.character,
9449 },
9450 end: lsp::Position {
9451 line: params.text_document_position.position.line,
9452 character: params.text_document_position.position.character,
9453 },
9454 },
9455 new_text: "new_text_4".to_string(),
9456 })),
9457 ..lsp::CompletionItem::default()
9458 },
9459 ])))
9460 });
9461
9462 editor.update_in(cx, |editor, window, cx| {
9463 cx.focus_self(window);
9464 editor.move_to_end(&MoveToEnd, window, cx);
9465 editor.handle_input(".", window, cx);
9466 });
9467 cx.run_until_parked();
9468 completion_handle.next().await.unwrap();
9469
9470 editor.update(cx, |editor, _| {
9471 assert!(editor.context_menu_visible());
9472 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9473 {
9474 let completion_labels = menu
9475 .completions
9476 .borrow()
9477 .iter()
9478 .map(|c| c.label.text.clone())
9479 .collect::<Vec<_>>();
9480 assert_eq!(
9481 completion_labels,
9482 &[
9483 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9484 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9485 "single line label 2 d e f ",
9486 "a b c g h i ",
9487 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9488 ],
9489 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9490 );
9491
9492 for completion in menu
9493 .completions
9494 .borrow()
9495 .iter() {
9496 assert_eq!(
9497 completion.label.filter_range,
9498 0..completion.label.text.len(),
9499 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9500 );
9501 }
9502
9503 } else {
9504 panic!("expected completion menu to be open");
9505 }
9506 });
9507}
9508
9509#[gpui::test]
9510async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9511 init_test(cx, |_| {});
9512 let mut cx = EditorLspTestContext::new_rust(
9513 lsp::ServerCapabilities {
9514 completion_provider: Some(lsp::CompletionOptions {
9515 trigger_characters: Some(vec![".".to_string()]),
9516 ..Default::default()
9517 }),
9518 ..Default::default()
9519 },
9520 cx,
9521 )
9522 .await;
9523 cx.lsp
9524 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9525 Ok(Some(lsp::CompletionResponse::Array(vec![
9526 lsp::CompletionItem {
9527 label: "first".into(),
9528 ..Default::default()
9529 },
9530 lsp::CompletionItem {
9531 label: "last".into(),
9532 ..Default::default()
9533 },
9534 ])))
9535 });
9536 cx.set_state("variableˇ");
9537 cx.simulate_keystroke(".");
9538 cx.executor().run_until_parked();
9539
9540 cx.update_editor(|editor, _, _| {
9541 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9542 {
9543 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9544 } else {
9545 panic!("expected completion menu to be open");
9546 }
9547 });
9548
9549 cx.update_editor(|editor, window, cx| {
9550 editor.move_page_down(&MovePageDown::default(), window, cx);
9551 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9552 {
9553 assert!(
9554 menu.selected_item == 1,
9555 "expected PageDown to select the last item from the context menu"
9556 );
9557 } else {
9558 panic!("expected completion menu to stay open after PageDown");
9559 }
9560 });
9561
9562 cx.update_editor(|editor, window, cx| {
9563 editor.move_page_up(&MovePageUp::default(), window, cx);
9564 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9565 {
9566 assert!(
9567 menu.selected_item == 0,
9568 "expected PageUp to select the first item from the context menu"
9569 );
9570 } else {
9571 panic!("expected completion menu to stay open after PageUp");
9572 }
9573 });
9574}
9575
9576#[gpui::test]
9577async fn test_completion_sort(cx: &mut TestAppContext) {
9578 init_test(cx, |_| {});
9579 let mut cx = EditorLspTestContext::new_rust(
9580 lsp::ServerCapabilities {
9581 completion_provider: Some(lsp::CompletionOptions {
9582 trigger_characters: Some(vec![".".to_string()]),
9583 ..Default::default()
9584 }),
9585 ..Default::default()
9586 },
9587 cx,
9588 )
9589 .await;
9590 cx.lsp
9591 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9592 Ok(Some(lsp::CompletionResponse::Array(vec![
9593 lsp::CompletionItem {
9594 label: "Range".into(),
9595 sort_text: Some("a".into()),
9596 ..Default::default()
9597 },
9598 lsp::CompletionItem {
9599 label: "r".into(),
9600 sort_text: Some("b".into()),
9601 ..Default::default()
9602 },
9603 lsp::CompletionItem {
9604 label: "ret".into(),
9605 sort_text: Some("c".into()),
9606 ..Default::default()
9607 },
9608 lsp::CompletionItem {
9609 label: "return".into(),
9610 sort_text: Some("d".into()),
9611 ..Default::default()
9612 },
9613 lsp::CompletionItem {
9614 label: "slice".into(),
9615 sort_text: Some("d".into()),
9616 ..Default::default()
9617 },
9618 ])))
9619 });
9620 cx.set_state("rˇ");
9621 cx.executor().run_until_parked();
9622 cx.update_editor(|editor, window, cx| {
9623 editor.show_completions(
9624 &ShowCompletions {
9625 trigger: Some("r".into()),
9626 },
9627 window,
9628 cx,
9629 );
9630 });
9631 cx.executor().run_until_parked();
9632
9633 cx.update_editor(|editor, _, _| {
9634 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9635 {
9636 assert_eq!(
9637 completion_menu_entries(&menu),
9638 &["r", "ret", "Range", "return"]
9639 );
9640 } else {
9641 panic!("expected completion menu to be open");
9642 }
9643 });
9644}
9645
9646#[gpui::test]
9647async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9648 init_test(cx, |_| {});
9649
9650 let mut cx = EditorLspTestContext::new_rust(
9651 lsp::ServerCapabilities {
9652 completion_provider: Some(lsp::CompletionOptions {
9653 trigger_characters: Some(vec![".".to_string()]),
9654 resolve_provider: Some(true),
9655 ..Default::default()
9656 }),
9657 ..Default::default()
9658 },
9659 cx,
9660 )
9661 .await;
9662
9663 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9664 cx.simulate_keystroke(".");
9665 let completion_item = lsp::CompletionItem {
9666 label: "Some".into(),
9667 kind: Some(lsp::CompletionItemKind::SNIPPET),
9668 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9669 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9670 kind: lsp::MarkupKind::Markdown,
9671 value: "```rust\nSome(2)\n```".to_string(),
9672 })),
9673 deprecated: Some(false),
9674 sort_text: Some("Some".to_string()),
9675 filter_text: Some("Some".to_string()),
9676 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9677 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9678 range: lsp::Range {
9679 start: lsp::Position {
9680 line: 0,
9681 character: 22,
9682 },
9683 end: lsp::Position {
9684 line: 0,
9685 character: 22,
9686 },
9687 },
9688 new_text: "Some(2)".to_string(),
9689 })),
9690 additional_text_edits: Some(vec![lsp::TextEdit {
9691 range: lsp::Range {
9692 start: lsp::Position {
9693 line: 0,
9694 character: 20,
9695 },
9696 end: lsp::Position {
9697 line: 0,
9698 character: 22,
9699 },
9700 },
9701 new_text: "".to_string(),
9702 }]),
9703 ..Default::default()
9704 };
9705
9706 let closure_completion_item = completion_item.clone();
9707 let counter = Arc::new(AtomicUsize::new(0));
9708 let counter_clone = counter.clone();
9709 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9710 let task_completion_item = closure_completion_item.clone();
9711 counter_clone.fetch_add(1, atomic::Ordering::Release);
9712 async move {
9713 Ok(Some(lsp::CompletionResponse::Array(vec![
9714 task_completion_item,
9715 ])))
9716 }
9717 });
9718
9719 cx.condition(|editor, _| editor.context_menu_visible())
9720 .await;
9721 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9722 assert!(request.next().await.is_some());
9723 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9724
9725 cx.simulate_keystroke("S");
9726 cx.simulate_keystroke("o");
9727 cx.simulate_keystroke("m");
9728 cx.condition(|editor, _| editor.context_menu_visible())
9729 .await;
9730 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9731 assert!(request.next().await.is_some());
9732 assert!(request.next().await.is_some());
9733 assert!(request.next().await.is_some());
9734 request.close();
9735 assert!(request.next().await.is_none());
9736 assert_eq!(
9737 counter.load(atomic::Ordering::Acquire),
9738 4,
9739 "With the completions menu open, only one LSP request should happen per input"
9740 );
9741}
9742
9743#[gpui::test]
9744async fn test_toggle_comment(cx: &mut TestAppContext) {
9745 init_test(cx, |_| {});
9746 let mut cx = EditorTestContext::new(cx).await;
9747 let language = Arc::new(Language::new(
9748 LanguageConfig {
9749 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9750 ..Default::default()
9751 },
9752 Some(tree_sitter_rust::LANGUAGE.into()),
9753 ));
9754 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9755
9756 // If multiple selections intersect a line, the line is only toggled once.
9757 cx.set_state(indoc! {"
9758 fn a() {
9759 «//b();
9760 ˇ»// «c();
9761 //ˇ» d();
9762 }
9763 "});
9764
9765 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9766
9767 cx.assert_editor_state(indoc! {"
9768 fn a() {
9769 «b();
9770 c();
9771 ˇ» d();
9772 }
9773 "});
9774
9775 // The comment prefix is inserted at the same column for every line in a
9776 // selection.
9777 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9778
9779 cx.assert_editor_state(indoc! {"
9780 fn a() {
9781 // «b();
9782 // c();
9783 ˇ»// d();
9784 }
9785 "});
9786
9787 // If a selection ends at the beginning of a line, that line is not toggled.
9788 cx.set_selections_state(indoc! {"
9789 fn a() {
9790 // b();
9791 «// c();
9792 ˇ» // d();
9793 }
9794 "});
9795
9796 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9797
9798 cx.assert_editor_state(indoc! {"
9799 fn a() {
9800 // b();
9801 «c();
9802 ˇ» // d();
9803 }
9804 "});
9805
9806 // If a selection span a single line and is empty, the line is toggled.
9807 cx.set_state(indoc! {"
9808 fn a() {
9809 a();
9810 b();
9811 ˇ
9812 }
9813 "});
9814
9815 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9816
9817 cx.assert_editor_state(indoc! {"
9818 fn a() {
9819 a();
9820 b();
9821 //•ˇ
9822 }
9823 "});
9824
9825 // If a selection span multiple lines, empty lines are not toggled.
9826 cx.set_state(indoc! {"
9827 fn a() {
9828 «a();
9829
9830 c();ˇ»
9831 }
9832 "});
9833
9834 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9835
9836 cx.assert_editor_state(indoc! {"
9837 fn a() {
9838 // «a();
9839
9840 // c();ˇ»
9841 }
9842 "});
9843
9844 // If a selection includes multiple comment prefixes, all lines are uncommented.
9845 cx.set_state(indoc! {"
9846 fn a() {
9847 «// a();
9848 /// b();
9849 //! c();ˇ»
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 «a();
9858 b();
9859 c();ˇ»
9860 }
9861 "});
9862}
9863
9864#[gpui::test]
9865async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9866 init_test(cx, |_| {});
9867 let mut cx = EditorTestContext::new(cx).await;
9868 let language = Arc::new(Language::new(
9869 LanguageConfig {
9870 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9871 ..Default::default()
9872 },
9873 Some(tree_sitter_rust::LANGUAGE.into()),
9874 ));
9875 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9876
9877 let toggle_comments = &ToggleComments {
9878 advance_downwards: false,
9879 ignore_indent: true,
9880 };
9881
9882 // If multiple selections intersect a line, the line is only toggled once.
9883 cx.set_state(indoc! {"
9884 fn a() {
9885 // «b();
9886 // c();
9887 // ˇ» d();
9888 }
9889 "});
9890
9891 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9892
9893 cx.assert_editor_state(indoc! {"
9894 fn a() {
9895 «b();
9896 c();
9897 ˇ» d();
9898 }
9899 "});
9900
9901 // The comment prefix is inserted at the beginning of each line
9902 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9903
9904 cx.assert_editor_state(indoc! {"
9905 fn a() {
9906 // «b();
9907 // c();
9908 // ˇ» d();
9909 }
9910 "});
9911
9912 // If a selection ends at the beginning of a line, that line is not toggled.
9913 cx.set_selections_state(indoc! {"
9914 fn a() {
9915 // b();
9916 // «c();
9917 ˇ»// d();
9918 }
9919 "});
9920
9921 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9922
9923 cx.assert_editor_state(indoc! {"
9924 fn a() {
9925 // b();
9926 «c();
9927 ˇ»// d();
9928 }
9929 "});
9930
9931 // If a selection span a single line and is empty, the line is toggled.
9932 cx.set_state(indoc! {"
9933 fn a() {
9934 a();
9935 b();
9936 ˇ
9937 }
9938 "});
9939
9940 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9941
9942 cx.assert_editor_state(indoc! {"
9943 fn a() {
9944 a();
9945 b();
9946 //ˇ
9947 }
9948 "});
9949
9950 // If a selection span multiple lines, empty lines are not toggled.
9951 cx.set_state(indoc! {"
9952 fn a() {
9953 «a();
9954
9955 c();ˇ»
9956 }
9957 "});
9958
9959 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9960
9961 cx.assert_editor_state(indoc! {"
9962 fn a() {
9963 // «a();
9964
9965 // c();ˇ»
9966 }
9967 "});
9968
9969 // If a selection includes multiple comment prefixes, all lines are uncommented.
9970 cx.set_state(indoc! {"
9971 fn a() {
9972 // «a();
9973 /// b();
9974 //! c();ˇ»
9975 }
9976 "});
9977
9978 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9979
9980 cx.assert_editor_state(indoc! {"
9981 fn a() {
9982 «a();
9983 b();
9984 c();ˇ»
9985 }
9986 "});
9987}
9988
9989#[gpui::test]
9990async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9991 init_test(cx, |_| {});
9992
9993 let language = Arc::new(Language::new(
9994 LanguageConfig {
9995 line_comments: vec!["// ".into()],
9996 ..Default::default()
9997 },
9998 Some(tree_sitter_rust::LANGUAGE.into()),
9999 ));
10000
10001 let mut cx = EditorTestContext::new(cx).await;
10002
10003 cx.language_registry().add(language.clone());
10004 cx.update_buffer(|buffer, cx| {
10005 buffer.set_language(Some(language), cx);
10006 });
10007
10008 let toggle_comments = &ToggleComments {
10009 advance_downwards: true,
10010 ignore_indent: false,
10011 };
10012
10013 // Single cursor on one line -> advance
10014 // Cursor moves horizontally 3 characters as well on non-blank line
10015 cx.set_state(indoc!(
10016 "fn a() {
10017 ˇdog();
10018 cat();
10019 }"
10020 ));
10021 cx.update_editor(|editor, window, cx| {
10022 editor.toggle_comments(toggle_comments, window, cx);
10023 });
10024 cx.assert_editor_state(indoc!(
10025 "fn a() {
10026 // dog();
10027 catˇ();
10028 }"
10029 ));
10030
10031 // Single selection on one line -> don't advance
10032 cx.set_state(indoc!(
10033 "fn a() {
10034 «dog()ˇ»;
10035 cat();
10036 }"
10037 ));
10038 cx.update_editor(|editor, window, cx| {
10039 editor.toggle_comments(toggle_comments, window, cx);
10040 });
10041 cx.assert_editor_state(indoc!(
10042 "fn a() {
10043 // «dog()ˇ»;
10044 cat();
10045 }"
10046 ));
10047
10048 // Multiple cursors on one line -> advance
10049 cx.set_state(indoc!(
10050 "fn a() {
10051 ˇdˇog();
10052 cat();
10053 }"
10054 ));
10055 cx.update_editor(|editor, window, cx| {
10056 editor.toggle_comments(toggle_comments, window, cx);
10057 });
10058 cx.assert_editor_state(indoc!(
10059 "fn a() {
10060 // dog();
10061 catˇ(ˇ);
10062 }"
10063 ));
10064
10065 // Multiple cursors on one line, with selection -> don't advance
10066 cx.set_state(indoc!(
10067 "fn a() {
10068 ˇdˇog«()ˇ»;
10069 cat();
10070 }"
10071 ));
10072 cx.update_editor(|editor, window, cx| {
10073 editor.toggle_comments(toggle_comments, window, cx);
10074 });
10075 cx.assert_editor_state(indoc!(
10076 "fn a() {
10077 // ˇdˇog«()ˇ»;
10078 cat();
10079 }"
10080 ));
10081
10082 // Single cursor on one line -> advance
10083 // Cursor moves to column 0 on blank line
10084 cx.set_state(indoc!(
10085 "fn a() {
10086 ˇdog();
10087
10088 cat();
10089 }"
10090 ));
10091 cx.update_editor(|editor, window, cx| {
10092 editor.toggle_comments(toggle_comments, window, cx);
10093 });
10094 cx.assert_editor_state(indoc!(
10095 "fn a() {
10096 // dog();
10097 ˇ
10098 cat();
10099 }"
10100 ));
10101
10102 // Single cursor on one line -> advance
10103 // Cursor starts and ends at column 0
10104 cx.set_state(indoc!(
10105 "fn a() {
10106 ˇ dog();
10107 cat();
10108 }"
10109 ));
10110 cx.update_editor(|editor, window, cx| {
10111 editor.toggle_comments(toggle_comments, window, cx);
10112 });
10113 cx.assert_editor_state(indoc!(
10114 "fn a() {
10115 // dog();
10116 ˇ cat();
10117 }"
10118 ));
10119}
10120
10121#[gpui::test]
10122async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10123 init_test(cx, |_| {});
10124
10125 let mut cx = EditorTestContext::new(cx).await;
10126
10127 let html_language = Arc::new(
10128 Language::new(
10129 LanguageConfig {
10130 name: "HTML".into(),
10131 block_comment: Some(("<!-- ".into(), " -->".into())),
10132 ..Default::default()
10133 },
10134 Some(tree_sitter_html::LANGUAGE.into()),
10135 )
10136 .with_injection_query(
10137 r#"
10138 (script_element
10139 (raw_text) @injection.content
10140 (#set! injection.language "javascript"))
10141 "#,
10142 )
10143 .unwrap(),
10144 );
10145
10146 let javascript_language = Arc::new(Language::new(
10147 LanguageConfig {
10148 name: "JavaScript".into(),
10149 line_comments: vec!["// ".into()],
10150 ..Default::default()
10151 },
10152 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10153 ));
10154
10155 cx.language_registry().add(html_language.clone());
10156 cx.language_registry().add(javascript_language.clone());
10157 cx.update_buffer(|buffer, cx| {
10158 buffer.set_language(Some(html_language), cx);
10159 });
10160
10161 // Toggle comments for empty selections
10162 cx.set_state(
10163 &r#"
10164 <p>A</p>ˇ
10165 <p>B</p>ˇ
10166 <p>C</p>ˇ
10167 "#
10168 .unindent(),
10169 );
10170 cx.update_editor(|editor, window, cx| {
10171 editor.toggle_comments(&ToggleComments::default(), window, cx)
10172 });
10173 cx.assert_editor_state(
10174 &r#"
10175 <!-- <p>A</p>ˇ -->
10176 <!-- <p>B</p>ˇ -->
10177 <!-- <p>C</p>ˇ -->
10178 "#
10179 .unindent(),
10180 );
10181 cx.update_editor(|editor, window, cx| {
10182 editor.toggle_comments(&ToggleComments::default(), window, cx)
10183 });
10184 cx.assert_editor_state(
10185 &r#"
10186 <p>A</p>ˇ
10187 <p>B</p>ˇ
10188 <p>C</p>ˇ
10189 "#
10190 .unindent(),
10191 );
10192
10193 // Toggle comments for mixture of empty and non-empty selections, where
10194 // multiple selections occupy a given line.
10195 cx.set_state(
10196 &r#"
10197 <p>A«</p>
10198 <p>ˇ»B</p>ˇ
10199 <p>C«</p>
10200 <p>ˇ»D</p>ˇ
10201 "#
10202 .unindent(),
10203 );
10204
10205 cx.update_editor(|editor, window, cx| {
10206 editor.toggle_comments(&ToggleComments::default(), window, cx)
10207 });
10208 cx.assert_editor_state(
10209 &r#"
10210 <!-- <p>A«</p>
10211 <p>ˇ»B</p>ˇ -->
10212 <!-- <p>C«</p>
10213 <p>ˇ»D</p>ˇ -->
10214 "#
10215 .unindent(),
10216 );
10217 cx.update_editor(|editor, window, cx| {
10218 editor.toggle_comments(&ToggleComments::default(), window, cx)
10219 });
10220 cx.assert_editor_state(
10221 &r#"
10222 <p>A«</p>
10223 <p>ˇ»B</p>ˇ
10224 <p>C«</p>
10225 <p>ˇ»D</p>ˇ
10226 "#
10227 .unindent(),
10228 );
10229
10230 // Toggle comments when different languages are active for different
10231 // selections.
10232 cx.set_state(
10233 &r#"
10234 ˇ<script>
10235 ˇvar x = new Y();
10236 ˇ</script>
10237 "#
10238 .unindent(),
10239 );
10240 cx.executor().run_until_parked();
10241 cx.update_editor(|editor, window, cx| {
10242 editor.toggle_comments(&ToggleComments::default(), window, cx)
10243 });
10244 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10245 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10246 cx.assert_editor_state(
10247 &r#"
10248 <!-- ˇ<script> -->
10249 // ˇvar x = new Y();
10250 <!-- ˇ</script> -->
10251 "#
10252 .unindent(),
10253 );
10254}
10255
10256#[gpui::test]
10257fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10258 init_test(cx, |_| {});
10259
10260 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10261 let multibuffer = cx.new(|cx| {
10262 let mut multibuffer = MultiBuffer::new(ReadWrite);
10263 multibuffer.push_excerpts(
10264 buffer.clone(),
10265 [
10266 ExcerptRange {
10267 context: Point::new(0, 0)..Point::new(0, 4),
10268 primary: None,
10269 },
10270 ExcerptRange {
10271 context: Point::new(1, 0)..Point::new(1, 4),
10272 primary: None,
10273 },
10274 ],
10275 cx,
10276 );
10277 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10278 multibuffer
10279 });
10280
10281 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10282 editor.update_in(cx, |editor, window, cx| {
10283 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10284 editor.change_selections(None, window, cx, |s| {
10285 s.select_ranges([
10286 Point::new(0, 0)..Point::new(0, 0),
10287 Point::new(1, 0)..Point::new(1, 0),
10288 ])
10289 });
10290
10291 editor.handle_input("X", window, cx);
10292 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10293 assert_eq!(
10294 editor.selections.ranges(cx),
10295 [
10296 Point::new(0, 1)..Point::new(0, 1),
10297 Point::new(1, 1)..Point::new(1, 1),
10298 ]
10299 );
10300
10301 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10302 editor.change_selections(None, window, cx, |s| {
10303 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10304 });
10305 editor.backspace(&Default::default(), window, cx);
10306 assert_eq!(editor.text(cx), "Xa\nbbb");
10307 assert_eq!(
10308 editor.selections.ranges(cx),
10309 [Point::new(1, 0)..Point::new(1, 0)]
10310 );
10311
10312 editor.change_selections(None, window, cx, |s| {
10313 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10314 });
10315 editor.backspace(&Default::default(), window, cx);
10316 assert_eq!(editor.text(cx), "X\nbb");
10317 assert_eq!(
10318 editor.selections.ranges(cx),
10319 [Point::new(0, 1)..Point::new(0, 1)]
10320 );
10321 });
10322}
10323
10324#[gpui::test]
10325fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10326 init_test(cx, |_| {});
10327
10328 let markers = vec![('[', ']').into(), ('(', ')').into()];
10329 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10330 indoc! {"
10331 [aaaa
10332 (bbbb]
10333 cccc)",
10334 },
10335 markers.clone(),
10336 );
10337 let excerpt_ranges = markers.into_iter().map(|marker| {
10338 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10339 ExcerptRange {
10340 context,
10341 primary: None,
10342 }
10343 });
10344 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10345 let multibuffer = cx.new(|cx| {
10346 let mut multibuffer = MultiBuffer::new(ReadWrite);
10347 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10348 multibuffer
10349 });
10350
10351 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10352 editor.update_in(cx, |editor, window, cx| {
10353 let (expected_text, selection_ranges) = marked_text_ranges(
10354 indoc! {"
10355 aaaa
10356 bˇbbb
10357 bˇbbˇb
10358 cccc"
10359 },
10360 true,
10361 );
10362 assert_eq!(editor.text(cx), expected_text);
10363 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10364
10365 editor.handle_input("X", window, cx);
10366
10367 let (expected_text, expected_selections) = marked_text_ranges(
10368 indoc! {"
10369 aaaa
10370 bXˇbbXb
10371 bXˇbbXˇb
10372 cccc"
10373 },
10374 false,
10375 );
10376 assert_eq!(editor.text(cx), expected_text);
10377 assert_eq!(editor.selections.ranges(cx), expected_selections);
10378
10379 editor.newline(&Newline, window, cx);
10380 let (expected_text, expected_selections) = marked_text_ranges(
10381 indoc! {"
10382 aaaa
10383 bX
10384 ˇbbX
10385 b
10386 bX
10387 ˇbbX
10388 ˇb
10389 cccc"
10390 },
10391 false,
10392 );
10393 assert_eq!(editor.text(cx), expected_text);
10394 assert_eq!(editor.selections.ranges(cx), expected_selections);
10395 });
10396}
10397
10398#[gpui::test]
10399fn test_refresh_selections(cx: &mut TestAppContext) {
10400 init_test(cx, |_| {});
10401
10402 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10403 let mut excerpt1_id = None;
10404 let multibuffer = cx.new(|cx| {
10405 let mut multibuffer = MultiBuffer::new(ReadWrite);
10406 excerpt1_id = multibuffer
10407 .push_excerpts(
10408 buffer.clone(),
10409 [
10410 ExcerptRange {
10411 context: Point::new(0, 0)..Point::new(1, 4),
10412 primary: None,
10413 },
10414 ExcerptRange {
10415 context: Point::new(1, 0)..Point::new(2, 4),
10416 primary: None,
10417 },
10418 ],
10419 cx,
10420 )
10421 .into_iter()
10422 .next();
10423 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10424 multibuffer
10425 });
10426
10427 let editor = cx.add_window(|window, cx| {
10428 let mut editor = build_editor(multibuffer.clone(), window, cx);
10429 let snapshot = editor.snapshot(window, cx);
10430 editor.change_selections(None, window, cx, |s| {
10431 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10432 });
10433 editor.begin_selection(
10434 Point::new(2, 1).to_display_point(&snapshot),
10435 true,
10436 1,
10437 window,
10438 cx,
10439 );
10440 assert_eq!(
10441 editor.selections.ranges(cx),
10442 [
10443 Point::new(1, 3)..Point::new(1, 3),
10444 Point::new(2, 1)..Point::new(2, 1),
10445 ]
10446 );
10447 editor
10448 });
10449
10450 // Refreshing selections is a no-op when excerpts haven't changed.
10451 _ = editor.update(cx, |editor, window, cx| {
10452 editor.change_selections(None, window, cx, |s| s.refresh());
10453 assert_eq!(
10454 editor.selections.ranges(cx),
10455 [
10456 Point::new(1, 3)..Point::new(1, 3),
10457 Point::new(2, 1)..Point::new(2, 1),
10458 ]
10459 );
10460 });
10461
10462 multibuffer.update(cx, |multibuffer, cx| {
10463 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10464 });
10465 _ = editor.update(cx, |editor, window, cx| {
10466 // Removing an excerpt causes the first selection to become degenerate.
10467 assert_eq!(
10468 editor.selections.ranges(cx),
10469 [
10470 Point::new(0, 0)..Point::new(0, 0),
10471 Point::new(0, 1)..Point::new(0, 1)
10472 ]
10473 );
10474
10475 // Refreshing selections will relocate the first selection to the original buffer
10476 // location.
10477 editor.change_selections(None, window, cx, |s| s.refresh());
10478 assert_eq!(
10479 editor.selections.ranges(cx),
10480 [
10481 Point::new(0, 1)..Point::new(0, 1),
10482 Point::new(0, 3)..Point::new(0, 3)
10483 ]
10484 );
10485 assert!(editor.selections.pending_anchor().is_some());
10486 });
10487}
10488
10489#[gpui::test]
10490fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10491 init_test(cx, |_| {});
10492
10493 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10494 let mut excerpt1_id = None;
10495 let multibuffer = cx.new(|cx| {
10496 let mut multibuffer = MultiBuffer::new(ReadWrite);
10497 excerpt1_id = multibuffer
10498 .push_excerpts(
10499 buffer.clone(),
10500 [
10501 ExcerptRange {
10502 context: Point::new(0, 0)..Point::new(1, 4),
10503 primary: None,
10504 },
10505 ExcerptRange {
10506 context: Point::new(1, 0)..Point::new(2, 4),
10507 primary: None,
10508 },
10509 ],
10510 cx,
10511 )
10512 .into_iter()
10513 .next();
10514 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10515 multibuffer
10516 });
10517
10518 let editor = cx.add_window(|window, cx| {
10519 let mut editor = build_editor(multibuffer.clone(), window, cx);
10520 let snapshot = editor.snapshot(window, cx);
10521 editor.begin_selection(
10522 Point::new(1, 3).to_display_point(&snapshot),
10523 false,
10524 1,
10525 window,
10526 cx,
10527 );
10528 assert_eq!(
10529 editor.selections.ranges(cx),
10530 [Point::new(1, 3)..Point::new(1, 3)]
10531 );
10532 editor
10533 });
10534
10535 multibuffer.update(cx, |multibuffer, cx| {
10536 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10537 });
10538 _ = editor.update(cx, |editor, window, cx| {
10539 assert_eq!(
10540 editor.selections.ranges(cx),
10541 [Point::new(0, 0)..Point::new(0, 0)]
10542 );
10543
10544 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10545 editor.change_selections(None, window, cx, |s| s.refresh());
10546 assert_eq!(
10547 editor.selections.ranges(cx),
10548 [Point::new(0, 3)..Point::new(0, 3)]
10549 );
10550 assert!(editor.selections.pending_anchor().is_some());
10551 });
10552}
10553
10554#[gpui::test]
10555async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10556 init_test(cx, |_| {});
10557
10558 let language = Arc::new(
10559 Language::new(
10560 LanguageConfig {
10561 brackets: BracketPairConfig {
10562 pairs: vec![
10563 BracketPair {
10564 start: "{".to_string(),
10565 end: "}".to_string(),
10566 close: true,
10567 surround: true,
10568 newline: true,
10569 },
10570 BracketPair {
10571 start: "/* ".to_string(),
10572 end: " */".to_string(),
10573 close: true,
10574 surround: true,
10575 newline: true,
10576 },
10577 ],
10578 ..Default::default()
10579 },
10580 ..Default::default()
10581 },
10582 Some(tree_sitter_rust::LANGUAGE.into()),
10583 )
10584 .with_indents_query("")
10585 .unwrap(),
10586 );
10587
10588 let text = concat!(
10589 "{ }\n", //
10590 " x\n", //
10591 " /* */\n", //
10592 "x\n", //
10593 "{{} }\n", //
10594 );
10595
10596 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10597 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10598 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10599 editor
10600 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10601 .await;
10602
10603 editor.update_in(cx, |editor, window, cx| {
10604 editor.change_selections(None, window, cx, |s| {
10605 s.select_display_ranges([
10606 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10607 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10608 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10609 ])
10610 });
10611 editor.newline(&Newline, window, cx);
10612
10613 assert_eq!(
10614 editor.buffer().read(cx).read(cx).text(),
10615 concat!(
10616 "{ \n", // Suppress rustfmt
10617 "\n", //
10618 "}\n", //
10619 " x\n", //
10620 " /* \n", //
10621 " \n", //
10622 " */\n", //
10623 "x\n", //
10624 "{{} \n", //
10625 "}\n", //
10626 )
10627 );
10628 });
10629}
10630
10631#[gpui::test]
10632fn test_highlighted_ranges(cx: &mut TestAppContext) {
10633 init_test(cx, |_| {});
10634
10635 let editor = cx.add_window(|window, cx| {
10636 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10637 build_editor(buffer.clone(), window, cx)
10638 });
10639
10640 _ = editor.update(cx, |editor, window, cx| {
10641 struct Type1;
10642 struct Type2;
10643
10644 let buffer = editor.buffer.read(cx).snapshot(cx);
10645
10646 let anchor_range =
10647 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10648
10649 editor.highlight_background::<Type1>(
10650 &[
10651 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10652 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10653 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10654 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10655 ],
10656 |_| Hsla::red(),
10657 cx,
10658 );
10659 editor.highlight_background::<Type2>(
10660 &[
10661 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10662 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10663 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10664 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10665 ],
10666 |_| Hsla::green(),
10667 cx,
10668 );
10669
10670 let snapshot = editor.snapshot(window, cx);
10671 let mut highlighted_ranges = editor.background_highlights_in_range(
10672 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10673 &snapshot,
10674 cx.theme().colors(),
10675 );
10676 // Enforce a consistent ordering based on color without relying on the ordering of the
10677 // highlight's `TypeId` which is non-executor.
10678 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10679 assert_eq!(
10680 highlighted_ranges,
10681 &[
10682 (
10683 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10684 Hsla::red(),
10685 ),
10686 (
10687 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10688 Hsla::red(),
10689 ),
10690 (
10691 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10692 Hsla::green(),
10693 ),
10694 (
10695 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10696 Hsla::green(),
10697 ),
10698 ]
10699 );
10700 assert_eq!(
10701 editor.background_highlights_in_range(
10702 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10703 &snapshot,
10704 cx.theme().colors(),
10705 ),
10706 &[(
10707 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10708 Hsla::red(),
10709 )]
10710 );
10711 });
10712}
10713
10714#[gpui::test]
10715async fn test_following(cx: &mut TestAppContext) {
10716 init_test(cx, |_| {});
10717
10718 let fs = FakeFs::new(cx.executor());
10719 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10720
10721 let buffer = project.update(cx, |project, cx| {
10722 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10723 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10724 });
10725 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10726 let follower = cx.update(|cx| {
10727 cx.open_window(
10728 WindowOptions {
10729 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10730 gpui::Point::new(px(0.), px(0.)),
10731 gpui::Point::new(px(10.), px(80.)),
10732 ))),
10733 ..Default::default()
10734 },
10735 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10736 )
10737 .unwrap()
10738 });
10739
10740 let is_still_following = Rc::new(RefCell::new(true));
10741 let follower_edit_event_count = Rc::new(RefCell::new(0));
10742 let pending_update = Rc::new(RefCell::new(None));
10743 let leader_entity = leader.root(cx).unwrap();
10744 let follower_entity = follower.root(cx).unwrap();
10745 _ = follower.update(cx, {
10746 let update = pending_update.clone();
10747 let is_still_following = is_still_following.clone();
10748 let follower_edit_event_count = follower_edit_event_count.clone();
10749 |_, window, cx| {
10750 cx.subscribe_in(
10751 &leader_entity,
10752 window,
10753 move |_, leader, event, window, cx| {
10754 leader.read(cx).add_event_to_update_proto(
10755 event,
10756 &mut update.borrow_mut(),
10757 window,
10758 cx,
10759 );
10760 },
10761 )
10762 .detach();
10763
10764 cx.subscribe_in(
10765 &follower_entity,
10766 window,
10767 move |_, _, event: &EditorEvent, _window, _cx| {
10768 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10769 *is_still_following.borrow_mut() = false;
10770 }
10771
10772 if let EditorEvent::BufferEdited = event {
10773 *follower_edit_event_count.borrow_mut() += 1;
10774 }
10775 },
10776 )
10777 .detach();
10778 }
10779 });
10780
10781 // Update the selections only
10782 _ = leader.update(cx, |leader, window, cx| {
10783 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10784 });
10785 follower
10786 .update(cx, |follower, window, cx| {
10787 follower.apply_update_proto(
10788 &project,
10789 pending_update.borrow_mut().take().unwrap(),
10790 window,
10791 cx,
10792 )
10793 })
10794 .unwrap()
10795 .await
10796 .unwrap();
10797 _ = follower.update(cx, |follower, _, cx| {
10798 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10799 });
10800 assert!(*is_still_following.borrow());
10801 assert_eq!(*follower_edit_event_count.borrow(), 0);
10802
10803 // Update the scroll position only
10804 _ = leader.update(cx, |leader, window, cx| {
10805 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10806 });
10807 follower
10808 .update(cx, |follower, window, cx| {
10809 follower.apply_update_proto(
10810 &project,
10811 pending_update.borrow_mut().take().unwrap(),
10812 window,
10813 cx,
10814 )
10815 })
10816 .unwrap()
10817 .await
10818 .unwrap();
10819 assert_eq!(
10820 follower
10821 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10822 .unwrap(),
10823 gpui::Point::new(1.5, 3.5)
10824 );
10825 assert!(*is_still_following.borrow());
10826 assert_eq!(*follower_edit_event_count.borrow(), 0);
10827
10828 // Update the selections and scroll position. The follower's scroll position is updated
10829 // via autoscroll, not via the leader's exact scroll position.
10830 _ = leader.update(cx, |leader, window, cx| {
10831 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10832 leader.request_autoscroll(Autoscroll::newest(), cx);
10833 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10834 });
10835 follower
10836 .update(cx, |follower, window, cx| {
10837 follower.apply_update_proto(
10838 &project,
10839 pending_update.borrow_mut().take().unwrap(),
10840 window,
10841 cx,
10842 )
10843 })
10844 .unwrap()
10845 .await
10846 .unwrap();
10847 _ = follower.update(cx, |follower, _, cx| {
10848 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10849 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10850 });
10851 assert!(*is_still_following.borrow());
10852
10853 // Creating a pending selection that precedes another selection
10854 _ = leader.update(cx, |leader, window, cx| {
10855 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10856 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10857 });
10858 follower
10859 .update(cx, |follower, window, cx| {
10860 follower.apply_update_proto(
10861 &project,
10862 pending_update.borrow_mut().take().unwrap(),
10863 window,
10864 cx,
10865 )
10866 })
10867 .unwrap()
10868 .await
10869 .unwrap();
10870 _ = follower.update(cx, |follower, _, cx| {
10871 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10872 });
10873 assert!(*is_still_following.borrow());
10874
10875 // Extend the pending selection so that it surrounds another selection
10876 _ = leader.update(cx, |leader, window, cx| {
10877 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10878 });
10879 follower
10880 .update(cx, |follower, window, cx| {
10881 follower.apply_update_proto(
10882 &project,
10883 pending_update.borrow_mut().take().unwrap(),
10884 window,
10885 cx,
10886 )
10887 })
10888 .unwrap()
10889 .await
10890 .unwrap();
10891 _ = follower.update(cx, |follower, _, cx| {
10892 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10893 });
10894
10895 // Scrolling locally breaks the follow
10896 _ = follower.update(cx, |follower, window, cx| {
10897 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10898 follower.set_scroll_anchor(
10899 ScrollAnchor {
10900 anchor: top_anchor,
10901 offset: gpui::Point::new(0.0, 0.5),
10902 },
10903 window,
10904 cx,
10905 );
10906 });
10907 assert!(!(*is_still_following.borrow()));
10908}
10909
10910#[gpui::test]
10911async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10912 init_test(cx, |_| {});
10913
10914 let fs = FakeFs::new(cx.executor());
10915 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10916 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10917 let pane = workspace
10918 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10919 .unwrap();
10920
10921 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10922
10923 let leader = pane.update_in(cx, |_, window, cx| {
10924 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10925 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10926 });
10927
10928 // Start following the editor when it has no excerpts.
10929 let mut state_message =
10930 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10931 let workspace_entity = workspace.root(cx).unwrap();
10932 let follower_1 = cx
10933 .update_window(*workspace.deref(), |_, window, cx| {
10934 Editor::from_state_proto(
10935 workspace_entity,
10936 ViewId {
10937 creator: Default::default(),
10938 id: 0,
10939 },
10940 &mut state_message,
10941 window,
10942 cx,
10943 )
10944 })
10945 .unwrap()
10946 .unwrap()
10947 .await
10948 .unwrap();
10949
10950 let update_message = Rc::new(RefCell::new(None));
10951 follower_1.update_in(cx, {
10952 let update = update_message.clone();
10953 |_, window, cx| {
10954 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10955 leader.read(cx).add_event_to_update_proto(
10956 event,
10957 &mut update.borrow_mut(),
10958 window,
10959 cx,
10960 );
10961 })
10962 .detach();
10963 }
10964 });
10965
10966 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10967 (
10968 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10969 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10970 )
10971 });
10972
10973 // Insert some excerpts.
10974 leader.update(cx, |leader, cx| {
10975 leader.buffer.update(cx, |multibuffer, cx| {
10976 let excerpt_ids = multibuffer.push_excerpts(
10977 buffer_1.clone(),
10978 [
10979 ExcerptRange {
10980 context: 1..6,
10981 primary: None,
10982 },
10983 ExcerptRange {
10984 context: 12..15,
10985 primary: None,
10986 },
10987 ExcerptRange {
10988 context: 0..3,
10989 primary: None,
10990 },
10991 ],
10992 cx,
10993 );
10994 multibuffer.insert_excerpts_after(
10995 excerpt_ids[0],
10996 buffer_2.clone(),
10997 [
10998 ExcerptRange {
10999 context: 8..12,
11000 primary: None,
11001 },
11002 ExcerptRange {
11003 context: 0..6,
11004 primary: None,
11005 },
11006 ],
11007 cx,
11008 );
11009 });
11010 });
11011
11012 // Apply the update of adding the excerpts.
11013 follower_1
11014 .update_in(cx, |follower, window, cx| {
11015 follower.apply_update_proto(
11016 &project,
11017 update_message.borrow().clone().unwrap(),
11018 window,
11019 cx,
11020 )
11021 })
11022 .await
11023 .unwrap();
11024 assert_eq!(
11025 follower_1.update(cx, |editor, cx| editor.text(cx)),
11026 leader.update(cx, |editor, cx| editor.text(cx))
11027 );
11028 update_message.borrow_mut().take();
11029
11030 // Start following separately after it already has excerpts.
11031 let mut state_message =
11032 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11033 let workspace_entity = workspace.root(cx).unwrap();
11034 let follower_2 = cx
11035 .update_window(*workspace.deref(), |_, window, cx| {
11036 Editor::from_state_proto(
11037 workspace_entity,
11038 ViewId {
11039 creator: Default::default(),
11040 id: 0,
11041 },
11042 &mut state_message,
11043 window,
11044 cx,
11045 )
11046 })
11047 .unwrap()
11048 .unwrap()
11049 .await
11050 .unwrap();
11051 assert_eq!(
11052 follower_2.update(cx, |editor, cx| editor.text(cx)),
11053 leader.update(cx, |editor, cx| editor.text(cx))
11054 );
11055
11056 // Remove some excerpts.
11057 leader.update(cx, |leader, cx| {
11058 leader.buffer.update(cx, |multibuffer, cx| {
11059 let excerpt_ids = multibuffer.excerpt_ids();
11060 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11061 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11062 });
11063 });
11064
11065 // Apply the update of removing the excerpts.
11066 follower_1
11067 .update_in(cx, |follower, window, cx| {
11068 follower.apply_update_proto(
11069 &project,
11070 update_message.borrow().clone().unwrap(),
11071 window,
11072 cx,
11073 )
11074 })
11075 .await
11076 .unwrap();
11077 follower_2
11078 .update_in(cx, |follower, window, cx| {
11079 follower.apply_update_proto(
11080 &project,
11081 update_message.borrow().clone().unwrap(),
11082 window,
11083 cx,
11084 )
11085 })
11086 .await
11087 .unwrap();
11088 update_message.borrow_mut().take();
11089 assert_eq!(
11090 follower_1.update(cx, |editor, cx| editor.text(cx)),
11091 leader.update(cx, |editor, cx| editor.text(cx))
11092 );
11093}
11094
11095#[gpui::test]
11096async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11097 init_test(cx, |_| {});
11098
11099 let mut cx = EditorTestContext::new(cx).await;
11100 let lsp_store =
11101 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11102
11103 cx.set_state(indoc! {"
11104 ˇfn func(abc def: i32) -> u32 {
11105 }
11106 "});
11107
11108 cx.update(|_, cx| {
11109 lsp_store.update(cx, |lsp_store, cx| {
11110 lsp_store
11111 .update_diagnostics(
11112 LanguageServerId(0),
11113 lsp::PublishDiagnosticsParams {
11114 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11115 version: None,
11116 diagnostics: vec![
11117 lsp::Diagnostic {
11118 range: lsp::Range::new(
11119 lsp::Position::new(0, 11),
11120 lsp::Position::new(0, 12),
11121 ),
11122 severity: Some(lsp::DiagnosticSeverity::ERROR),
11123 ..Default::default()
11124 },
11125 lsp::Diagnostic {
11126 range: lsp::Range::new(
11127 lsp::Position::new(0, 12),
11128 lsp::Position::new(0, 15),
11129 ),
11130 severity: Some(lsp::DiagnosticSeverity::ERROR),
11131 ..Default::default()
11132 },
11133 lsp::Diagnostic {
11134 range: lsp::Range::new(
11135 lsp::Position::new(0, 25),
11136 lsp::Position::new(0, 28),
11137 ),
11138 severity: Some(lsp::DiagnosticSeverity::ERROR),
11139 ..Default::default()
11140 },
11141 ],
11142 },
11143 &[],
11144 cx,
11145 )
11146 .unwrap()
11147 });
11148 });
11149
11150 executor.run_until_parked();
11151
11152 cx.update_editor(|editor, window, cx| {
11153 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11154 });
11155
11156 cx.assert_editor_state(indoc! {"
11157 fn func(abc def: i32) -> ˇu32 {
11158 }
11159 "});
11160
11161 cx.update_editor(|editor, window, cx| {
11162 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11163 });
11164
11165 cx.assert_editor_state(indoc! {"
11166 fn func(abc ˇdef: i32) -> u32 {
11167 }
11168 "});
11169
11170 cx.update_editor(|editor, window, cx| {
11171 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11172 });
11173
11174 cx.assert_editor_state(indoc! {"
11175 fn func(abcˇ def: i32) -> u32 {
11176 }
11177 "});
11178
11179 cx.update_editor(|editor, window, cx| {
11180 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11181 });
11182
11183 cx.assert_editor_state(indoc! {"
11184 fn func(abc def: i32) -> ˇu32 {
11185 }
11186 "});
11187}
11188
11189#[gpui::test]
11190async fn cycle_through_same_place_diagnostics(
11191 executor: BackgroundExecutor,
11192 cx: &mut TestAppContext,
11193) {
11194 init_test(cx, |_| {});
11195
11196 let mut cx = EditorTestContext::new(cx).await;
11197 let lsp_store =
11198 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11199
11200 cx.set_state(indoc! {"
11201 ˇfn func(abc def: i32) -> u32 {
11202 }
11203 "});
11204
11205 cx.update(|_, cx| {
11206 lsp_store.update(cx, |lsp_store, cx| {
11207 lsp_store
11208 .update_diagnostics(
11209 LanguageServerId(0),
11210 lsp::PublishDiagnosticsParams {
11211 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11212 version: None,
11213 diagnostics: vec![
11214 lsp::Diagnostic {
11215 range: lsp::Range::new(
11216 lsp::Position::new(0, 11),
11217 lsp::Position::new(0, 12),
11218 ),
11219 severity: Some(lsp::DiagnosticSeverity::ERROR),
11220 ..Default::default()
11221 },
11222 lsp::Diagnostic {
11223 range: lsp::Range::new(
11224 lsp::Position::new(0, 12),
11225 lsp::Position::new(0, 15),
11226 ),
11227 severity: Some(lsp::DiagnosticSeverity::ERROR),
11228 ..Default::default()
11229 },
11230 lsp::Diagnostic {
11231 range: lsp::Range::new(
11232 lsp::Position::new(0, 12),
11233 lsp::Position::new(0, 15),
11234 ),
11235 severity: Some(lsp::DiagnosticSeverity::ERROR),
11236 ..Default::default()
11237 },
11238 lsp::Diagnostic {
11239 range: lsp::Range::new(
11240 lsp::Position::new(0, 25),
11241 lsp::Position::new(0, 28),
11242 ),
11243 severity: Some(lsp::DiagnosticSeverity::ERROR),
11244 ..Default::default()
11245 },
11246 ],
11247 },
11248 &[],
11249 cx,
11250 )
11251 .unwrap()
11252 });
11253 });
11254 executor.run_until_parked();
11255
11256 //// Backward
11257
11258 // Fourth diagnostic
11259 cx.update_editor(|editor, window, cx| {
11260 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11261 });
11262 cx.assert_editor_state(indoc! {"
11263 fn func(abc def: i32) -> ˇu32 {
11264 }
11265 "});
11266
11267 // Third diagnostic
11268 cx.update_editor(|editor, window, cx| {
11269 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11270 });
11271 cx.assert_editor_state(indoc! {"
11272 fn func(abc ˇdef: i32) -> u32 {
11273 }
11274 "});
11275
11276 // Second diagnostic, same place
11277 cx.update_editor(|editor, window, cx| {
11278 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11279 });
11280 cx.assert_editor_state(indoc! {"
11281 fn func(abc ˇdef: i32) -> u32 {
11282 }
11283 "});
11284
11285 // First diagnostic
11286 cx.update_editor(|editor, window, cx| {
11287 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11288 });
11289 cx.assert_editor_state(indoc! {"
11290 fn func(abcˇ def: i32) -> u32 {
11291 }
11292 "});
11293
11294 // Wrapped over, fourth diagnostic
11295 cx.update_editor(|editor, window, cx| {
11296 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11297 });
11298 cx.assert_editor_state(indoc! {"
11299 fn func(abc def: i32) -> ˇu32 {
11300 }
11301 "});
11302
11303 cx.update_editor(|editor, window, cx| {
11304 editor.move_to_beginning(&MoveToBeginning, window, cx);
11305 });
11306 cx.assert_editor_state(indoc! {"
11307 ˇfn func(abc def: i32) -> u32 {
11308 }
11309 "});
11310
11311 //// Forward
11312
11313 // First diagnostic
11314 cx.update_editor(|editor, window, cx| {
11315 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11316 });
11317 cx.assert_editor_state(indoc! {"
11318 fn func(abcˇ def: i32) -> u32 {
11319 }
11320 "});
11321
11322 // Second diagnostic
11323 cx.update_editor(|editor, window, cx| {
11324 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11325 });
11326 cx.assert_editor_state(indoc! {"
11327 fn func(abc ˇdef: i32) -> u32 {
11328 }
11329 "});
11330
11331 // Third diagnostic, same place
11332 cx.update_editor(|editor, window, cx| {
11333 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11334 });
11335 cx.assert_editor_state(indoc! {"
11336 fn func(abc ˇdef: i32) -> u32 {
11337 }
11338 "});
11339
11340 // Fourth diagnostic
11341 cx.update_editor(|editor, window, cx| {
11342 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11343 });
11344 cx.assert_editor_state(indoc! {"
11345 fn func(abc def: i32) -> ˇu32 {
11346 }
11347 "});
11348
11349 // Wrapped around, first diagnostic
11350 cx.update_editor(|editor, window, cx| {
11351 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11352 });
11353 cx.assert_editor_state(indoc! {"
11354 fn func(abcˇ def: i32) -> u32 {
11355 }
11356 "});
11357}
11358
11359#[gpui::test]
11360async fn active_diagnostics_dismiss_after_invalidation(
11361 executor: BackgroundExecutor,
11362 cx: &mut TestAppContext,
11363) {
11364 init_test(cx, |_| {});
11365
11366 let mut cx = EditorTestContext::new(cx).await;
11367 let lsp_store =
11368 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11369
11370 cx.set_state(indoc! {"
11371 ˇfn func(abc def: i32) -> u32 {
11372 }
11373 "});
11374
11375 let message = "Something's wrong!";
11376 cx.update(|_, cx| {
11377 lsp_store.update(cx, |lsp_store, cx| {
11378 lsp_store
11379 .update_diagnostics(
11380 LanguageServerId(0),
11381 lsp::PublishDiagnosticsParams {
11382 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11383 version: None,
11384 diagnostics: vec![lsp::Diagnostic {
11385 range: lsp::Range::new(
11386 lsp::Position::new(0, 11),
11387 lsp::Position::new(0, 12),
11388 ),
11389 severity: Some(lsp::DiagnosticSeverity::ERROR),
11390 message: message.to_string(),
11391 ..Default::default()
11392 }],
11393 },
11394 &[],
11395 cx,
11396 )
11397 .unwrap()
11398 });
11399 });
11400 executor.run_until_parked();
11401
11402 cx.update_editor(|editor, window, cx| {
11403 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11404 assert_eq!(
11405 editor
11406 .active_diagnostics
11407 .as_ref()
11408 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11409 Some(message),
11410 "Should have a diagnostics group activated"
11411 );
11412 });
11413 cx.assert_editor_state(indoc! {"
11414 fn func(abcˇ def: i32) -> u32 {
11415 }
11416 "});
11417
11418 cx.update(|_, cx| {
11419 lsp_store.update(cx, |lsp_store, cx| {
11420 lsp_store
11421 .update_diagnostics(
11422 LanguageServerId(0),
11423 lsp::PublishDiagnosticsParams {
11424 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11425 version: None,
11426 diagnostics: Vec::new(),
11427 },
11428 &[],
11429 cx,
11430 )
11431 .unwrap()
11432 });
11433 });
11434 executor.run_until_parked();
11435 cx.update_editor(|editor, _, _| {
11436 assert_eq!(
11437 editor.active_diagnostics, None,
11438 "After no diagnostics set to the editor, no diagnostics should be active"
11439 );
11440 });
11441 cx.assert_editor_state(indoc! {"
11442 fn func(abcˇ def: i32) -> u32 {
11443 }
11444 "});
11445
11446 cx.update_editor(|editor, window, cx| {
11447 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11448 assert_eq!(
11449 editor.active_diagnostics, None,
11450 "Should be no diagnostics to go to and activate"
11451 );
11452 });
11453 cx.assert_editor_state(indoc! {"
11454 fn func(abcˇ def: i32) -> u32 {
11455 }
11456 "});
11457}
11458
11459#[gpui::test]
11460async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11461 init_test(cx, |_| {});
11462
11463 let mut cx = EditorTestContext::new(cx).await;
11464
11465 cx.set_state(indoc! {"
11466 fn func(abˇc def: i32) -> u32 {
11467 }
11468 "});
11469 let lsp_store =
11470 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11471
11472 cx.update(|_, cx| {
11473 lsp_store.update(cx, |lsp_store, cx| {
11474 lsp_store.update_diagnostics(
11475 LanguageServerId(0),
11476 lsp::PublishDiagnosticsParams {
11477 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11478 version: None,
11479 diagnostics: vec![lsp::Diagnostic {
11480 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11481 severity: Some(lsp::DiagnosticSeverity::ERROR),
11482 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11483 ..Default::default()
11484 }],
11485 },
11486 &[],
11487 cx,
11488 )
11489 })
11490 }).unwrap();
11491 cx.run_until_parked();
11492 cx.update_editor(|editor, window, cx| {
11493 hover_popover::hover(editor, &Default::default(), window, cx)
11494 });
11495 cx.run_until_parked();
11496 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11497}
11498
11499#[gpui::test]
11500async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11501 init_test(cx, |_| {});
11502
11503 let mut cx = EditorTestContext::new(cx).await;
11504
11505 let diff_base = r#"
11506 use some::mod;
11507
11508 const A: u32 = 42;
11509
11510 fn main() {
11511 println!("hello");
11512
11513 println!("world");
11514 }
11515 "#
11516 .unindent();
11517
11518 // Edits are modified, removed, modified, added
11519 cx.set_state(
11520 &r#"
11521 use some::modified;
11522
11523 ˇ
11524 fn main() {
11525 println!("hello there");
11526
11527 println!("around the");
11528 println!("world");
11529 }
11530 "#
11531 .unindent(),
11532 );
11533
11534 cx.set_head_text(&diff_base);
11535 executor.run_until_parked();
11536
11537 cx.update_editor(|editor, window, cx| {
11538 //Wrap around the bottom of the buffer
11539 for _ in 0..3 {
11540 editor.go_to_next_hunk(&GoToHunk, window, cx);
11541 }
11542 });
11543
11544 cx.assert_editor_state(
11545 &r#"
11546 ˇuse some::modified;
11547
11548
11549 fn main() {
11550 println!("hello there");
11551
11552 println!("around the");
11553 println!("world");
11554 }
11555 "#
11556 .unindent(),
11557 );
11558
11559 cx.update_editor(|editor, window, cx| {
11560 //Wrap around the top of the buffer
11561 for _ in 0..2 {
11562 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11563 }
11564 });
11565
11566 cx.assert_editor_state(
11567 &r#"
11568 use some::modified;
11569
11570
11571 fn main() {
11572 ˇ println!("hello there");
11573
11574 println!("around the");
11575 println!("world");
11576 }
11577 "#
11578 .unindent(),
11579 );
11580
11581 cx.update_editor(|editor, window, cx| {
11582 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11583 });
11584
11585 cx.assert_editor_state(
11586 &r#"
11587 use some::modified;
11588
11589 ˇ
11590 fn main() {
11591 println!("hello there");
11592
11593 println!("around the");
11594 println!("world");
11595 }
11596 "#
11597 .unindent(),
11598 );
11599
11600 cx.update_editor(|editor, window, cx| {
11601 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11602 });
11603
11604 cx.assert_editor_state(
11605 &r#"
11606 ˇuse some::modified;
11607
11608
11609 fn main() {
11610 println!("hello there");
11611
11612 println!("around the");
11613 println!("world");
11614 }
11615 "#
11616 .unindent(),
11617 );
11618
11619 cx.update_editor(|editor, window, cx| {
11620 for _ in 0..2 {
11621 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11622 }
11623 });
11624
11625 cx.assert_editor_state(
11626 &r#"
11627 use some::modified;
11628
11629
11630 fn main() {
11631 ˇ println!("hello there");
11632
11633 println!("around the");
11634 println!("world");
11635 }
11636 "#
11637 .unindent(),
11638 );
11639
11640 cx.update_editor(|editor, window, cx| {
11641 editor.fold(&Fold, window, cx);
11642 });
11643
11644 cx.update_editor(|editor, window, cx| {
11645 editor.go_to_next_hunk(&GoToHunk, window, cx);
11646 });
11647
11648 cx.assert_editor_state(
11649 &r#"
11650 ˇuse some::modified;
11651
11652
11653 fn main() {
11654 println!("hello there");
11655
11656 println!("around the");
11657 println!("world");
11658 }
11659 "#
11660 .unindent(),
11661 );
11662}
11663
11664#[test]
11665fn test_split_words() {
11666 fn split(text: &str) -> Vec<&str> {
11667 split_words(text).collect()
11668 }
11669
11670 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11671 assert_eq!(split("hello_world"), &["hello_", "world"]);
11672 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11673 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11674 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11675 assert_eq!(split("helloworld"), &["helloworld"]);
11676
11677 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11678}
11679
11680#[gpui::test]
11681async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11682 init_test(cx, |_| {});
11683
11684 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11685 let mut assert = |before, after| {
11686 let _state_context = cx.set_state(before);
11687 cx.run_until_parked();
11688 cx.update_editor(|editor, window, cx| {
11689 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11690 });
11691 cx.assert_editor_state(after);
11692 };
11693
11694 // Outside bracket jumps to outside of matching bracket
11695 assert("console.logˇ(var);", "console.log(var)ˇ;");
11696 assert("console.log(var)ˇ;", "console.logˇ(var);");
11697
11698 // Inside bracket jumps to inside of matching bracket
11699 assert("console.log(ˇvar);", "console.log(varˇ);");
11700 assert("console.log(varˇ);", "console.log(ˇvar);");
11701
11702 // When outside a bracket and inside, favor jumping to the inside bracket
11703 assert(
11704 "console.log('foo', [1, 2, 3]ˇ);",
11705 "console.log(ˇ'foo', [1, 2, 3]);",
11706 );
11707 assert(
11708 "console.log(ˇ'foo', [1, 2, 3]);",
11709 "console.log('foo', [1, 2, 3]ˇ);",
11710 );
11711
11712 // Bias forward if two options are equally likely
11713 assert(
11714 "let result = curried_fun()ˇ();",
11715 "let result = curried_fun()()ˇ;",
11716 );
11717
11718 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11719 assert(
11720 indoc! {"
11721 function test() {
11722 console.log('test')ˇ
11723 }"},
11724 indoc! {"
11725 function test() {
11726 console.logˇ('test')
11727 }"},
11728 );
11729}
11730
11731#[gpui::test]
11732async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11733 init_test(cx, |_| {});
11734
11735 let fs = FakeFs::new(cx.executor());
11736 fs.insert_tree(
11737 path!("/a"),
11738 json!({
11739 "main.rs": "fn main() { let a = 5; }",
11740 "other.rs": "// Test file",
11741 }),
11742 )
11743 .await;
11744 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11745
11746 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11747 language_registry.add(Arc::new(Language::new(
11748 LanguageConfig {
11749 name: "Rust".into(),
11750 matcher: LanguageMatcher {
11751 path_suffixes: vec!["rs".to_string()],
11752 ..Default::default()
11753 },
11754 brackets: BracketPairConfig {
11755 pairs: vec![BracketPair {
11756 start: "{".to_string(),
11757 end: "}".to_string(),
11758 close: true,
11759 surround: true,
11760 newline: true,
11761 }],
11762 disabled_scopes_by_bracket_ix: Vec::new(),
11763 },
11764 ..Default::default()
11765 },
11766 Some(tree_sitter_rust::LANGUAGE.into()),
11767 )));
11768 let mut fake_servers = language_registry.register_fake_lsp(
11769 "Rust",
11770 FakeLspAdapter {
11771 capabilities: lsp::ServerCapabilities {
11772 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11773 first_trigger_character: "{".to_string(),
11774 more_trigger_character: None,
11775 }),
11776 ..Default::default()
11777 },
11778 ..Default::default()
11779 },
11780 );
11781
11782 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11783
11784 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11785
11786 let worktree_id = workspace
11787 .update(cx, |workspace, _, cx| {
11788 workspace.project().update(cx, |project, cx| {
11789 project.worktrees(cx).next().unwrap().read(cx).id()
11790 })
11791 })
11792 .unwrap();
11793
11794 let buffer = project
11795 .update(cx, |project, cx| {
11796 project.open_local_buffer(path!("/a/main.rs"), cx)
11797 })
11798 .await
11799 .unwrap();
11800 let editor_handle = workspace
11801 .update(cx, |workspace, window, cx| {
11802 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11803 })
11804 .unwrap()
11805 .await
11806 .unwrap()
11807 .downcast::<Editor>()
11808 .unwrap();
11809
11810 cx.executor().start_waiting();
11811 let fake_server = fake_servers.next().await.unwrap();
11812
11813 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11814 assert_eq!(
11815 params.text_document_position.text_document.uri,
11816 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11817 );
11818 assert_eq!(
11819 params.text_document_position.position,
11820 lsp::Position::new(0, 21),
11821 );
11822
11823 Ok(Some(vec![lsp::TextEdit {
11824 new_text: "]".to_string(),
11825 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11826 }]))
11827 });
11828
11829 editor_handle.update_in(cx, |editor, window, cx| {
11830 window.focus(&editor.focus_handle(cx));
11831 editor.change_selections(None, window, cx, |s| {
11832 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11833 });
11834 editor.handle_input("{", window, cx);
11835 });
11836
11837 cx.executor().run_until_parked();
11838
11839 buffer.update(cx, |buffer, _| {
11840 assert_eq!(
11841 buffer.text(),
11842 "fn main() { let a = {5}; }",
11843 "No extra braces from on type formatting should appear in the buffer"
11844 )
11845 });
11846}
11847
11848#[gpui::test]
11849async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11850 init_test(cx, |_| {});
11851
11852 let fs = FakeFs::new(cx.executor());
11853 fs.insert_tree(
11854 path!("/a"),
11855 json!({
11856 "main.rs": "fn main() { let a = 5; }",
11857 "other.rs": "// Test file",
11858 }),
11859 )
11860 .await;
11861
11862 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11863
11864 let server_restarts = Arc::new(AtomicUsize::new(0));
11865 let closure_restarts = Arc::clone(&server_restarts);
11866 let language_server_name = "test language server";
11867 let language_name: LanguageName = "Rust".into();
11868
11869 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11870 language_registry.add(Arc::new(Language::new(
11871 LanguageConfig {
11872 name: language_name.clone(),
11873 matcher: LanguageMatcher {
11874 path_suffixes: vec!["rs".to_string()],
11875 ..Default::default()
11876 },
11877 ..Default::default()
11878 },
11879 Some(tree_sitter_rust::LANGUAGE.into()),
11880 )));
11881 let mut fake_servers = language_registry.register_fake_lsp(
11882 "Rust",
11883 FakeLspAdapter {
11884 name: language_server_name,
11885 initialization_options: Some(json!({
11886 "testOptionValue": true
11887 })),
11888 initializer: Some(Box::new(move |fake_server| {
11889 let task_restarts = Arc::clone(&closure_restarts);
11890 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11891 task_restarts.fetch_add(1, atomic::Ordering::Release);
11892 futures::future::ready(Ok(()))
11893 });
11894 })),
11895 ..Default::default()
11896 },
11897 );
11898
11899 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11900 let _buffer = project
11901 .update(cx, |project, cx| {
11902 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11903 })
11904 .await
11905 .unwrap();
11906 let _fake_server = fake_servers.next().await.unwrap();
11907 update_test_language_settings(cx, |language_settings| {
11908 language_settings.languages.insert(
11909 language_name.clone(),
11910 LanguageSettingsContent {
11911 tab_size: NonZeroU32::new(8),
11912 ..Default::default()
11913 },
11914 );
11915 });
11916 cx.executor().run_until_parked();
11917 assert_eq!(
11918 server_restarts.load(atomic::Ordering::Acquire),
11919 0,
11920 "Should not restart LSP server on an unrelated change"
11921 );
11922
11923 update_test_project_settings(cx, |project_settings| {
11924 project_settings.lsp.insert(
11925 "Some other server name".into(),
11926 LspSettings {
11927 binary: None,
11928 settings: None,
11929 initialization_options: Some(json!({
11930 "some other init value": false
11931 })),
11932 },
11933 );
11934 });
11935 cx.executor().run_until_parked();
11936 assert_eq!(
11937 server_restarts.load(atomic::Ordering::Acquire),
11938 0,
11939 "Should not restart LSP server on an unrelated LSP settings change"
11940 );
11941
11942 update_test_project_settings(cx, |project_settings| {
11943 project_settings.lsp.insert(
11944 language_server_name.into(),
11945 LspSettings {
11946 binary: None,
11947 settings: None,
11948 initialization_options: Some(json!({
11949 "anotherInitValue": false
11950 })),
11951 },
11952 );
11953 });
11954 cx.executor().run_until_parked();
11955 assert_eq!(
11956 server_restarts.load(atomic::Ordering::Acquire),
11957 1,
11958 "Should restart LSP server on a related LSP settings change"
11959 );
11960
11961 update_test_project_settings(cx, |project_settings| {
11962 project_settings.lsp.insert(
11963 language_server_name.into(),
11964 LspSettings {
11965 binary: None,
11966 settings: None,
11967 initialization_options: Some(json!({
11968 "anotherInitValue": false
11969 })),
11970 },
11971 );
11972 });
11973 cx.executor().run_until_parked();
11974 assert_eq!(
11975 server_restarts.load(atomic::Ordering::Acquire),
11976 1,
11977 "Should not restart LSP server on a related LSP settings change that is the same"
11978 );
11979
11980 update_test_project_settings(cx, |project_settings| {
11981 project_settings.lsp.insert(
11982 language_server_name.into(),
11983 LspSettings {
11984 binary: None,
11985 settings: None,
11986 initialization_options: None,
11987 },
11988 );
11989 });
11990 cx.executor().run_until_parked();
11991 assert_eq!(
11992 server_restarts.load(atomic::Ordering::Acquire),
11993 2,
11994 "Should restart LSP server on another related LSP settings change"
11995 );
11996}
11997
11998#[gpui::test]
11999async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12000 init_test(cx, |_| {});
12001
12002 let mut cx = EditorLspTestContext::new_rust(
12003 lsp::ServerCapabilities {
12004 completion_provider: Some(lsp::CompletionOptions {
12005 trigger_characters: Some(vec![".".to_string()]),
12006 resolve_provider: Some(true),
12007 ..Default::default()
12008 }),
12009 ..Default::default()
12010 },
12011 cx,
12012 )
12013 .await;
12014
12015 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12016 cx.simulate_keystroke(".");
12017 let completion_item = lsp::CompletionItem {
12018 label: "some".into(),
12019 kind: Some(lsp::CompletionItemKind::SNIPPET),
12020 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12021 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12022 kind: lsp::MarkupKind::Markdown,
12023 value: "```rust\nSome(2)\n```".to_string(),
12024 })),
12025 deprecated: Some(false),
12026 sort_text: Some("fffffff2".to_string()),
12027 filter_text: Some("some".to_string()),
12028 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12029 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12030 range: lsp::Range {
12031 start: lsp::Position {
12032 line: 0,
12033 character: 22,
12034 },
12035 end: lsp::Position {
12036 line: 0,
12037 character: 22,
12038 },
12039 },
12040 new_text: "Some(2)".to_string(),
12041 })),
12042 additional_text_edits: Some(vec![lsp::TextEdit {
12043 range: lsp::Range {
12044 start: lsp::Position {
12045 line: 0,
12046 character: 20,
12047 },
12048 end: lsp::Position {
12049 line: 0,
12050 character: 22,
12051 },
12052 },
12053 new_text: "".to_string(),
12054 }]),
12055 ..Default::default()
12056 };
12057
12058 let closure_completion_item = completion_item.clone();
12059 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12060 let task_completion_item = closure_completion_item.clone();
12061 async move {
12062 Ok(Some(lsp::CompletionResponse::Array(vec![
12063 task_completion_item,
12064 ])))
12065 }
12066 });
12067
12068 request.next().await;
12069
12070 cx.condition(|editor, _| editor.context_menu_visible())
12071 .await;
12072 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12073 editor
12074 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12075 .unwrap()
12076 });
12077 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12078
12079 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12080 let task_completion_item = completion_item.clone();
12081 async move { Ok(task_completion_item) }
12082 })
12083 .next()
12084 .await
12085 .unwrap();
12086 apply_additional_edits.await.unwrap();
12087 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12088}
12089
12090#[gpui::test]
12091async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12092 init_test(cx, |_| {});
12093
12094 let mut cx = EditorLspTestContext::new_rust(
12095 lsp::ServerCapabilities {
12096 completion_provider: Some(lsp::CompletionOptions {
12097 trigger_characters: Some(vec![".".to_string()]),
12098 resolve_provider: Some(true),
12099 ..Default::default()
12100 }),
12101 ..Default::default()
12102 },
12103 cx,
12104 )
12105 .await;
12106
12107 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12108 cx.simulate_keystroke(".");
12109
12110 let item1 = lsp::CompletionItem {
12111 label: "method id()".to_string(),
12112 filter_text: Some("id".to_string()),
12113 detail: None,
12114 documentation: None,
12115 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12116 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12117 new_text: ".id".to_string(),
12118 })),
12119 ..lsp::CompletionItem::default()
12120 };
12121
12122 let item2 = lsp::CompletionItem {
12123 label: "other".to_string(),
12124 filter_text: Some("other".to_string()),
12125 detail: None,
12126 documentation: None,
12127 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12128 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12129 new_text: ".other".to_string(),
12130 })),
12131 ..lsp::CompletionItem::default()
12132 };
12133
12134 let item1 = item1.clone();
12135 cx.handle_request::<lsp::request::Completion, _, _>({
12136 let item1 = item1.clone();
12137 move |_, _, _| {
12138 let item1 = item1.clone();
12139 let item2 = item2.clone();
12140 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12141 }
12142 })
12143 .next()
12144 .await;
12145
12146 cx.condition(|editor, _| editor.context_menu_visible())
12147 .await;
12148 cx.update_editor(|editor, _, _| {
12149 let context_menu = editor.context_menu.borrow_mut();
12150 let context_menu = context_menu
12151 .as_ref()
12152 .expect("Should have the context menu deployed");
12153 match context_menu {
12154 CodeContextMenu::Completions(completions_menu) => {
12155 let completions = completions_menu.completions.borrow_mut();
12156 assert_eq!(
12157 completions
12158 .iter()
12159 .map(|completion| &completion.label.text)
12160 .collect::<Vec<_>>(),
12161 vec!["method id()", "other"]
12162 )
12163 }
12164 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12165 }
12166 });
12167
12168 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12169 let item1 = item1.clone();
12170 move |_, item_to_resolve, _| {
12171 let item1 = item1.clone();
12172 async move {
12173 if item1 == item_to_resolve {
12174 Ok(lsp::CompletionItem {
12175 label: "method id()".to_string(),
12176 filter_text: Some("id".to_string()),
12177 detail: Some("Now resolved!".to_string()),
12178 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12179 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12180 range: lsp::Range::new(
12181 lsp::Position::new(0, 22),
12182 lsp::Position::new(0, 22),
12183 ),
12184 new_text: ".id".to_string(),
12185 })),
12186 ..lsp::CompletionItem::default()
12187 })
12188 } else {
12189 Ok(item_to_resolve)
12190 }
12191 }
12192 }
12193 })
12194 .next()
12195 .await
12196 .unwrap();
12197 cx.run_until_parked();
12198
12199 cx.update_editor(|editor, window, cx| {
12200 editor.context_menu_next(&Default::default(), window, cx);
12201 });
12202
12203 cx.update_editor(|editor, _, _| {
12204 let context_menu = editor.context_menu.borrow_mut();
12205 let context_menu = context_menu
12206 .as_ref()
12207 .expect("Should have the context menu deployed");
12208 match context_menu {
12209 CodeContextMenu::Completions(completions_menu) => {
12210 let completions = completions_menu.completions.borrow_mut();
12211 assert_eq!(
12212 completions
12213 .iter()
12214 .map(|completion| &completion.label.text)
12215 .collect::<Vec<_>>(),
12216 vec!["method id() Now resolved!", "other"],
12217 "Should update first completion label, but not second as the filter text did not match."
12218 );
12219 }
12220 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12221 }
12222 });
12223}
12224
12225#[gpui::test]
12226async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12227 init_test(cx, |_| {});
12228
12229 let mut cx = EditorLspTestContext::new_rust(
12230 lsp::ServerCapabilities {
12231 completion_provider: Some(lsp::CompletionOptions {
12232 trigger_characters: Some(vec![".".to_string()]),
12233 resolve_provider: Some(true),
12234 ..Default::default()
12235 }),
12236 ..Default::default()
12237 },
12238 cx,
12239 )
12240 .await;
12241
12242 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12243 cx.simulate_keystroke(".");
12244
12245 let unresolved_item_1 = lsp::CompletionItem {
12246 label: "id".to_string(),
12247 filter_text: Some("id".to_string()),
12248 detail: None,
12249 documentation: None,
12250 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12251 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12252 new_text: ".id".to_string(),
12253 })),
12254 ..lsp::CompletionItem::default()
12255 };
12256 let resolved_item_1 = lsp::CompletionItem {
12257 additional_text_edits: Some(vec![lsp::TextEdit {
12258 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12259 new_text: "!!".to_string(),
12260 }]),
12261 ..unresolved_item_1.clone()
12262 };
12263 let unresolved_item_2 = lsp::CompletionItem {
12264 label: "other".to_string(),
12265 filter_text: Some("other".to_string()),
12266 detail: None,
12267 documentation: None,
12268 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12269 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12270 new_text: ".other".to_string(),
12271 })),
12272 ..lsp::CompletionItem::default()
12273 };
12274 let resolved_item_2 = lsp::CompletionItem {
12275 additional_text_edits: Some(vec![lsp::TextEdit {
12276 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12277 new_text: "??".to_string(),
12278 }]),
12279 ..unresolved_item_2.clone()
12280 };
12281
12282 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12283 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12284 cx.lsp
12285 .server
12286 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12287 let unresolved_item_1 = unresolved_item_1.clone();
12288 let resolved_item_1 = resolved_item_1.clone();
12289 let unresolved_item_2 = unresolved_item_2.clone();
12290 let resolved_item_2 = resolved_item_2.clone();
12291 let resolve_requests_1 = resolve_requests_1.clone();
12292 let resolve_requests_2 = resolve_requests_2.clone();
12293 move |unresolved_request, _| {
12294 let unresolved_item_1 = unresolved_item_1.clone();
12295 let resolved_item_1 = resolved_item_1.clone();
12296 let unresolved_item_2 = unresolved_item_2.clone();
12297 let resolved_item_2 = resolved_item_2.clone();
12298 let resolve_requests_1 = resolve_requests_1.clone();
12299 let resolve_requests_2 = resolve_requests_2.clone();
12300 async move {
12301 if unresolved_request == unresolved_item_1 {
12302 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12303 Ok(resolved_item_1.clone())
12304 } else if unresolved_request == unresolved_item_2 {
12305 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12306 Ok(resolved_item_2.clone())
12307 } else {
12308 panic!("Unexpected completion item {unresolved_request:?}")
12309 }
12310 }
12311 }
12312 })
12313 .detach();
12314
12315 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12316 let unresolved_item_1 = unresolved_item_1.clone();
12317 let unresolved_item_2 = unresolved_item_2.clone();
12318 async move {
12319 Ok(Some(lsp::CompletionResponse::Array(vec![
12320 unresolved_item_1,
12321 unresolved_item_2,
12322 ])))
12323 }
12324 })
12325 .next()
12326 .await;
12327
12328 cx.condition(|editor, _| editor.context_menu_visible())
12329 .await;
12330 cx.update_editor(|editor, _, _| {
12331 let context_menu = editor.context_menu.borrow_mut();
12332 let context_menu = context_menu
12333 .as_ref()
12334 .expect("Should have the context menu deployed");
12335 match context_menu {
12336 CodeContextMenu::Completions(completions_menu) => {
12337 let completions = completions_menu.completions.borrow_mut();
12338 assert_eq!(
12339 completions
12340 .iter()
12341 .map(|completion| &completion.label.text)
12342 .collect::<Vec<_>>(),
12343 vec!["id", "other"]
12344 )
12345 }
12346 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12347 }
12348 });
12349 cx.run_until_parked();
12350
12351 cx.update_editor(|editor, window, cx| {
12352 editor.context_menu_next(&ContextMenuNext, window, cx);
12353 });
12354 cx.run_until_parked();
12355 cx.update_editor(|editor, window, cx| {
12356 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12357 });
12358 cx.run_until_parked();
12359 cx.update_editor(|editor, window, cx| {
12360 editor.context_menu_next(&ContextMenuNext, window, cx);
12361 });
12362 cx.run_until_parked();
12363 cx.update_editor(|editor, window, cx| {
12364 editor
12365 .compose_completion(&ComposeCompletion::default(), window, cx)
12366 .expect("No task returned")
12367 })
12368 .await
12369 .expect("Completion failed");
12370 cx.run_until_parked();
12371
12372 cx.update_editor(|editor, _, cx| {
12373 assert_eq!(
12374 resolve_requests_1.load(atomic::Ordering::Acquire),
12375 1,
12376 "Should always resolve once despite multiple selections"
12377 );
12378 assert_eq!(
12379 resolve_requests_2.load(atomic::Ordering::Acquire),
12380 1,
12381 "Should always resolve once after multiple selections and applying the completion"
12382 );
12383 assert_eq!(
12384 editor.text(cx),
12385 "fn main() { let a = ??.other; }",
12386 "Should use resolved data when applying the completion"
12387 );
12388 });
12389}
12390
12391#[gpui::test]
12392async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12393 init_test(cx, |_| {});
12394
12395 let item_0 = lsp::CompletionItem {
12396 label: "abs".into(),
12397 insert_text: Some("abs".into()),
12398 data: Some(json!({ "very": "special"})),
12399 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12400 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12401 lsp::InsertReplaceEdit {
12402 new_text: "abs".to_string(),
12403 insert: lsp::Range::default(),
12404 replace: lsp::Range::default(),
12405 },
12406 )),
12407 ..lsp::CompletionItem::default()
12408 };
12409 let items = iter::once(item_0.clone())
12410 .chain((11..51).map(|i| lsp::CompletionItem {
12411 label: format!("item_{}", i),
12412 insert_text: Some(format!("item_{}", i)),
12413 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12414 ..lsp::CompletionItem::default()
12415 }))
12416 .collect::<Vec<_>>();
12417
12418 let default_commit_characters = vec!["?".to_string()];
12419 let default_data = json!({ "default": "data"});
12420 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12421 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12422 let default_edit_range = lsp::Range {
12423 start: lsp::Position {
12424 line: 0,
12425 character: 5,
12426 },
12427 end: lsp::Position {
12428 line: 0,
12429 character: 5,
12430 },
12431 };
12432
12433 let mut cx = EditorLspTestContext::new_rust(
12434 lsp::ServerCapabilities {
12435 completion_provider: Some(lsp::CompletionOptions {
12436 trigger_characters: Some(vec![".".to_string()]),
12437 resolve_provider: Some(true),
12438 ..Default::default()
12439 }),
12440 ..Default::default()
12441 },
12442 cx,
12443 )
12444 .await;
12445
12446 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12447 cx.simulate_keystroke(".");
12448
12449 let completion_data = default_data.clone();
12450 let completion_characters = default_commit_characters.clone();
12451 let completion_items = items.clone();
12452 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12453 let default_data = completion_data.clone();
12454 let default_commit_characters = completion_characters.clone();
12455 let items = completion_items.clone();
12456 async move {
12457 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12458 items,
12459 item_defaults: Some(lsp::CompletionListItemDefaults {
12460 data: Some(default_data.clone()),
12461 commit_characters: Some(default_commit_characters.clone()),
12462 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12463 default_edit_range,
12464 )),
12465 insert_text_format: Some(default_insert_text_format),
12466 insert_text_mode: Some(default_insert_text_mode),
12467 }),
12468 ..lsp::CompletionList::default()
12469 })))
12470 }
12471 })
12472 .next()
12473 .await;
12474
12475 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12476 cx.lsp
12477 .server
12478 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12479 let closure_resolved_items = resolved_items.clone();
12480 move |item_to_resolve, _| {
12481 let closure_resolved_items = closure_resolved_items.clone();
12482 async move {
12483 closure_resolved_items.lock().push(item_to_resolve.clone());
12484 Ok(item_to_resolve)
12485 }
12486 }
12487 })
12488 .detach();
12489
12490 cx.condition(|editor, _| editor.context_menu_visible())
12491 .await;
12492 cx.run_until_parked();
12493 cx.update_editor(|editor, _, _| {
12494 let menu = editor.context_menu.borrow_mut();
12495 match menu.as_ref().expect("should have the completions menu") {
12496 CodeContextMenu::Completions(completions_menu) => {
12497 assert_eq!(
12498 completions_menu
12499 .entries
12500 .borrow()
12501 .iter()
12502 .map(|mat| mat.string.clone())
12503 .collect::<Vec<String>>(),
12504 items
12505 .iter()
12506 .map(|completion| completion.label.clone())
12507 .collect::<Vec<String>>()
12508 );
12509 }
12510 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12511 }
12512 });
12513 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12514 // with 4 from the end.
12515 assert_eq!(
12516 *resolved_items.lock(),
12517 [&items[0..16], &items[items.len() - 4..items.len()]]
12518 .concat()
12519 .iter()
12520 .cloned()
12521 .map(|mut item| {
12522 if item.data.is_none() {
12523 item.data = Some(default_data.clone());
12524 }
12525 item
12526 })
12527 .collect::<Vec<lsp::CompletionItem>>(),
12528 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12529 );
12530 resolved_items.lock().clear();
12531
12532 cx.update_editor(|editor, window, cx| {
12533 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12534 });
12535 cx.run_until_parked();
12536 // Completions that have already been resolved are skipped.
12537 assert_eq!(
12538 *resolved_items.lock(),
12539 items[items.len() - 16..items.len() - 4]
12540 .iter()
12541 .cloned()
12542 .map(|mut item| {
12543 if item.data.is_none() {
12544 item.data = Some(default_data.clone());
12545 }
12546 item
12547 })
12548 .collect::<Vec<lsp::CompletionItem>>()
12549 );
12550 resolved_items.lock().clear();
12551}
12552
12553#[gpui::test]
12554async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12555 init_test(cx, |_| {});
12556
12557 let mut cx = EditorLspTestContext::new(
12558 Language::new(
12559 LanguageConfig {
12560 matcher: LanguageMatcher {
12561 path_suffixes: vec!["jsx".into()],
12562 ..Default::default()
12563 },
12564 overrides: [(
12565 "element".into(),
12566 LanguageConfigOverride {
12567 word_characters: Override::Set(['-'].into_iter().collect()),
12568 ..Default::default()
12569 },
12570 )]
12571 .into_iter()
12572 .collect(),
12573 ..Default::default()
12574 },
12575 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12576 )
12577 .with_override_query("(jsx_self_closing_element) @element")
12578 .unwrap(),
12579 lsp::ServerCapabilities {
12580 completion_provider: Some(lsp::CompletionOptions {
12581 trigger_characters: Some(vec![":".to_string()]),
12582 ..Default::default()
12583 }),
12584 ..Default::default()
12585 },
12586 cx,
12587 )
12588 .await;
12589
12590 cx.lsp
12591 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12592 Ok(Some(lsp::CompletionResponse::Array(vec![
12593 lsp::CompletionItem {
12594 label: "bg-blue".into(),
12595 ..Default::default()
12596 },
12597 lsp::CompletionItem {
12598 label: "bg-red".into(),
12599 ..Default::default()
12600 },
12601 lsp::CompletionItem {
12602 label: "bg-yellow".into(),
12603 ..Default::default()
12604 },
12605 ])))
12606 });
12607
12608 cx.set_state(r#"<p class="bgˇ" />"#);
12609
12610 // Trigger completion when typing a dash, because the dash is an extra
12611 // word character in the 'element' scope, which contains the cursor.
12612 cx.simulate_keystroke("-");
12613 cx.executor().run_until_parked();
12614 cx.update_editor(|editor, _, _| {
12615 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12616 {
12617 assert_eq!(
12618 completion_menu_entries(&menu),
12619 &["bg-red", "bg-blue", "bg-yellow"]
12620 );
12621 } else {
12622 panic!("expected completion menu to be open");
12623 }
12624 });
12625
12626 cx.simulate_keystroke("l");
12627 cx.executor().run_until_parked();
12628 cx.update_editor(|editor, _, _| {
12629 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12630 {
12631 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12632 } else {
12633 panic!("expected completion menu to be open");
12634 }
12635 });
12636
12637 // When filtering completions, consider the character after the '-' to
12638 // be the start of a subword.
12639 cx.set_state(r#"<p class="yelˇ" />"#);
12640 cx.simulate_keystroke("l");
12641 cx.executor().run_until_parked();
12642 cx.update_editor(|editor, _, _| {
12643 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12644 {
12645 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12646 } else {
12647 panic!("expected completion menu to be open");
12648 }
12649 });
12650}
12651
12652fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12653 let entries = menu.entries.borrow();
12654 entries.iter().map(|mat| mat.string.clone()).collect()
12655}
12656
12657#[gpui::test]
12658async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12659 init_test(cx, |settings| {
12660 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12661 FormatterList(vec![Formatter::Prettier].into()),
12662 ))
12663 });
12664
12665 let fs = FakeFs::new(cx.executor());
12666 fs.insert_file(path!("/file.ts"), Default::default()).await;
12667
12668 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12669 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12670
12671 language_registry.add(Arc::new(Language::new(
12672 LanguageConfig {
12673 name: "TypeScript".into(),
12674 matcher: LanguageMatcher {
12675 path_suffixes: vec!["ts".to_string()],
12676 ..Default::default()
12677 },
12678 ..Default::default()
12679 },
12680 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12681 )));
12682 update_test_language_settings(cx, |settings| {
12683 settings.defaults.prettier = Some(PrettierSettings {
12684 allowed: true,
12685 ..PrettierSettings::default()
12686 });
12687 });
12688
12689 let test_plugin = "test_plugin";
12690 let _ = language_registry.register_fake_lsp(
12691 "TypeScript",
12692 FakeLspAdapter {
12693 prettier_plugins: vec![test_plugin],
12694 ..Default::default()
12695 },
12696 );
12697
12698 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12699 let buffer = project
12700 .update(cx, |project, cx| {
12701 project.open_local_buffer(path!("/file.ts"), cx)
12702 })
12703 .await
12704 .unwrap();
12705
12706 let buffer_text = "one\ntwo\nthree\n";
12707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12708 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12709 editor.update_in(cx, |editor, window, cx| {
12710 editor.set_text(buffer_text, window, cx)
12711 });
12712
12713 editor
12714 .update_in(cx, |editor, window, cx| {
12715 editor.perform_format(
12716 project.clone(),
12717 FormatTrigger::Manual,
12718 FormatTarget::Buffers,
12719 window,
12720 cx,
12721 )
12722 })
12723 .unwrap()
12724 .await;
12725 assert_eq!(
12726 editor.update(cx, |editor, cx| editor.text(cx)),
12727 buffer_text.to_string() + prettier_format_suffix,
12728 "Test prettier formatting was not applied to the original buffer text",
12729 );
12730
12731 update_test_language_settings(cx, |settings| {
12732 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12733 });
12734 let format = editor.update_in(cx, |editor, window, cx| {
12735 editor.perform_format(
12736 project.clone(),
12737 FormatTrigger::Manual,
12738 FormatTarget::Buffers,
12739 window,
12740 cx,
12741 )
12742 });
12743 format.await.unwrap();
12744 assert_eq!(
12745 editor.update(cx, |editor, cx| editor.text(cx)),
12746 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12747 "Autoformatting (via test prettier) was not applied to the original buffer text",
12748 );
12749}
12750
12751#[gpui::test]
12752async fn test_addition_reverts(cx: &mut TestAppContext) {
12753 init_test(cx, |_| {});
12754 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12755 let base_text = indoc! {r#"
12756 struct Row;
12757 struct Row1;
12758 struct Row2;
12759
12760 struct Row4;
12761 struct Row5;
12762 struct Row6;
12763
12764 struct Row8;
12765 struct Row9;
12766 struct Row10;"#};
12767
12768 // When addition hunks are not adjacent to carets, no hunk revert is performed
12769 assert_hunk_revert(
12770 indoc! {r#"struct Row;
12771 struct Row1;
12772 struct Row1.1;
12773 struct Row1.2;
12774 struct Row2;ˇ
12775
12776 struct Row4;
12777 struct Row5;
12778 struct Row6;
12779
12780 struct Row8;
12781 ˇstruct Row9;
12782 struct Row9.1;
12783 struct Row9.2;
12784 struct Row9.3;
12785 struct Row10;"#},
12786 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12787 indoc! {r#"struct Row;
12788 struct Row1;
12789 struct Row1.1;
12790 struct Row1.2;
12791 struct Row2;ˇ
12792
12793 struct Row4;
12794 struct Row5;
12795 struct Row6;
12796
12797 struct Row8;
12798 ˇstruct Row9;
12799 struct Row9.1;
12800 struct Row9.2;
12801 struct Row9.3;
12802 struct Row10;"#},
12803 base_text,
12804 &mut cx,
12805 );
12806 // Same for selections
12807 assert_hunk_revert(
12808 indoc! {r#"struct Row;
12809 struct Row1;
12810 struct Row2;
12811 struct Row2.1;
12812 struct Row2.2;
12813 «ˇ
12814 struct Row4;
12815 struct» Row5;
12816 «struct Row6;
12817 ˇ»
12818 struct Row9.1;
12819 struct Row9.2;
12820 struct Row9.3;
12821 struct Row8;
12822 struct Row9;
12823 struct Row10;"#},
12824 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12825 indoc! {r#"struct Row;
12826 struct Row1;
12827 struct Row2;
12828 struct Row2.1;
12829 struct Row2.2;
12830 «ˇ
12831 struct Row4;
12832 struct» Row5;
12833 «struct Row6;
12834 ˇ»
12835 struct Row9.1;
12836 struct Row9.2;
12837 struct Row9.3;
12838 struct Row8;
12839 struct Row9;
12840 struct Row10;"#},
12841 base_text,
12842 &mut cx,
12843 );
12844
12845 // When carets and selections intersect the addition hunks, those are reverted.
12846 // Adjacent carets got merged.
12847 assert_hunk_revert(
12848 indoc! {r#"struct Row;
12849 ˇ// something on the top
12850 struct Row1;
12851 struct Row2;
12852 struct Roˇw3.1;
12853 struct Row2.2;
12854 struct Row2.3;ˇ
12855
12856 struct Row4;
12857 struct ˇRow5.1;
12858 struct Row5.2;
12859 struct «Rowˇ»5.3;
12860 struct Row5;
12861 struct Row6;
12862 ˇ
12863 struct Row9.1;
12864 struct «Rowˇ»9.2;
12865 struct «ˇRow»9.3;
12866 struct Row8;
12867 struct Row9;
12868 «ˇ// something on bottom»
12869 struct Row10;"#},
12870 vec![
12871 DiffHunkStatusKind::Added,
12872 DiffHunkStatusKind::Added,
12873 DiffHunkStatusKind::Added,
12874 DiffHunkStatusKind::Added,
12875 DiffHunkStatusKind::Added,
12876 ],
12877 indoc! {r#"struct Row;
12878 ˇstruct Row1;
12879 struct Row2;
12880 ˇ
12881 struct Row4;
12882 ˇstruct Row5;
12883 struct Row6;
12884 ˇ
12885 ˇstruct Row8;
12886 struct Row9;
12887 ˇstruct Row10;"#},
12888 base_text,
12889 &mut cx,
12890 );
12891}
12892
12893#[gpui::test]
12894async fn test_modification_reverts(cx: &mut TestAppContext) {
12895 init_test(cx, |_| {});
12896 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12897 let base_text = indoc! {r#"
12898 struct Row;
12899 struct Row1;
12900 struct Row2;
12901
12902 struct Row4;
12903 struct Row5;
12904 struct Row6;
12905
12906 struct Row8;
12907 struct Row9;
12908 struct Row10;"#};
12909
12910 // Modification hunks behave the same as the addition ones.
12911 assert_hunk_revert(
12912 indoc! {r#"struct Row;
12913 struct Row1;
12914 struct Row33;
12915 ˇ
12916 struct Row4;
12917 struct Row5;
12918 struct Row6;
12919 ˇ
12920 struct Row99;
12921 struct Row9;
12922 struct Row10;"#},
12923 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12924 indoc! {r#"struct Row;
12925 struct Row1;
12926 struct Row33;
12927 ˇ
12928 struct Row4;
12929 struct Row5;
12930 struct Row6;
12931 ˇ
12932 struct Row99;
12933 struct Row9;
12934 struct Row10;"#},
12935 base_text,
12936 &mut cx,
12937 );
12938 assert_hunk_revert(
12939 indoc! {r#"struct Row;
12940 struct Row1;
12941 struct Row33;
12942 «ˇ
12943 struct Row4;
12944 struct» Row5;
12945 «struct Row6;
12946 ˇ»
12947 struct Row99;
12948 struct Row9;
12949 struct Row10;"#},
12950 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12951 indoc! {r#"struct Row;
12952 struct Row1;
12953 struct Row33;
12954 «ˇ
12955 struct Row4;
12956 struct» Row5;
12957 «struct Row6;
12958 ˇ»
12959 struct Row99;
12960 struct Row9;
12961 struct Row10;"#},
12962 base_text,
12963 &mut cx,
12964 );
12965
12966 assert_hunk_revert(
12967 indoc! {r#"ˇstruct Row1.1;
12968 struct Row1;
12969 «ˇstr»uct Row22;
12970
12971 struct ˇRow44;
12972 struct Row5;
12973 struct «Rˇ»ow66;ˇ
12974
12975 «struˇ»ct Row88;
12976 struct Row9;
12977 struct Row1011;ˇ"#},
12978 vec![
12979 DiffHunkStatusKind::Modified,
12980 DiffHunkStatusKind::Modified,
12981 DiffHunkStatusKind::Modified,
12982 DiffHunkStatusKind::Modified,
12983 DiffHunkStatusKind::Modified,
12984 DiffHunkStatusKind::Modified,
12985 ],
12986 indoc! {r#"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 base_text,
12998 &mut cx,
12999 );
13000}
13001
13002#[gpui::test]
13003async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13004 init_test(cx, |_| {});
13005 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13006 let base_text = indoc! {r#"
13007 one
13008
13009 two
13010 three
13011 "#};
13012
13013 cx.set_head_text(base_text);
13014 cx.set_state("\nˇ\n");
13015 cx.executor().run_until_parked();
13016 cx.update_editor(|editor, _window, cx| {
13017 editor.expand_selected_diff_hunks(cx);
13018 });
13019 cx.executor().run_until_parked();
13020 cx.update_editor(|editor, window, cx| {
13021 editor.backspace(&Default::default(), window, cx);
13022 });
13023 cx.run_until_parked();
13024 cx.assert_state_with_diff(
13025 indoc! {r#"
13026
13027 - two
13028 - threeˇ
13029 +
13030 "#}
13031 .to_string(),
13032 );
13033}
13034
13035#[gpui::test]
13036async fn test_deletion_reverts(cx: &mut TestAppContext) {
13037 init_test(cx, |_| {});
13038 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13039 let base_text = indoc! {r#"struct Row;
13040struct Row1;
13041struct Row2;
13042
13043struct Row4;
13044struct Row5;
13045struct Row6;
13046
13047struct Row8;
13048struct Row9;
13049struct Row10;"#};
13050
13051 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13052 assert_hunk_revert(
13053 indoc! {r#"struct Row;
13054 struct Row2;
13055
13056 ˇstruct Row4;
13057 struct Row5;
13058 struct Row6;
13059 ˇ
13060 struct Row8;
13061 struct Row10;"#},
13062 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13063 indoc! {r#"struct Row;
13064 struct Row2;
13065
13066 ˇstruct Row4;
13067 struct Row5;
13068 struct Row6;
13069 ˇ
13070 struct Row8;
13071 struct Row10;"#},
13072 base_text,
13073 &mut cx,
13074 );
13075 assert_hunk_revert(
13076 indoc! {r#"struct Row;
13077 struct Row2;
13078
13079 «ˇstruct Row4;
13080 struct» Row5;
13081 «struct Row6;
13082 ˇ»
13083 struct Row8;
13084 struct Row10;"#},
13085 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13086 indoc! {r#"struct Row;
13087 struct Row2;
13088
13089 «ˇstruct Row4;
13090 struct» Row5;
13091 «struct Row6;
13092 ˇ»
13093 struct Row8;
13094 struct Row10;"#},
13095 base_text,
13096 &mut cx,
13097 );
13098
13099 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13100 assert_hunk_revert(
13101 indoc! {r#"struct Row;
13102 ˇstruct Row2;
13103
13104 struct Row4;
13105 struct Row5;
13106 struct Row6;
13107
13108 struct Row8;ˇ
13109 struct Row10;"#},
13110 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13111 indoc! {r#"struct Row;
13112 struct Row1;
13113 ˇstruct Row2;
13114
13115 struct Row4;
13116 struct Row5;
13117 struct Row6;
13118
13119 struct Row8;ˇ
13120 struct Row9;
13121 struct Row10;"#},
13122 base_text,
13123 &mut cx,
13124 );
13125 assert_hunk_revert(
13126 indoc! {r#"struct Row;
13127 struct Row2«ˇ;
13128 struct Row4;
13129 struct» Row5;
13130 «struct Row6;
13131
13132 struct Row8;ˇ»
13133 struct Row10;"#},
13134 vec![
13135 DiffHunkStatusKind::Deleted,
13136 DiffHunkStatusKind::Deleted,
13137 DiffHunkStatusKind::Deleted,
13138 ],
13139 indoc! {r#"struct Row;
13140 struct Row1;
13141 struct Row2«ˇ;
13142
13143 struct Row4;
13144 struct» Row5;
13145 «struct Row6;
13146
13147 struct Row8;ˇ»
13148 struct Row9;
13149 struct Row10;"#},
13150 base_text,
13151 &mut cx,
13152 );
13153}
13154
13155#[gpui::test]
13156async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13157 init_test(cx, |_| {});
13158
13159 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13160 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13161 let base_text_3 =
13162 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13163
13164 let text_1 = edit_first_char_of_every_line(base_text_1);
13165 let text_2 = edit_first_char_of_every_line(base_text_2);
13166 let text_3 = edit_first_char_of_every_line(base_text_3);
13167
13168 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13169 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13170 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13171
13172 let multibuffer = cx.new(|cx| {
13173 let mut multibuffer = MultiBuffer::new(ReadWrite);
13174 multibuffer.push_excerpts(
13175 buffer_1.clone(),
13176 [
13177 ExcerptRange {
13178 context: Point::new(0, 0)..Point::new(3, 0),
13179 primary: None,
13180 },
13181 ExcerptRange {
13182 context: Point::new(5, 0)..Point::new(7, 0),
13183 primary: None,
13184 },
13185 ExcerptRange {
13186 context: Point::new(9, 0)..Point::new(10, 4),
13187 primary: None,
13188 },
13189 ],
13190 cx,
13191 );
13192 multibuffer.push_excerpts(
13193 buffer_2.clone(),
13194 [
13195 ExcerptRange {
13196 context: Point::new(0, 0)..Point::new(3, 0),
13197 primary: None,
13198 },
13199 ExcerptRange {
13200 context: Point::new(5, 0)..Point::new(7, 0),
13201 primary: None,
13202 },
13203 ExcerptRange {
13204 context: Point::new(9, 0)..Point::new(10, 4),
13205 primary: None,
13206 },
13207 ],
13208 cx,
13209 );
13210 multibuffer.push_excerpts(
13211 buffer_3.clone(),
13212 [
13213 ExcerptRange {
13214 context: Point::new(0, 0)..Point::new(3, 0),
13215 primary: None,
13216 },
13217 ExcerptRange {
13218 context: Point::new(5, 0)..Point::new(7, 0),
13219 primary: None,
13220 },
13221 ExcerptRange {
13222 context: Point::new(9, 0)..Point::new(10, 4),
13223 primary: None,
13224 },
13225 ],
13226 cx,
13227 );
13228 multibuffer
13229 });
13230
13231 let fs = FakeFs::new(cx.executor());
13232 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13233 let (editor, cx) = cx
13234 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13235 editor.update_in(cx, |editor, _window, cx| {
13236 for (buffer, diff_base) in [
13237 (buffer_1.clone(), base_text_1),
13238 (buffer_2.clone(), base_text_2),
13239 (buffer_3.clone(), base_text_3),
13240 ] {
13241 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13242 editor
13243 .buffer
13244 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13245 }
13246 });
13247 cx.executor().run_until_parked();
13248
13249 editor.update_in(cx, |editor, window, cx| {
13250 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}");
13251 editor.select_all(&SelectAll, window, cx);
13252 editor.git_restore(&Default::default(), window, cx);
13253 });
13254 cx.executor().run_until_parked();
13255
13256 // When all ranges are selected, all buffer hunks are reverted.
13257 editor.update(cx, |editor, cx| {
13258 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");
13259 });
13260 buffer_1.update(cx, |buffer, _| {
13261 assert_eq!(buffer.text(), base_text_1);
13262 });
13263 buffer_2.update(cx, |buffer, _| {
13264 assert_eq!(buffer.text(), base_text_2);
13265 });
13266 buffer_3.update(cx, |buffer, _| {
13267 assert_eq!(buffer.text(), base_text_3);
13268 });
13269
13270 editor.update_in(cx, |editor, window, cx| {
13271 editor.undo(&Default::default(), window, cx);
13272 });
13273
13274 editor.update_in(cx, |editor, window, cx| {
13275 editor.change_selections(None, window, cx, |s| {
13276 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13277 });
13278 editor.git_restore(&Default::default(), window, cx);
13279 });
13280
13281 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13282 // but not affect buffer_2 and its related excerpts.
13283 editor.update(cx, |editor, cx| {
13284 assert_eq!(
13285 editor.text(cx),
13286 "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}"
13287 );
13288 });
13289 buffer_1.update(cx, |buffer, _| {
13290 assert_eq!(buffer.text(), base_text_1);
13291 });
13292 buffer_2.update(cx, |buffer, _| {
13293 assert_eq!(
13294 buffer.text(),
13295 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13296 );
13297 });
13298 buffer_3.update(cx, |buffer, _| {
13299 assert_eq!(
13300 buffer.text(),
13301 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13302 );
13303 });
13304
13305 fn edit_first_char_of_every_line(text: &str) -> String {
13306 text.split('\n')
13307 .map(|line| format!("X{}", &line[1..]))
13308 .collect::<Vec<_>>()
13309 .join("\n")
13310 }
13311}
13312
13313#[gpui::test]
13314async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13315 init_test(cx, |_| {});
13316
13317 let cols = 4;
13318 let rows = 10;
13319 let sample_text_1 = sample_text(rows, cols, 'a');
13320 assert_eq!(
13321 sample_text_1,
13322 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13323 );
13324 let sample_text_2 = sample_text(rows, cols, 'l');
13325 assert_eq!(
13326 sample_text_2,
13327 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13328 );
13329 let sample_text_3 = sample_text(rows, cols, 'v');
13330 assert_eq!(
13331 sample_text_3,
13332 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13333 );
13334
13335 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13336 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13337 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13338
13339 let multi_buffer = cx.new(|cx| {
13340 let mut multibuffer = MultiBuffer::new(ReadWrite);
13341 multibuffer.push_excerpts(
13342 buffer_1.clone(),
13343 [
13344 ExcerptRange {
13345 context: Point::new(0, 0)..Point::new(3, 0),
13346 primary: None,
13347 },
13348 ExcerptRange {
13349 context: Point::new(5, 0)..Point::new(7, 0),
13350 primary: None,
13351 },
13352 ExcerptRange {
13353 context: Point::new(9, 0)..Point::new(10, 4),
13354 primary: None,
13355 },
13356 ],
13357 cx,
13358 );
13359 multibuffer.push_excerpts(
13360 buffer_2.clone(),
13361 [
13362 ExcerptRange {
13363 context: Point::new(0, 0)..Point::new(3, 0),
13364 primary: None,
13365 },
13366 ExcerptRange {
13367 context: Point::new(5, 0)..Point::new(7, 0),
13368 primary: None,
13369 },
13370 ExcerptRange {
13371 context: Point::new(9, 0)..Point::new(10, 4),
13372 primary: None,
13373 },
13374 ],
13375 cx,
13376 );
13377 multibuffer.push_excerpts(
13378 buffer_3.clone(),
13379 [
13380 ExcerptRange {
13381 context: Point::new(0, 0)..Point::new(3, 0),
13382 primary: None,
13383 },
13384 ExcerptRange {
13385 context: Point::new(5, 0)..Point::new(7, 0),
13386 primary: None,
13387 },
13388 ExcerptRange {
13389 context: Point::new(9, 0)..Point::new(10, 4),
13390 primary: None,
13391 },
13392 ],
13393 cx,
13394 );
13395 multibuffer
13396 });
13397
13398 let fs = FakeFs::new(cx.executor());
13399 fs.insert_tree(
13400 "/a",
13401 json!({
13402 "main.rs": sample_text_1,
13403 "other.rs": sample_text_2,
13404 "lib.rs": sample_text_3,
13405 }),
13406 )
13407 .await;
13408 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13409 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13410 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13411 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13412 Editor::new(
13413 EditorMode::Full,
13414 multi_buffer,
13415 Some(project.clone()),
13416 true,
13417 window,
13418 cx,
13419 )
13420 });
13421 let multibuffer_item_id = workspace
13422 .update(cx, |workspace, window, cx| {
13423 assert!(
13424 workspace.active_item(cx).is_none(),
13425 "active item should be None before the first item is added"
13426 );
13427 workspace.add_item_to_active_pane(
13428 Box::new(multi_buffer_editor.clone()),
13429 None,
13430 true,
13431 window,
13432 cx,
13433 );
13434 let active_item = workspace
13435 .active_item(cx)
13436 .expect("should have an active item after adding the multi buffer");
13437 assert!(
13438 !active_item.is_singleton(cx),
13439 "A multi buffer was expected to active after adding"
13440 );
13441 active_item.item_id()
13442 })
13443 .unwrap();
13444 cx.executor().run_until_parked();
13445
13446 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13447 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13448 s.select_ranges(Some(1..2))
13449 });
13450 editor.open_excerpts(&OpenExcerpts, window, cx);
13451 });
13452 cx.executor().run_until_parked();
13453 let first_item_id = workspace
13454 .update(cx, |workspace, window, cx| {
13455 let active_item = workspace
13456 .active_item(cx)
13457 .expect("should have an active item after navigating into the 1st buffer");
13458 let first_item_id = active_item.item_id();
13459 assert_ne!(
13460 first_item_id, multibuffer_item_id,
13461 "Should navigate into the 1st buffer and activate it"
13462 );
13463 assert!(
13464 active_item.is_singleton(cx),
13465 "New active item should be a singleton buffer"
13466 );
13467 assert_eq!(
13468 active_item
13469 .act_as::<Editor>(cx)
13470 .expect("should have navigated into an editor for the 1st buffer")
13471 .read(cx)
13472 .text(cx),
13473 sample_text_1
13474 );
13475
13476 workspace
13477 .go_back(workspace.active_pane().downgrade(), window, cx)
13478 .detach_and_log_err(cx);
13479
13480 first_item_id
13481 })
13482 .unwrap();
13483 cx.executor().run_until_parked();
13484 workspace
13485 .update(cx, |workspace, _, cx| {
13486 let active_item = workspace
13487 .active_item(cx)
13488 .expect("should have an active item after navigating back");
13489 assert_eq!(
13490 active_item.item_id(),
13491 multibuffer_item_id,
13492 "Should navigate back to the multi buffer"
13493 );
13494 assert!(!active_item.is_singleton(cx));
13495 })
13496 .unwrap();
13497
13498 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13499 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13500 s.select_ranges(Some(39..40))
13501 });
13502 editor.open_excerpts(&OpenExcerpts, window, cx);
13503 });
13504 cx.executor().run_until_parked();
13505 let second_item_id = workspace
13506 .update(cx, |workspace, window, cx| {
13507 let active_item = workspace
13508 .active_item(cx)
13509 .expect("should have an active item after navigating into the 2nd buffer");
13510 let second_item_id = active_item.item_id();
13511 assert_ne!(
13512 second_item_id, multibuffer_item_id,
13513 "Should navigate away from the multibuffer"
13514 );
13515 assert_ne!(
13516 second_item_id, first_item_id,
13517 "Should navigate into the 2nd buffer and activate it"
13518 );
13519 assert!(
13520 active_item.is_singleton(cx),
13521 "New active item should be a singleton buffer"
13522 );
13523 assert_eq!(
13524 active_item
13525 .act_as::<Editor>(cx)
13526 .expect("should have navigated into an editor")
13527 .read(cx)
13528 .text(cx),
13529 sample_text_2
13530 );
13531
13532 workspace
13533 .go_back(workspace.active_pane().downgrade(), window, cx)
13534 .detach_and_log_err(cx);
13535
13536 second_item_id
13537 })
13538 .unwrap();
13539 cx.executor().run_until_parked();
13540 workspace
13541 .update(cx, |workspace, _, cx| {
13542 let active_item = workspace
13543 .active_item(cx)
13544 .expect("should have an active item after navigating back from the 2nd buffer");
13545 assert_eq!(
13546 active_item.item_id(),
13547 multibuffer_item_id,
13548 "Should navigate back from the 2nd buffer to the multi buffer"
13549 );
13550 assert!(!active_item.is_singleton(cx));
13551 })
13552 .unwrap();
13553
13554 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13555 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13556 s.select_ranges(Some(70..70))
13557 });
13558 editor.open_excerpts(&OpenExcerpts, window, cx);
13559 });
13560 cx.executor().run_until_parked();
13561 workspace
13562 .update(cx, |workspace, window, cx| {
13563 let active_item = workspace
13564 .active_item(cx)
13565 .expect("should have an active item after navigating into the 3rd buffer");
13566 let third_item_id = active_item.item_id();
13567 assert_ne!(
13568 third_item_id, multibuffer_item_id,
13569 "Should navigate into the 3rd buffer and activate it"
13570 );
13571 assert_ne!(third_item_id, first_item_id);
13572 assert_ne!(third_item_id, second_item_id);
13573 assert!(
13574 active_item.is_singleton(cx),
13575 "New active item should be a singleton buffer"
13576 );
13577 assert_eq!(
13578 active_item
13579 .act_as::<Editor>(cx)
13580 .expect("should have navigated into an editor")
13581 .read(cx)
13582 .text(cx),
13583 sample_text_3
13584 );
13585
13586 workspace
13587 .go_back(workspace.active_pane().downgrade(), window, cx)
13588 .detach_and_log_err(cx);
13589 })
13590 .unwrap();
13591 cx.executor().run_until_parked();
13592 workspace
13593 .update(cx, |workspace, _, cx| {
13594 let active_item = workspace
13595 .active_item(cx)
13596 .expect("should have an active item after navigating back from the 3rd buffer");
13597 assert_eq!(
13598 active_item.item_id(),
13599 multibuffer_item_id,
13600 "Should navigate back from the 3rd buffer to the multi buffer"
13601 );
13602 assert!(!active_item.is_singleton(cx));
13603 })
13604 .unwrap();
13605}
13606
13607#[gpui::test]
13608async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13609 init_test(cx, |_| {});
13610
13611 let mut cx = EditorTestContext::new(cx).await;
13612
13613 let diff_base = r#"
13614 use some::mod;
13615
13616 const A: u32 = 42;
13617
13618 fn main() {
13619 println!("hello");
13620
13621 println!("world");
13622 }
13623 "#
13624 .unindent();
13625
13626 cx.set_state(
13627 &r#"
13628 use some::modified;
13629
13630 ˇ
13631 fn main() {
13632 println!("hello there");
13633
13634 println!("around the");
13635 println!("world");
13636 }
13637 "#
13638 .unindent(),
13639 );
13640
13641 cx.set_head_text(&diff_base);
13642 executor.run_until_parked();
13643
13644 cx.update_editor(|editor, window, cx| {
13645 editor.go_to_next_hunk(&GoToHunk, window, cx);
13646 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13647 });
13648 executor.run_until_parked();
13649 cx.assert_state_with_diff(
13650 r#"
13651 use some::modified;
13652
13653
13654 fn main() {
13655 - println!("hello");
13656 + ˇ println!("hello there");
13657
13658 println!("around the");
13659 println!("world");
13660 }
13661 "#
13662 .unindent(),
13663 );
13664
13665 cx.update_editor(|editor, window, cx| {
13666 for _ in 0..2 {
13667 editor.go_to_next_hunk(&GoToHunk, window, cx);
13668 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13669 }
13670 });
13671 executor.run_until_parked();
13672 cx.assert_state_with_diff(
13673 r#"
13674 - use some::mod;
13675 + ˇuse some::modified;
13676
13677
13678 fn main() {
13679 - println!("hello");
13680 + println!("hello there");
13681
13682 + println!("around the");
13683 println!("world");
13684 }
13685 "#
13686 .unindent(),
13687 );
13688
13689 cx.update_editor(|editor, window, cx| {
13690 editor.go_to_next_hunk(&GoToHunk, window, cx);
13691 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13692 });
13693 executor.run_until_parked();
13694 cx.assert_state_with_diff(
13695 r#"
13696 - use some::mod;
13697 + use some::modified;
13698
13699 - const A: u32 = 42;
13700 ˇ
13701 fn main() {
13702 - println!("hello");
13703 + println!("hello there");
13704
13705 + println!("around the");
13706 println!("world");
13707 }
13708 "#
13709 .unindent(),
13710 );
13711
13712 cx.update_editor(|editor, window, cx| {
13713 editor.cancel(&Cancel, window, cx);
13714 });
13715
13716 cx.assert_state_with_diff(
13717 r#"
13718 use some::modified;
13719
13720 ˇ
13721 fn main() {
13722 println!("hello there");
13723
13724 println!("around the");
13725 println!("world");
13726 }
13727 "#
13728 .unindent(),
13729 );
13730}
13731
13732#[gpui::test]
13733async fn test_diff_base_change_with_expanded_diff_hunks(
13734 executor: BackgroundExecutor,
13735 cx: &mut TestAppContext,
13736) {
13737 init_test(cx, |_| {});
13738
13739 let mut cx = EditorTestContext::new(cx).await;
13740
13741 let diff_base = r#"
13742 use some::mod1;
13743 use some::mod2;
13744
13745 const A: u32 = 42;
13746 const B: u32 = 42;
13747 const C: u32 = 42;
13748
13749 fn main() {
13750 println!("hello");
13751
13752 println!("world");
13753 }
13754 "#
13755 .unindent();
13756
13757 cx.set_state(
13758 &r#"
13759 use some::mod2;
13760
13761 const A: u32 = 42;
13762 const C: u32 = 42;
13763
13764 fn main(ˇ) {
13765 //println!("hello");
13766
13767 println!("world");
13768 //
13769 //
13770 }
13771 "#
13772 .unindent(),
13773 );
13774
13775 cx.set_head_text(&diff_base);
13776 executor.run_until_parked();
13777
13778 cx.update_editor(|editor, window, cx| {
13779 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13780 });
13781 executor.run_until_parked();
13782 cx.assert_state_with_diff(
13783 r#"
13784 - use some::mod1;
13785 use some::mod2;
13786
13787 const A: u32 = 42;
13788 - const B: u32 = 42;
13789 const C: u32 = 42;
13790
13791 fn main(ˇ) {
13792 - println!("hello");
13793 + //println!("hello");
13794
13795 println!("world");
13796 + //
13797 + //
13798 }
13799 "#
13800 .unindent(),
13801 );
13802
13803 cx.set_head_text("new diff base!");
13804 executor.run_until_parked();
13805 cx.assert_state_with_diff(
13806 r#"
13807 - new diff base!
13808 + use some::mod2;
13809 +
13810 + const A: u32 = 42;
13811 + const C: u32 = 42;
13812 +
13813 + fn main(ˇ) {
13814 + //println!("hello");
13815 +
13816 + println!("world");
13817 + //
13818 + //
13819 + }
13820 "#
13821 .unindent(),
13822 );
13823}
13824
13825#[gpui::test]
13826async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13827 init_test(cx, |_| {});
13828
13829 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13830 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13831 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13832 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13833 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13834 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13835
13836 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13837 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13838 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13839
13840 let multi_buffer = cx.new(|cx| {
13841 let mut multibuffer = MultiBuffer::new(ReadWrite);
13842 multibuffer.push_excerpts(
13843 buffer_1.clone(),
13844 [
13845 ExcerptRange {
13846 context: Point::new(0, 0)..Point::new(3, 0),
13847 primary: None,
13848 },
13849 ExcerptRange {
13850 context: Point::new(5, 0)..Point::new(7, 0),
13851 primary: None,
13852 },
13853 ExcerptRange {
13854 context: Point::new(9, 0)..Point::new(10, 3),
13855 primary: None,
13856 },
13857 ],
13858 cx,
13859 );
13860 multibuffer.push_excerpts(
13861 buffer_2.clone(),
13862 [
13863 ExcerptRange {
13864 context: Point::new(0, 0)..Point::new(3, 0),
13865 primary: None,
13866 },
13867 ExcerptRange {
13868 context: Point::new(5, 0)..Point::new(7, 0),
13869 primary: None,
13870 },
13871 ExcerptRange {
13872 context: Point::new(9, 0)..Point::new(10, 3),
13873 primary: None,
13874 },
13875 ],
13876 cx,
13877 );
13878 multibuffer.push_excerpts(
13879 buffer_3.clone(),
13880 [
13881 ExcerptRange {
13882 context: Point::new(0, 0)..Point::new(3, 0),
13883 primary: None,
13884 },
13885 ExcerptRange {
13886 context: Point::new(5, 0)..Point::new(7, 0),
13887 primary: None,
13888 },
13889 ExcerptRange {
13890 context: Point::new(9, 0)..Point::new(10, 3),
13891 primary: None,
13892 },
13893 ],
13894 cx,
13895 );
13896 multibuffer
13897 });
13898
13899 let editor = cx.add_window(|window, cx| {
13900 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13901 });
13902 editor
13903 .update(cx, |editor, _window, cx| {
13904 for (buffer, diff_base) in [
13905 (buffer_1.clone(), file_1_old),
13906 (buffer_2.clone(), file_2_old),
13907 (buffer_3.clone(), file_3_old),
13908 ] {
13909 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13910 editor
13911 .buffer
13912 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13913 }
13914 })
13915 .unwrap();
13916
13917 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13918 cx.run_until_parked();
13919
13920 cx.assert_editor_state(
13921 &"
13922 ˇaaa
13923 ccc
13924 ddd
13925
13926 ggg
13927 hhh
13928
13929
13930 lll
13931 mmm
13932 NNN
13933
13934 qqq
13935 rrr
13936
13937 uuu
13938 111
13939 222
13940 333
13941
13942 666
13943 777
13944
13945 000
13946 !!!"
13947 .unindent(),
13948 );
13949
13950 cx.update_editor(|editor, window, cx| {
13951 editor.select_all(&SelectAll, window, cx);
13952 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13953 });
13954 cx.executor().run_until_parked();
13955
13956 cx.assert_state_with_diff(
13957 "
13958 «aaa
13959 - bbb
13960 ccc
13961 ddd
13962
13963 ggg
13964 hhh
13965
13966
13967 lll
13968 mmm
13969 - nnn
13970 + NNN
13971
13972 qqq
13973 rrr
13974
13975 uuu
13976 111
13977 222
13978 333
13979
13980 + 666
13981 777
13982
13983 000
13984 !!!ˇ»"
13985 .unindent(),
13986 );
13987}
13988
13989#[gpui::test]
13990async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13991 init_test(cx, |_| {});
13992
13993 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13994 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13995
13996 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13997 let multi_buffer = cx.new(|cx| {
13998 let mut multibuffer = MultiBuffer::new(ReadWrite);
13999 multibuffer.push_excerpts(
14000 buffer.clone(),
14001 [
14002 ExcerptRange {
14003 context: Point::new(0, 0)..Point::new(2, 0),
14004 primary: None,
14005 },
14006 ExcerptRange {
14007 context: Point::new(4, 0)..Point::new(7, 0),
14008 primary: None,
14009 },
14010 ExcerptRange {
14011 context: Point::new(9, 0)..Point::new(10, 0),
14012 primary: None,
14013 },
14014 ],
14015 cx,
14016 );
14017 multibuffer
14018 });
14019
14020 let editor = cx.add_window(|window, cx| {
14021 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
14022 });
14023 editor
14024 .update(cx, |editor, _window, cx| {
14025 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14026 editor
14027 .buffer
14028 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14029 })
14030 .unwrap();
14031
14032 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14033 cx.run_until_parked();
14034
14035 cx.update_editor(|editor, window, cx| {
14036 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14037 });
14038 cx.executor().run_until_parked();
14039
14040 // When the start of a hunk coincides with the start of its excerpt,
14041 // the hunk is expanded. When the start of a a hunk is earlier than
14042 // the start of its excerpt, the hunk is not expanded.
14043 cx.assert_state_with_diff(
14044 "
14045 ˇaaa
14046 - bbb
14047 + BBB
14048
14049 - ddd
14050 - eee
14051 + DDD
14052 + EEE
14053 fff
14054
14055 iii
14056 "
14057 .unindent(),
14058 );
14059}
14060
14061#[gpui::test]
14062async fn test_edits_around_expanded_insertion_hunks(
14063 executor: BackgroundExecutor,
14064 cx: &mut TestAppContext,
14065) {
14066 init_test(cx, |_| {});
14067
14068 let mut cx = EditorTestContext::new(cx).await;
14069
14070 let diff_base = r#"
14071 use some::mod1;
14072 use some::mod2;
14073
14074 const A: u32 = 42;
14075
14076 fn main() {
14077 println!("hello");
14078
14079 println!("world");
14080 }
14081 "#
14082 .unindent();
14083 executor.run_until_parked();
14084 cx.set_state(
14085 &r#"
14086 use some::mod1;
14087 use some::mod2;
14088
14089 const A: u32 = 42;
14090 const B: u32 = 42;
14091 const C: u32 = 42;
14092 ˇ
14093
14094 fn main() {
14095 println!("hello");
14096
14097 println!("world");
14098 }
14099 "#
14100 .unindent(),
14101 );
14102
14103 cx.set_head_text(&diff_base);
14104 executor.run_until_parked();
14105
14106 cx.update_editor(|editor, window, cx| {
14107 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14108 });
14109 executor.run_until_parked();
14110
14111 cx.assert_state_with_diff(
14112 r#"
14113 use some::mod1;
14114 use some::mod2;
14115
14116 const A: u32 = 42;
14117 + const B: u32 = 42;
14118 + const C: u32 = 42;
14119 + ˇ
14120
14121 fn main() {
14122 println!("hello");
14123
14124 println!("world");
14125 }
14126 "#
14127 .unindent(),
14128 );
14129
14130 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14131 executor.run_until_parked();
14132
14133 cx.assert_state_with_diff(
14134 r#"
14135 use some::mod1;
14136 use some::mod2;
14137
14138 const A: u32 = 42;
14139 + const B: u32 = 42;
14140 + const C: u32 = 42;
14141 + const D: u32 = 42;
14142 + ˇ
14143
14144 fn main() {
14145 println!("hello");
14146
14147 println!("world");
14148 }
14149 "#
14150 .unindent(),
14151 );
14152
14153 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14154 executor.run_until_parked();
14155
14156 cx.assert_state_with_diff(
14157 r#"
14158 use some::mod1;
14159 use some::mod2;
14160
14161 const A: u32 = 42;
14162 + const B: u32 = 42;
14163 + const C: u32 = 42;
14164 + const D: u32 = 42;
14165 + const E: u32 = 42;
14166 + ˇ
14167
14168 fn main() {
14169 println!("hello");
14170
14171 println!("world");
14172 }
14173 "#
14174 .unindent(),
14175 );
14176
14177 cx.update_editor(|editor, window, cx| {
14178 editor.delete_line(&DeleteLine, window, cx);
14179 });
14180 executor.run_until_parked();
14181
14182 cx.assert_state_with_diff(
14183 r#"
14184 use some::mod1;
14185 use some::mod2;
14186
14187 const A: u32 = 42;
14188 + const B: u32 = 42;
14189 + const C: u32 = 42;
14190 + const D: u32 = 42;
14191 + const E: u32 = 42;
14192 ˇ
14193 fn main() {
14194 println!("hello");
14195
14196 println!("world");
14197 }
14198 "#
14199 .unindent(),
14200 );
14201
14202 cx.update_editor(|editor, window, cx| {
14203 editor.move_up(&MoveUp, window, cx);
14204 editor.delete_line(&DeleteLine, window, cx);
14205 editor.move_up(&MoveUp, window, cx);
14206 editor.delete_line(&DeleteLine, window, cx);
14207 editor.move_up(&MoveUp, window, cx);
14208 editor.delete_line(&DeleteLine, window, cx);
14209 });
14210 executor.run_until_parked();
14211 cx.assert_state_with_diff(
14212 r#"
14213 use some::mod1;
14214 use some::mod2;
14215
14216 const A: u32 = 42;
14217 + const B: u32 = 42;
14218 ˇ
14219 fn main() {
14220 println!("hello");
14221
14222 println!("world");
14223 }
14224 "#
14225 .unindent(),
14226 );
14227
14228 cx.update_editor(|editor, window, cx| {
14229 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14230 editor.delete_line(&DeleteLine, window, cx);
14231 });
14232 executor.run_until_parked();
14233 cx.assert_state_with_diff(
14234 r#"
14235 ˇ
14236 fn main() {
14237 println!("hello");
14238
14239 println!("world");
14240 }
14241 "#
14242 .unindent(),
14243 );
14244}
14245
14246#[gpui::test]
14247async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14248 init_test(cx, |_| {});
14249
14250 let mut cx = EditorTestContext::new(cx).await;
14251 cx.set_head_text(indoc! { "
14252 one
14253 two
14254 three
14255 four
14256 five
14257 "
14258 });
14259 cx.set_state(indoc! { "
14260 one
14261 ˇthree
14262 five
14263 "});
14264 cx.run_until_parked();
14265 cx.update_editor(|editor, window, cx| {
14266 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14267 });
14268 cx.assert_state_with_diff(
14269 indoc! { "
14270 one
14271 - two
14272 ˇthree
14273 - four
14274 five
14275 "}
14276 .to_string(),
14277 );
14278 cx.update_editor(|editor, window, cx| {
14279 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14280 });
14281
14282 cx.assert_state_with_diff(
14283 indoc! { "
14284 one
14285 ˇthree
14286 five
14287 "}
14288 .to_string(),
14289 );
14290
14291 cx.set_state(indoc! { "
14292 one
14293 ˇTWO
14294 three
14295 four
14296 five
14297 "});
14298 cx.run_until_parked();
14299 cx.update_editor(|editor, window, cx| {
14300 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14301 });
14302
14303 cx.assert_state_with_diff(
14304 indoc! { "
14305 one
14306 - two
14307 + ˇTWO
14308 three
14309 four
14310 five
14311 "}
14312 .to_string(),
14313 );
14314 cx.update_editor(|editor, window, cx| {
14315 editor.move_up(&Default::default(), window, cx);
14316 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14317 });
14318 cx.assert_state_with_diff(
14319 indoc! { "
14320 one
14321 ˇTWO
14322 three
14323 four
14324 five
14325 "}
14326 .to_string(),
14327 );
14328}
14329
14330#[gpui::test]
14331async fn test_edits_around_expanded_deletion_hunks(
14332 executor: BackgroundExecutor,
14333 cx: &mut TestAppContext,
14334) {
14335 init_test(cx, |_| {});
14336
14337 let mut cx = EditorTestContext::new(cx).await;
14338
14339 let diff_base = r#"
14340 use some::mod1;
14341 use some::mod2;
14342
14343 const A: u32 = 42;
14344 const B: u32 = 42;
14345 const C: u32 = 42;
14346
14347
14348 fn main() {
14349 println!("hello");
14350
14351 println!("world");
14352 }
14353 "#
14354 .unindent();
14355 executor.run_until_parked();
14356 cx.set_state(
14357 &r#"
14358 use some::mod1;
14359 use some::mod2;
14360
14361 ˇconst B: u32 = 42;
14362 const C: u32 = 42;
14363
14364
14365 fn main() {
14366 println!("hello");
14367
14368 println!("world");
14369 }
14370 "#
14371 .unindent(),
14372 );
14373
14374 cx.set_head_text(&diff_base);
14375 executor.run_until_parked();
14376
14377 cx.update_editor(|editor, window, cx| {
14378 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14379 });
14380 executor.run_until_parked();
14381
14382 cx.assert_state_with_diff(
14383 r#"
14384 use some::mod1;
14385 use some::mod2;
14386
14387 - const A: u32 = 42;
14388 ˇconst B: u32 = 42;
14389 const C: u32 = 42;
14390
14391
14392 fn main() {
14393 println!("hello");
14394
14395 println!("world");
14396 }
14397 "#
14398 .unindent(),
14399 );
14400
14401 cx.update_editor(|editor, window, cx| {
14402 editor.delete_line(&DeleteLine, window, cx);
14403 });
14404 executor.run_until_parked();
14405 cx.assert_state_with_diff(
14406 r#"
14407 use some::mod1;
14408 use some::mod2;
14409
14410 - const A: u32 = 42;
14411 - const B: u32 = 42;
14412 ˇconst C: u32 = 42;
14413
14414
14415 fn main() {
14416 println!("hello");
14417
14418 println!("world");
14419 }
14420 "#
14421 .unindent(),
14422 );
14423
14424 cx.update_editor(|editor, window, cx| {
14425 editor.delete_line(&DeleteLine, window, cx);
14426 });
14427 executor.run_until_parked();
14428 cx.assert_state_with_diff(
14429 r#"
14430 use some::mod1;
14431 use some::mod2;
14432
14433 - const A: u32 = 42;
14434 - const B: u32 = 42;
14435 - const C: u32 = 42;
14436 ˇ
14437
14438 fn main() {
14439 println!("hello");
14440
14441 println!("world");
14442 }
14443 "#
14444 .unindent(),
14445 );
14446
14447 cx.update_editor(|editor, window, cx| {
14448 editor.handle_input("replacement", window, cx);
14449 });
14450 executor.run_until_parked();
14451 cx.assert_state_with_diff(
14452 r#"
14453 use some::mod1;
14454 use some::mod2;
14455
14456 - const A: u32 = 42;
14457 - const B: u32 = 42;
14458 - const C: u32 = 42;
14459 -
14460 + replacementˇ
14461
14462 fn main() {
14463 println!("hello");
14464
14465 println!("world");
14466 }
14467 "#
14468 .unindent(),
14469 );
14470}
14471
14472#[gpui::test]
14473async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14474 init_test(cx, |_| {});
14475
14476 let mut cx = EditorTestContext::new(cx).await;
14477
14478 let base_text = r#"
14479 one
14480 two
14481 three
14482 four
14483 five
14484 "#
14485 .unindent();
14486 executor.run_until_parked();
14487 cx.set_state(
14488 &r#"
14489 one
14490 two
14491 fˇour
14492 five
14493 "#
14494 .unindent(),
14495 );
14496
14497 cx.set_head_text(&base_text);
14498 executor.run_until_parked();
14499
14500 cx.update_editor(|editor, window, cx| {
14501 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14502 });
14503 executor.run_until_parked();
14504
14505 cx.assert_state_with_diff(
14506 r#"
14507 one
14508 two
14509 - three
14510 fˇour
14511 five
14512 "#
14513 .unindent(),
14514 );
14515
14516 cx.update_editor(|editor, window, cx| {
14517 editor.backspace(&Backspace, window, cx);
14518 editor.backspace(&Backspace, window, cx);
14519 });
14520 executor.run_until_parked();
14521 cx.assert_state_with_diff(
14522 r#"
14523 one
14524 two
14525 - threeˇ
14526 - four
14527 + our
14528 five
14529 "#
14530 .unindent(),
14531 );
14532}
14533
14534#[gpui::test]
14535async fn test_edit_after_expanded_modification_hunk(
14536 executor: BackgroundExecutor,
14537 cx: &mut TestAppContext,
14538) {
14539 init_test(cx, |_| {});
14540
14541 let mut cx = EditorTestContext::new(cx).await;
14542
14543 let diff_base = r#"
14544 use some::mod1;
14545 use some::mod2;
14546
14547 const A: u32 = 42;
14548 const B: u32 = 42;
14549 const C: u32 = 42;
14550 const D: u32 = 42;
14551
14552
14553 fn main() {
14554 println!("hello");
14555
14556 println!("world");
14557 }"#
14558 .unindent();
14559
14560 cx.set_state(
14561 &r#"
14562 use some::mod1;
14563 use some::mod2;
14564
14565 const A: u32 = 42;
14566 const B: u32 = 42;
14567 const C: u32 = 43ˇ
14568 const D: u32 = 42;
14569
14570
14571 fn main() {
14572 println!("hello");
14573
14574 println!("world");
14575 }"#
14576 .unindent(),
14577 );
14578
14579 cx.set_head_text(&diff_base);
14580 executor.run_until_parked();
14581 cx.update_editor(|editor, window, cx| {
14582 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14583 });
14584 executor.run_until_parked();
14585
14586 cx.assert_state_with_diff(
14587 r#"
14588 use some::mod1;
14589 use some::mod2;
14590
14591 const A: u32 = 42;
14592 const B: u32 = 42;
14593 - const C: u32 = 42;
14594 + const C: u32 = 43ˇ
14595 const D: u32 = 42;
14596
14597
14598 fn main() {
14599 println!("hello");
14600
14601 println!("world");
14602 }"#
14603 .unindent(),
14604 );
14605
14606 cx.update_editor(|editor, window, cx| {
14607 editor.handle_input("\nnew_line\n", window, cx);
14608 });
14609 executor.run_until_parked();
14610
14611 cx.assert_state_with_diff(
14612 r#"
14613 use some::mod1;
14614 use some::mod2;
14615
14616 const A: u32 = 42;
14617 const B: u32 = 42;
14618 - const C: u32 = 42;
14619 + const C: u32 = 43
14620 + new_line
14621 + ˇ
14622 const D: u32 = 42;
14623
14624
14625 fn main() {
14626 println!("hello");
14627
14628 println!("world");
14629 }"#
14630 .unindent(),
14631 );
14632}
14633
14634#[gpui::test]
14635async fn test_stage_and_unstage_added_file_hunk(
14636 executor: BackgroundExecutor,
14637 cx: &mut TestAppContext,
14638) {
14639 init_test(cx, |_| {});
14640
14641 let mut cx = EditorTestContext::new(cx).await;
14642 cx.update_editor(|editor, _, cx| {
14643 editor.set_expand_all_diff_hunks(cx);
14644 });
14645
14646 let working_copy = r#"
14647 ˇfn main() {
14648 println!("hello, world!");
14649 }
14650 "#
14651 .unindent();
14652
14653 cx.set_state(&working_copy);
14654 executor.run_until_parked();
14655
14656 cx.assert_state_with_diff(
14657 r#"
14658 + ˇfn main() {
14659 + println!("hello, world!");
14660 + }
14661 "#
14662 .unindent(),
14663 );
14664 cx.assert_index_text(None);
14665
14666 cx.update_editor(|editor, window, cx| {
14667 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14668 });
14669 executor.run_until_parked();
14670 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14671 cx.assert_state_with_diff(
14672 r#"
14673 + ˇfn main() {
14674 + println!("hello, world!");
14675 + }
14676 "#
14677 .unindent(),
14678 );
14679
14680 cx.update_editor(|editor, window, cx| {
14681 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14682 });
14683 executor.run_until_parked();
14684 cx.assert_index_text(None);
14685}
14686
14687async fn setup_indent_guides_editor(
14688 text: &str,
14689 cx: &mut TestAppContext,
14690) -> (BufferId, EditorTestContext) {
14691 init_test(cx, |_| {});
14692
14693 let mut cx = EditorTestContext::new(cx).await;
14694
14695 let buffer_id = cx.update_editor(|editor, window, cx| {
14696 editor.set_text(text, window, cx);
14697 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14698
14699 buffer_ids[0]
14700 });
14701
14702 (buffer_id, cx)
14703}
14704
14705fn assert_indent_guides(
14706 range: Range<u32>,
14707 expected: Vec<IndentGuide>,
14708 active_indices: Option<Vec<usize>>,
14709 cx: &mut EditorTestContext,
14710) {
14711 let indent_guides = cx.update_editor(|editor, window, cx| {
14712 let snapshot = editor.snapshot(window, cx).display_snapshot;
14713 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14714 editor,
14715 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14716 true,
14717 &snapshot,
14718 cx,
14719 );
14720
14721 indent_guides.sort_by(|a, b| {
14722 a.depth.cmp(&b.depth).then(
14723 a.start_row
14724 .cmp(&b.start_row)
14725 .then(a.end_row.cmp(&b.end_row)),
14726 )
14727 });
14728 indent_guides
14729 });
14730
14731 if let Some(expected) = active_indices {
14732 let active_indices = cx.update_editor(|editor, window, cx| {
14733 let snapshot = editor.snapshot(window, cx).display_snapshot;
14734 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14735 });
14736
14737 assert_eq!(
14738 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14739 expected,
14740 "Active indent guide indices do not match"
14741 );
14742 }
14743
14744 assert_eq!(indent_guides, expected, "Indent guides do not match");
14745}
14746
14747fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14748 IndentGuide {
14749 buffer_id,
14750 start_row: MultiBufferRow(start_row),
14751 end_row: MultiBufferRow(end_row),
14752 depth,
14753 tab_size: 4,
14754 settings: IndentGuideSettings {
14755 enabled: true,
14756 line_width: 1,
14757 active_line_width: 1,
14758 ..Default::default()
14759 },
14760 }
14761}
14762
14763#[gpui::test]
14764async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14765 let (buffer_id, mut cx) = setup_indent_guides_editor(
14766 &"
14767 fn main() {
14768 let a = 1;
14769 }"
14770 .unindent(),
14771 cx,
14772 )
14773 .await;
14774
14775 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14776}
14777
14778#[gpui::test]
14779async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14780 let (buffer_id, mut cx) = setup_indent_guides_editor(
14781 &"
14782 fn main() {
14783 let a = 1;
14784 let b = 2;
14785 }"
14786 .unindent(),
14787 cx,
14788 )
14789 .await;
14790
14791 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14792}
14793
14794#[gpui::test]
14795async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14796 let (buffer_id, mut cx) = setup_indent_guides_editor(
14797 &"
14798 fn main() {
14799 let a = 1;
14800 if a == 3 {
14801 let b = 2;
14802 } else {
14803 let c = 3;
14804 }
14805 }"
14806 .unindent(),
14807 cx,
14808 )
14809 .await;
14810
14811 assert_indent_guides(
14812 0..8,
14813 vec![
14814 indent_guide(buffer_id, 1, 6, 0),
14815 indent_guide(buffer_id, 3, 3, 1),
14816 indent_guide(buffer_id, 5, 5, 1),
14817 ],
14818 None,
14819 &mut cx,
14820 );
14821}
14822
14823#[gpui::test]
14824async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14825 let (buffer_id, mut cx) = setup_indent_guides_editor(
14826 &"
14827 fn main() {
14828 let a = 1;
14829 let b = 2;
14830 let c = 3;
14831 }"
14832 .unindent(),
14833 cx,
14834 )
14835 .await;
14836
14837 assert_indent_guides(
14838 0..5,
14839 vec![
14840 indent_guide(buffer_id, 1, 3, 0),
14841 indent_guide(buffer_id, 2, 2, 1),
14842 ],
14843 None,
14844 &mut cx,
14845 );
14846}
14847
14848#[gpui::test]
14849async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14850 let (buffer_id, mut cx) = setup_indent_guides_editor(
14851 &"
14852 fn main() {
14853 let a = 1;
14854
14855 let c = 3;
14856 }"
14857 .unindent(),
14858 cx,
14859 )
14860 .await;
14861
14862 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14863}
14864
14865#[gpui::test]
14866async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14867 let (buffer_id, mut cx) = setup_indent_guides_editor(
14868 &"
14869 fn main() {
14870 let a = 1;
14871
14872 let c = 3;
14873
14874 if a == 3 {
14875 let b = 2;
14876 } else {
14877 let c = 3;
14878 }
14879 }"
14880 .unindent(),
14881 cx,
14882 )
14883 .await;
14884
14885 assert_indent_guides(
14886 0..11,
14887 vec![
14888 indent_guide(buffer_id, 1, 9, 0),
14889 indent_guide(buffer_id, 6, 6, 1),
14890 indent_guide(buffer_id, 8, 8, 1),
14891 ],
14892 None,
14893 &mut cx,
14894 );
14895}
14896
14897#[gpui::test]
14898async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14899 let (buffer_id, mut cx) = setup_indent_guides_editor(
14900 &"
14901 fn main() {
14902 let a = 1;
14903
14904 let c = 3;
14905
14906 if a == 3 {
14907 let b = 2;
14908 } else {
14909 let c = 3;
14910 }
14911 }"
14912 .unindent(),
14913 cx,
14914 )
14915 .await;
14916
14917 assert_indent_guides(
14918 1..11,
14919 vec![
14920 indent_guide(buffer_id, 1, 9, 0),
14921 indent_guide(buffer_id, 6, 6, 1),
14922 indent_guide(buffer_id, 8, 8, 1),
14923 ],
14924 None,
14925 &mut cx,
14926 );
14927}
14928
14929#[gpui::test]
14930async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14931 let (buffer_id, mut cx) = setup_indent_guides_editor(
14932 &"
14933 fn main() {
14934 let a = 1;
14935
14936 let c = 3;
14937
14938 if a == 3 {
14939 let b = 2;
14940 } else {
14941 let c = 3;
14942 }
14943 }"
14944 .unindent(),
14945 cx,
14946 )
14947 .await;
14948
14949 assert_indent_guides(
14950 1..10,
14951 vec![
14952 indent_guide(buffer_id, 1, 9, 0),
14953 indent_guide(buffer_id, 6, 6, 1),
14954 indent_guide(buffer_id, 8, 8, 1),
14955 ],
14956 None,
14957 &mut cx,
14958 );
14959}
14960
14961#[gpui::test]
14962async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14963 let (buffer_id, mut cx) = setup_indent_guides_editor(
14964 &"
14965 block1
14966 block2
14967 block3
14968 block4
14969 block2
14970 block1
14971 block1"
14972 .unindent(),
14973 cx,
14974 )
14975 .await;
14976
14977 assert_indent_guides(
14978 1..10,
14979 vec![
14980 indent_guide(buffer_id, 1, 4, 0),
14981 indent_guide(buffer_id, 2, 3, 1),
14982 indent_guide(buffer_id, 3, 3, 2),
14983 ],
14984 None,
14985 &mut cx,
14986 );
14987}
14988
14989#[gpui::test]
14990async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14991 let (buffer_id, mut cx) = setup_indent_guides_editor(
14992 &"
14993 block1
14994 block2
14995 block3
14996
14997 block1
14998 block1"
14999 .unindent(),
15000 cx,
15001 )
15002 .await;
15003
15004 assert_indent_guides(
15005 0..6,
15006 vec![
15007 indent_guide(buffer_id, 1, 2, 0),
15008 indent_guide(buffer_id, 2, 2, 1),
15009 ],
15010 None,
15011 &mut cx,
15012 );
15013}
15014
15015#[gpui::test]
15016async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15017 let (buffer_id, mut cx) = setup_indent_guides_editor(
15018 &"
15019 block1
15020
15021
15022
15023 block2
15024 "
15025 .unindent(),
15026 cx,
15027 )
15028 .await;
15029
15030 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15031}
15032
15033#[gpui::test]
15034async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15035 let (buffer_id, mut cx) = setup_indent_guides_editor(
15036 &"
15037 def a:
15038 \tb = 3
15039 \tif True:
15040 \t\tc = 4
15041 \t\td = 5
15042 \tprint(b)
15043 "
15044 .unindent(),
15045 cx,
15046 )
15047 .await;
15048
15049 assert_indent_guides(
15050 0..6,
15051 vec![
15052 indent_guide(buffer_id, 1, 6, 0),
15053 indent_guide(buffer_id, 3, 4, 1),
15054 ],
15055 None,
15056 &mut cx,
15057 );
15058}
15059
15060#[gpui::test]
15061async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15062 let (buffer_id, mut cx) = setup_indent_guides_editor(
15063 &"
15064 fn main() {
15065 let a = 1;
15066 }"
15067 .unindent(),
15068 cx,
15069 )
15070 .await;
15071
15072 cx.update_editor(|editor, window, cx| {
15073 editor.change_selections(None, window, cx, |s| {
15074 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15075 });
15076 });
15077
15078 assert_indent_guides(
15079 0..3,
15080 vec![indent_guide(buffer_id, 1, 1, 0)],
15081 Some(vec![0]),
15082 &mut cx,
15083 );
15084}
15085
15086#[gpui::test]
15087async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15088 let (buffer_id, mut cx) = setup_indent_guides_editor(
15089 &"
15090 fn main() {
15091 if 1 == 2 {
15092 let a = 1;
15093 }
15094 }"
15095 .unindent(),
15096 cx,
15097 )
15098 .await;
15099
15100 cx.update_editor(|editor, window, cx| {
15101 editor.change_selections(None, window, cx, |s| {
15102 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15103 });
15104 });
15105
15106 assert_indent_guides(
15107 0..4,
15108 vec![
15109 indent_guide(buffer_id, 1, 3, 0),
15110 indent_guide(buffer_id, 2, 2, 1),
15111 ],
15112 Some(vec![1]),
15113 &mut cx,
15114 );
15115
15116 cx.update_editor(|editor, window, cx| {
15117 editor.change_selections(None, window, cx, |s| {
15118 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15119 });
15120 });
15121
15122 assert_indent_guides(
15123 0..4,
15124 vec![
15125 indent_guide(buffer_id, 1, 3, 0),
15126 indent_guide(buffer_id, 2, 2, 1),
15127 ],
15128 Some(vec![1]),
15129 &mut cx,
15130 );
15131
15132 cx.update_editor(|editor, window, cx| {
15133 editor.change_selections(None, window, cx, |s| {
15134 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15135 });
15136 });
15137
15138 assert_indent_guides(
15139 0..4,
15140 vec![
15141 indent_guide(buffer_id, 1, 3, 0),
15142 indent_guide(buffer_id, 2, 2, 1),
15143 ],
15144 Some(vec![0]),
15145 &mut cx,
15146 );
15147}
15148
15149#[gpui::test]
15150async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15151 let (buffer_id, mut cx) = setup_indent_guides_editor(
15152 &"
15153 fn main() {
15154 let a = 1;
15155
15156 let b = 2;
15157 }"
15158 .unindent(),
15159 cx,
15160 )
15161 .await;
15162
15163 cx.update_editor(|editor, window, cx| {
15164 editor.change_selections(None, window, cx, |s| {
15165 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15166 });
15167 });
15168
15169 assert_indent_guides(
15170 0..5,
15171 vec![indent_guide(buffer_id, 1, 3, 0)],
15172 Some(vec![0]),
15173 &mut cx,
15174 );
15175}
15176
15177#[gpui::test]
15178async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15179 let (buffer_id, mut cx) = setup_indent_guides_editor(
15180 &"
15181 def m:
15182 a = 1
15183 pass"
15184 .unindent(),
15185 cx,
15186 )
15187 .await;
15188
15189 cx.update_editor(|editor, window, cx| {
15190 editor.change_selections(None, window, cx, |s| {
15191 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15192 });
15193 });
15194
15195 assert_indent_guides(
15196 0..3,
15197 vec![indent_guide(buffer_id, 1, 2, 0)],
15198 Some(vec![0]),
15199 &mut cx,
15200 );
15201}
15202
15203#[gpui::test]
15204async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15205 init_test(cx, |_| {});
15206 let mut cx = EditorTestContext::new(cx).await;
15207 let text = indoc! {
15208 "
15209 impl A {
15210 fn b() {
15211 0;
15212 3;
15213 5;
15214 6;
15215 7;
15216 }
15217 }
15218 "
15219 };
15220 let base_text = indoc! {
15221 "
15222 impl A {
15223 fn b() {
15224 0;
15225 1;
15226 2;
15227 3;
15228 4;
15229 }
15230 fn c() {
15231 5;
15232 6;
15233 7;
15234 }
15235 }
15236 "
15237 };
15238
15239 cx.update_editor(|editor, window, cx| {
15240 editor.set_text(text, window, cx);
15241
15242 editor.buffer().update(cx, |multibuffer, cx| {
15243 let buffer = multibuffer.as_singleton().unwrap();
15244 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15245
15246 multibuffer.set_all_diff_hunks_expanded(cx);
15247 multibuffer.add_diff(diff, cx);
15248
15249 buffer.read(cx).remote_id()
15250 })
15251 });
15252 cx.run_until_parked();
15253
15254 cx.assert_state_with_diff(
15255 indoc! { "
15256 impl A {
15257 fn b() {
15258 0;
15259 - 1;
15260 - 2;
15261 3;
15262 - 4;
15263 - }
15264 - fn c() {
15265 5;
15266 6;
15267 7;
15268 }
15269 }
15270 ˇ"
15271 }
15272 .to_string(),
15273 );
15274
15275 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15276 editor
15277 .snapshot(window, cx)
15278 .buffer_snapshot
15279 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15280 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15281 .collect::<Vec<_>>()
15282 });
15283 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15284 assert_eq!(
15285 actual_guides,
15286 vec![
15287 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15288 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15289 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15290 ]
15291 );
15292}
15293
15294#[gpui::test]
15295async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15296 init_test(cx, |_| {});
15297 let mut cx = EditorTestContext::new(cx).await;
15298
15299 let diff_base = r#"
15300 a
15301 b
15302 c
15303 "#
15304 .unindent();
15305
15306 cx.set_state(
15307 &r#"
15308 ˇA
15309 b
15310 C
15311 "#
15312 .unindent(),
15313 );
15314 cx.set_head_text(&diff_base);
15315 cx.update_editor(|editor, window, cx| {
15316 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15317 });
15318 executor.run_until_parked();
15319
15320 let both_hunks_expanded = r#"
15321 - a
15322 + ˇA
15323 b
15324 - c
15325 + C
15326 "#
15327 .unindent();
15328
15329 cx.assert_state_with_diff(both_hunks_expanded.clone());
15330
15331 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15332 let snapshot = editor.snapshot(window, cx);
15333 let hunks = editor
15334 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15335 .collect::<Vec<_>>();
15336 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15337 let buffer_id = hunks[0].buffer_id;
15338 hunks
15339 .into_iter()
15340 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15341 .collect::<Vec<_>>()
15342 });
15343 assert_eq!(hunk_ranges.len(), 2);
15344
15345 cx.update_editor(|editor, _, cx| {
15346 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15347 });
15348 executor.run_until_parked();
15349
15350 let second_hunk_expanded = r#"
15351 ˇA
15352 b
15353 - c
15354 + C
15355 "#
15356 .unindent();
15357
15358 cx.assert_state_with_diff(second_hunk_expanded);
15359
15360 cx.update_editor(|editor, _, cx| {
15361 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15362 });
15363 executor.run_until_parked();
15364
15365 cx.assert_state_with_diff(both_hunks_expanded.clone());
15366
15367 cx.update_editor(|editor, _, cx| {
15368 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15369 });
15370 executor.run_until_parked();
15371
15372 let first_hunk_expanded = r#"
15373 - a
15374 + ˇA
15375 b
15376 C
15377 "#
15378 .unindent();
15379
15380 cx.assert_state_with_diff(first_hunk_expanded);
15381
15382 cx.update_editor(|editor, _, cx| {
15383 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15384 });
15385 executor.run_until_parked();
15386
15387 cx.assert_state_with_diff(both_hunks_expanded);
15388
15389 cx.set_state(
15390 &r#"
15391 ˇA
15392 b
15393 "#
15394 .unindent(),
15395 );
15396 cx.run_until_parked();
15397
15398 // TODO this cursor position seems bad
15399 cx.assert_state_with_diff(
15400 r#"
15401 - ˇa
15402 + A
15403 b
15404 "#
15405 .unindent(),
15406 );
15407
15408 cx.update_editor(|editor, window, cx| {
15409 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15410 });
15411
15412 cx.assert_state_with_diff(
15413 r#"
15414 - ˇa
15415 + A
15416 b
15417 - c
15418 "#
15419 .unindent(),
15420 );
15421
15422 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15423 let snapshot = editor.snapshot(window, cx);
15424 let hunks = editor
15425 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15426 .collect::<Vec<_>>();
15427 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15428 let buffer_id = hunks[0].buffer_id;
15429 hunks
15430 .into_iter()
15431 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15432 .collect::<Vec<_>>()
15433 });
15434 assert_eq!(hunk_ranges.len(), 2);
15435
15436 cx.update_editor(|editor, _, cx| {
15437 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15438 });
15439 executor.run_until_parked();
15440
15441 cx.assert_state_with_diff(
15442 r#"
15443 - ˇa
15444 + A
15445 b
15446 "#
15447 .unindent(),
15448 );
15449}
15450
15451#[gpui::test]
15452async fn test_toggle_deletion_hunk_at_start_of_file(
15453 executor: BackgroundExecutor,
15454 cx: &mut TestAppContext,
15455) {
15456 init_test(cx, |_| {});
15457 let mut cx = EditorTestContext::new(cx).await;
15458
15459 let diff_base = r#"
15460 a
15461 b
15462 c
15463 "#
15464 .unindent();
15465
15466 cx.set_state(
15467 &r#"
15468 ˇb
15469 c
15470 "#
15471 .unindent(),
15472 );
15473 cx.set_head_text(&diff_base);
15474 cx.update_editor(|editor, window, cx| {
15475 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15476 });
15477 executor.run_until_parked();
15478
15479 let hunk_expanded = r#"
15480 - a
15481 ˇb
15482 c
15483 "#
15484 .unindent();
15485
15486 cx.assert_state_with_diff(hunk_expanded.clone());
15487
15488 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15489 let snapshot = editor.snapshot(window, cx);
15490 let hunks = editor
15491 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15492 .collect::<Vec<_>>();
15493 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15494 let buffer_id = hunks[0].buffer_id;
15495 hunks
15496 .into_iter()
15497 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15498 .collect::<Vec<_>>()
15499 });
15500 assert_eq!(hunk_ranges.len(), 1);
15501
15502 cx.update_editor(|editor, _, cx| {
15503 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15504 });
15505 executor.run_until_parked();
15506
15507 let hunk_collapsed = r#"
15508 ˇb
15509 c
15510 "#
15511 .unindent();
15512
15513 cx.assert_state_with_diff(hunk_collapsed);
15514
15515 cx.update_editor(|editor, _, cx| {
15516 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15517 });
15518 executor.run_until_parked();
15519
15520 cx.assert_state_with_diff(hunk_expanded.clone());
15521}
15522
15523#[gpui::test]
15524async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15525 init_test(cx, |_| {});
15526
15527 let fs = FakeFs::new(cx.executor());
15528 fs.insert_tree(
15529 path!("/test"),
15530 json!({
15531 ".git": {},
15532 "file-1": "ONE\n",
15533 "file-2": "TWO\n",
15534 "file-3": "THREE\n",
15535 }),
15536 )
15537 .await;
15538
15539 fs.set_head_for_repo(
15540 path!("/test/.git").as_ref(),
15541 &[
15542 ("file-1".into(), "one\n".into()),
15543 ("file-2".into(), "two\n".into()),
15544 ("file-3".into(), "three\n".into()),
15545 ],
15546 );
15547
15548 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15549 let mut buffers = vec![];
15550 for i in 1..=3 {
15551 let buffer = project
15552 .update(cx, |project, cx| {
15553 let path = format!(path!("/test/file-{}"), i);
15554 project.open_local_buffer(path, cx)
15555 })
15556 .await
15557 .unwrap();
15558 buffers.push(buffer);
15559 }
15560
15561 let multibuffer = cx.new(|cx| {
15562 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15563 multibuffer.set_all_diff_hunks_expanded(cx);
15564 for buffer in &buffers {
15565 let snapshot = buffer.read(cx).snapshot();
15566 multibuffer.set_excerpts_for_path(
15567 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15568 buffer.clone(),
15569 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15570 DEFAULT_MULTIBUFFER_CONTEXT,
15571 cx,
15572 );
15573 }
15574 multibuffer
15575 });
15576
15577 let editor = cx.add_window(|window, cx| {
15578 Editor::new(
15579 EditorMode::Full,
15580 multibuffer,
15581 Some(project),
15582 true,
15583 window,
15584 cx,
15585 )
15586 });
15587 cx.run_until_parked();
15588
15589 let snapshot = editor
15590 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15591 .unwrap();
15592 let hunks = snapshot
15593 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15594 .map(|hunk| match hunk {
15595 DisplayDiffHunk::Unfolded {
15596 display_row_range, ..
15597 } => display_row_range,
15598 DisplayDiffHunk::Folded { .. } => unreachable!(),
15599 })
15600 .collect::<Vec<_>>();
15601 assert_eq!(
15602 hunks,
15603 [
15604 DisplayRow(3)..DisplayRow(5),
15605 DisplayRow(10)..DisplayRow(12),
15606 DisplayRow(17)..DisplayRow(19),
15607 ]
15608 );
15609}
15610
15611#[gpui::test]
15612async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15613 init_test(cx, |_| {});
15614
15615 let mut cx = EditorTestContext::new(cx).await;
15616 cx.set_head_text(indoc! { "
15617 one
15618 two
15619 three
15620 four
15621 five
15622 "
15623 });
15624 cx.set_index_text(indoc! { "
15625 one
15626 two
15627 three
15628 four
15629 five
15630 "
15631 });
15632 cx.set_state(indoc! {"
15633 one
15634 TWO
15635 ˇTHREE
15636 FOUR
15637 five
15638 "});
15639 cx.run_until_parked();
15640 cx.update_editor(|editor, window, cx| {
15641 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15642 });
15643 cx.run_until_parked();
15644 cx.assert_index_text(Some(indoc! {"
15645 one
15646 TWO
15647 THREE
15648 FOUR
15649 five
15650 "}));
15651 cx.set_state(indoc! { "
15652 one
15653 TWO
15654 ˇTHREE-HUNDRED
15655 FOUR
15656 five
15657 "});
15658 cx.run_until_parked();
15659 cx.update_editor(|editor, window, cx| {
15660 let snapshot = editor.snapshot(window, cx);
15661 let hunks = editor
15662 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15663 .collect::<Vec<_>>();
15664 assert_eq!(hunks.len(), 1);
15665 assert_eq!(
15666 hunks[0].status(),
15667 DiffHunkStatus {
15668 kind: DiffHunkStatusKind::Modified,
15669 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15670 }
15671 );
15672
15673 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15674 });
15675 cx.run_until_parked();
15676 cx.assert_index_text(Some(indoc! {"
15677 one
15678 TWO
15679 THREE-HUNDRED
15680 FOUR
15681 five
15682 "}));
15683}
15684
15685#[gpui::test]
15686fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15687 init_test(cx, |_| {});
15688
15689 let editor = cx.add_window(|window, cx| {
15690 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15691 build_editor(buffer, window, cx)
15692 });
15693
15694 let render_args = Arc::new(Mutex::new(None));
15695 let snapshot = editor
15696 .update(cx, |editor, window, cx| {
15697 let snapshot = editor.buffer().read(cx).snapshot(cx);
15698 let range =
15699 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15700
15701 struct RenderArgs {
15702 row: MultiBufferRow,
15703 folded: bool,
15704 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15705 }
15706
15707 let crease = Crease::inline(
15708 range,
15709 FoldPlaceholder::test(),
15710 {
15711 let toggle_callback = render_args.clone();
15712 move |row, folded, callback, _window, _cx| {
15713 *toggle_callback.lock() = Some(RenderArgs {
15714 row,
15715 folded,
15716 callback,
15717 });
15718 div()
15719 }
15720 },
15721 |_row, _folded, _window, _cx| div(),
15722 );
15723
15724 editor.insert_creases(Some(crease), cx);
15725 let snapshot = editor.snapshot(window, cx);
15726 let _div = snapshot.render_crease_toggle(
15727 MultiBufferRow(1),
15728 false,
15729 cx.entity().clone(),
15730 window,
15731 cx,
15732 );
15733 snapshot
15734 })
15735 .unwrap();
15736
15737 let render_args = render_args.lock().take().unwrap();
15738 assert_eq!(render_args.row, MultiBufferRow(1));
15739 assert!(!render_args.folded);
15740 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15741
15742 cx.update_window(*editor, |_, window, cx| {
15743 (render_args.callback)(true, window, cx)
15744 })
15745 .unwrap();
15746 let snapshot = editor
15747 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15748 .unwrap();
15749 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15750
15751 cx.update_window(*editor, |_, window, cx| {
15752 (render_args.callback)(false, window, cx)
15753 })
15754 .unwrap();
15755 let snapshot = editor
15756 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15757 .unwrap();
15758 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15759}
15760
15761#[gpui::test]
15762async fn test_input_text(cx: &mut TestAppContext) {
15763 init_test(cx, |_| {});
15764 let mut cx = EditorTestContext::new(cx).await;
15765
15766 cx.set_state(
15767 &r#"ˇone
15768 two
15769
15770 three
15771 fourˇ
15772 five
15773
15774 siˇx"#
15775 .unindent(),
15776 );
15777
15778 cx.dispatch_action(HandleInput(String::new()));
15779 cx.assert_editor_state(
15780 &r#"ˇone
15781 two
15782
15783 three
15784 fourˇ
15785 five
15786
15787 siˇx"#
15788 .unindent(),
15789 );
15790
15791 cx.dispatch_action(HandleInput("AAAA".to_string()));
15792 cx.assert_editor_state(
15793 &r#"AAAAˇone
15794 two
15795
15796 three
15797 fourAAAAˇ
15798 five
15799
15800 siAAAAˇx"#
15801 .unindent(),
15802 );
15803}
15804
15805#[gpui::test]
15806async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15807 init_test(cx, |_| {});
15808
15809 let mut cx = EditorTestContext::new(cx).await;
15810 cx.set_state(
15811 r#"let foo = 1;
15812let foo = 2;
15813let foo = 3;
15814let fooˇ = 4;
15815let foo = 5;
15816let foo = 6;
15817let foo = 7;
15818let foo = 8;
15819let foo = 9;
15820let foo = 10;
15821let foo = 11;
15822let foo = 12;
15823let foo = 13;
15824let foo = 14;
15825let foo = 15;"#,
15826 );
15827
15828 cx.update_editor(|e, window, cx| {
15829 assert_eq!(
15830 e.next_scroll_position,
15831 NextScrollCursorCenterTopBottom::Center,
15832 "Default next scroll direction is center",
15833 );
15834
15835 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15836 assert_eq!(
15837 e.next_scroll_position,
15838 NextScrollCursorCenterTopBottom::Top,
15839 "After center, next scroll direction should be top",
15840 );
15841
15842 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15843 assert_eq!(
15844 e.next_scroll_position,
15845 NextScrollCursorCenterTopBottom::Bottom,
15846 "After top, next scroll direction should be bottom",
15847 );
15848
15849 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15850 assert_eq!(
15851 e.next_scroll_position,
15852 NextScrollCursorCenterTopBottom::Center,
15853 "After bottom, scrolling should start over",
15854 );
15855
15856 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15857 assert_eq!(
15858 e.next_scroll_position,
15859 NextScrollCursorCenterTopBottom::Top,
15860 "Scrolling continues if retriggered fast enough"
15861 );
15862 });
15863
15864 cx.executor()
15865 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15866 cx.executor().run_until_parked();
15867 cx.update_editor(|e, _, _| {
15868 assert_eq!(
15869 e.next_scroll_position,
15870 NextScrollCursorCenterTopBottom::Center,
15871 "If scrolling is not triggered fast enough, it should reset"
15872 );
15873 });
15874}
15875
15876#[gpui::test]
15877async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15878 init_test(cx, |_| {});
15879 let mut cx = EditorLspTestContext::new_rust(
15880 lsp::ServerCapabilities {
15881 definition_provider: Some(lsp::OneOf::Left(true)),
15882 references_provider: Some(lsp::OneOf::Left(true)),
15883 ..lsp::ServerCapabilities::default()
15884 },
15885 cx,
15886 )
15887 .await;
15888
15889 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15890 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15891 move |params, _| async move {
15892 if empty_go_to_definition {
15893 Ok(None)
15894 } else {
15895 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15896 uri: params.text_document_position_params.text_document.uri,
15897 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15898 })))
15899 }
15900 },
15901 );
15902 let references =
15903 cx.lsp
15904 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15905 Ok(Some(vec![lsp::Location {
15906 uri: params.text_document_position.text_document.uri,
15907 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15908 }]))
15909 });
15910 (go_to_definition, references)
15911 };
15912
15913 cx.set_state(
15914 &r#"fn one() {
15915 let mut a = ˇtwo();
15916 }
15917
15918 fn two() {}"#
15919 .unindent(),
15920 );
15921 set_up_lsp_handlers(false, &mut cx);
15922 let navigated = cx
15923 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15924 .await
15925 .expect("Failed to navigate to definition");
15926 assert_eq!(
15927 navigated,
15928 Navigated::Yes,
15929 "Should have navigated to definition from the GetDefinition response"
15930 );
15931 cx.assert_editor_state(
15932 &r#"fn one() {
15933 let mut a = two();
15934 }
15935
15936 fn «twoˇ»() {}"#
15937 .unindent(),
15938 );
15939
15940 let editors = cx.update_workspace(|workspace, _, cx| {
15941 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15942 });
15943 cx.update_editor(|_, _, test_editor_cx| {
15944 assert_eq!(
15945 editors.len(),
15946 1,
15947 "Initially, only one, test, editor should be open in the workspace"
15948 );
15949 assert_eq!(
15950 test_editor_cx.entity(),
15951 editors.last().expect("Asserted len is 1").clone()
15952 );
15953 });
15954
15955 set_up_lsp_handlers(true, &mut cx);
15956 let navigated = cx
15957 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15958 .await
15959 .expect("Failed to navigate to lookup references");
15960 assert_eq!(
15961 navigated,
15962 Navigated::Yes,
15963 "Should have navigated to references as a fallback after empty GoToDefinition response"
15964 );
15965 // We should not change the selections in the existing file,
15966 // if opening another milti buffer with the references
15967 cx.assert_editor_state(
15968 &r#"fn one() {
15969 let mut a = two();
15970 }
15971
15972 fn «twoˇ»() {}"#
15973 .unindent(),
15974 );
15975 let editors = cx.update_workspace(|workspace, _, cx| {
15976 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15977 });
15978 cx.update_editor(|_, _, test_editor_cx| {
15979 assert_eq!(
15980 editors.len(),
15981 2,
15982 "After falling back to references search, we open a new editor with the results"
15983 );
15984 let references_fallback_text = editors
15985 .into_iter()
15986 .find(|new_editor| *new_editor != test_editor_cx.entity())
15987 .expect("Should have one non-test editor now")
15988 .read(test_editor_cx)
15989 .text(test_editor_cx);
15990 assert_eq!(
15991 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15992 "Should use the range from the references response and not the GoToDefinition one"
15993 );
15994 });
15995}
15996
15997#[gpui::test]
15998async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15999 init_test(cx, |_| {});
16000
16001 let language = Arc::new(Language::new(
16002 LanguageConfig::default(),
16003 Some(tree_sitter_rust::LANGUAGE.into()),
16004 ));
16005
16006 let text = r#"
16007 #[cfg(test)]
16008 mod tests() {
16009 #[test]
16010 fn runnable_1() {
16011 let a = 1;
16012 }
16013
16014 #[test]
16015 fn runnable_2() {
16016 let a = 1;
16017 let b = 2;
16018 }
16019 }
16020 "#
16021 .unindent();
16022
16023 let fs = FakeFs::new(cx.executor());
16024 fs.insert_file("/file.rs", Default::default()).await;
16025
16026 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16027 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16028 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16029 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16030 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16031
16032 let editor = cx.new_window_entity(|window, cx| {
16033 Editor::new(
16034 EditorMode::Full,
16035 multi_buffer,
16036 Some(project.clone()),
16037 true,
16038 window,
16039 cx,
16040 )
16041 });
16042
16043 editor.update_in(cx, |editor, window, cx| {
16044 let snapshot = editor.buffer().read(cx).snapshot(cx);
16045 editor.tasks.insert(
16046 (buffer.read(cx).remote_id(), 3),
16047 RunnableTasks {
16048 templates: vec![],
16049 offset: snapshot.anchor_before(43),
16050 column: 0,
16051 extra_variables: HashMap::default(),
16052 context_range: BufferOffset(43)..BufferOffset(85),
16053 },
16054 );
16055 editor.tasks.insert(
16056 (buffer.read(cx).remote_id(), 8),
16057 RunnableTasks {
16058 templates: vec![],
16059 offset: snapshot.anchor_before(86),
16060 column: 0,
16061 extra_variables: HashMap::default(),
16062 context_range: BufferOffset(86)..BufferOffset(191),
16063 },
16064 );
16065
16066 // Test finding task when cursor is inside function body
16067 editor.change_selections(None, window, cx, |s| {
16068 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16069 });
16070 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16071 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16072
16073 // Test finding task when cursor is on function name
16074 editor.change_selections(None, window, cx, |s| {
16075 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16076 });
16077 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16078 assert_eq!(row, 8, "Should find task when cursor is on function name");
16079 });
16080}
16081
16082#[gpui::test]
16083async fn test_folding_buffers(cx: &mut TestAppContext) {
16084 init_test(cx, |_| {});
16085
16086 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16087 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16088 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16089
16090 let fs = FakeFs::new(cx.executor());
16091 fs.insert_tree(
16092 path!("/a"),
16093 json!({
16094 "first.rs": sample_text_1,
16095 "second.rs": sample_text_2,
16096 "third.rs": sample_text_3,
16097 }),
16098 )
16099 .await;
16100 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16101 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16102 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16103 let worktree = project.update(cx, |project, cx| {
16104 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16105 assert_eq!(worktrees.len(), 1);
16106 worktrees.pop().unwrap()
16107 });
16108 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16109
16110 let buffer_1 = project
16111 .update(cx, |project, cx| {
16112 project.open_buffer((worktree_id, "first.rs"), cx)
16113 })
16114 .await
16115 .unwrap();
16116 let buffer_2 = project
16117 .update(cx, |project, cx| {
16118 project.open_buffer((worktree_id, "second.rs"), cx)
16119 })
16120 .await
16121 .unwrap();
16122 let buffer_3 = project
16123 .update(cx, |project, cx| {
16124 project.open_buffer((worktree_id, "third.rs"), cx)
16125 })
16126 .await
16127 .unwrap();
16128
16129 let multi_buffer = cx.new(|cx| {
16130 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16131 multi_buffer.push_excerpts(
16132 buffer_1.clone(),
16133 [
16134 ExcerptRange {
16135 context: Point::new(0, 0)..Point::new(3, 0),
16136 primary: None,
16137 },
16138 ExcerptRange {
16139 context: Point::new(5, 0)..Point::new(7, 0),
16140 primary: None,
16141 },
16142 ExcerptRange {
16143 context: Point::new(9, 0)..Point::new(10, 4),
16144 primary: None,
16145 },
16146 ],
16147 cx,
16148 );
16149 multi_buffer.push_excerpts(
16150 buffer_2.clone(),
16151 [
16152 ExcerptRange {
16153 context: Point::new(0, 0)..Point::new(3, 0),
16154 primary: None,
16155 },
16156 ExcerptRange {
16157 context: Point::new(5, 0)..Point::new(7, 0),
16158 primary: None,
16159 },
16160 ExcerptRange {
16161 context: Point::new(9, 0)..Point::new(10, 4),
16162 primary: None,
16163 },
16164 ],
16165 cx,
16166 );
16167 multi_buffer.push_excerpts(
16168 buffer_3.clone(),
16169 [
16170 ExcerptRange {
16171 context: Point::new(0, 0)..Point::new(3, 0),
16172 primary: None,
16173 },
16174 ExcerptRange {
16175 context: Point::new(5, 0)..Point::new(7, 0),
16176 primary: None,
16177 },
16178 ExcerptRange {
16179 context: Point::new(9, 0)..Point::new(10, 4),
16180 primary: None,
16181 },
16182 ],
16183 cx,
16184 );
16185 multi_buffer
16186 });
16187 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16188 Editor::new(
16189 EditorMode::Full,
16190 multi_buffer.clone(),
16191 Some(project.clone()),
16192 true,
16193 window,
16194 cx,
16195 )
16196 });
16197
16198 assert_eq!(
16199 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16200 "\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",
16201 );
16202
16203 multi_buffer_editor.update(cx, |editor, cx| {
16204 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16205 });
16206 assert_eq!(
16207 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16208 "\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",
16209 "After folding the first buffer, its text should not be displayed"
16210 );
16211
16212 multi_buffer_editor.update(cx, |editor, cx| {
16213 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16214 });
16215 assert_eq!(
16216 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16217 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16218 "After folding the second buffer, its text should not be displayed"
16219 );
16220
16221 multi_buffer_editor.update(cx, |editor, cx| {
16222 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16223 });
16224 assert_eq!(
16225 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16226 "\n\n\n\n\n",
16227 "After folding the third buffer, its text should not be displayed"
16228 );
16229
16230 // Emulate selection inside the fold logic, that should work
16231 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16232 editor
16233 .snapshot(window, cx)
16234 .next_line_boundary(Point::new(0, 4));
16235 });
16236
16237 multi_buffer_editor.update(cx, |editor, cx| {
16238 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16239 });
16240 assert_eq!(
16241 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16242 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16243 "After unfolding the second buffer, its text should be displayed"
16244 );
16245
16246 // Typing inside of buffer 1 causes that buffer to be unfolded.
16247 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16248 assert_eq!(
16249 multi_buffer
16250 .read(cx)
16251 .snapshot(cx)
16252 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16253 .collect::<String>(),
16254 "bbbb"
16255 );
16256 editor.change_selections(None, window, cx, |selections| {
16257 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16258 });
16259 editor.handle_input("B", window, cx);
16260 });
16261
16262 assert_eq!(
16263 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16264 "\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",
16265 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16266 );
16267
16268 multi_buffer_editor.update(cx, |editor, cx| {
16269 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16270 });
16271 assert_eq!(
16272 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16273 "\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",
16274 "After unfolding the all buffers, all original text should be displayed"
16275 );
16276}
16277
16278#[gpui::test]
16279async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16280 init_test(cx, |_| {});
16281
16282 let sample_text_1 = "1111\n2222\n3333".to_string();
16283 let sample_text_2 = "4444\n5555\n6666".to_string();
16284 let sample_text_3 = "7777\n8888\n9999".to_string();
16285
16286 let fs = FakeFs::new(cx.executor());
16287 fs.insert_tree(
16288 path!("/a"),
16289 json!({
16290 "first.rs": sample_text_1,
16291 "second.rs": sample_text_2,
16292 "third.rs": sample_text_3,
16293 }),
16294 )
16295 .await;
16296 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16297 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16298 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16299 let worktree = project.update(cx, |project, cx| {
16300 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16301 assert_eq!(worktrees.len(), 1);
16302 worktrees.pop().unwrap()
16303 });
16304 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16305
16306 let buffer_1 = project
16307 .update(cx, |project, cx| {
16308 project.open_buffer((worktree_id, "first.rs"), cx)
16309 })
16310 .await
16311 .unwrap();
16312 let buffer_2 = project
16313 .update(cx, |project, cx| {
16314 project.open_buffer((worktree_id, "second.rs"), cx)
16315 })
16316 .await
16317 .unwrap();
16318 let buffer_3 = project
16319 .update(cx, |project, cx| {
16320 project.open_buffer((worktree_id, "third.rs"), cx)
16321 })
16322 .await
16323 .unwrap();
16324
16325 let multi_buffer = cx.new(|cx| {
16326 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16327 multi_buffer.push_excerpts(
16328 buffer_1.clone(),
16329 [ExcerptRange {
16330 context: Point::new(0, 0)..Point::new(3, 0),
16331 primary: None,
16332 }],
16333 cx,
16334 );
16335 multi_buffer.push_excerpts(
16336 buffer_2.clone(),
16337 [ExcerptRange {
16338 context: Point::new(0, 0)..Point::new(3, 0),
16339 primary: None,
16340 }],
16341 cx,
16342 );
16343 multi_buffer.push_excerpts(
16344 buffer_3.clone(),
16345 [ExcerptRange {
16346 context: Point::new(0, 0)..Point::new(3, 0),
16347 primary: None,
16348 }],
16349 cx,
16350 );
16351 multi_buffer
16352 });
16353
16354 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16355 Editor::new(
16356 EditorMode::Full,
16357 multi_buffer,
16358 Some(project.clone()),
16359 true,
16360 window,
16361 cx,
16362 )
16363 });
16364
16365 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16366 assert_eq!(
16367 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16368 full_text,
16369 );
16370
16371 multi_buffer_editor.update(cx, |editor, cx| {
16372 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16373 });
16374 assert_eq!(
16375 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16376 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16377 "After folding the first buffer, its text should not be displayed"
16378 );
16379
16380 multi_buffer_editor.update(cx, |editor, cx| {
16381 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16382 });
16383
16384 assert_eq!(
16385 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16386 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16387 "After folding the second buffer, its text should not be displayed"
16388 );
16389
16390 multi_buffer_editor.update(cx, |editor, cx| {
16391 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16392 });
16393 assert_eq!(
16394 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16395 "\n\n\n\n\n",
16396 "After folding the third buffer, its text should not be displayed"
16397 );
16398
16399 multi_buffer_editor.update(cx, |editor, cx| {
16400 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16401 });
16402 assert_eq!(
16403 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16404 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16405 "After unfolding the second buffer, its text should be displayed"
16406 );
16407
16408 multi_buffer_editor.update(cx, |editor, cx| {
16409 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16410 });
16411 assert_eq!(
16412 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16413 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16414 "After unfolding the first buffer, its text should be displayed"
16415 );
16416
16417 multi_buffer_editor.update(cx, |editor, cx| {
16418 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16419 });
16420 assert_eq!(
16421 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16422 full_text,
16423 "After unfolding all buffers, all original text should be displayed"
16424 );
16425}
16426
16427#[gpui::test]
16428async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16429 init_test(cx, |_| {});
16430
16431 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16432
16433 let fs = FakeFs::new(cx.executor());
16434 fs.insert_tree(
16435 path!("/a"),
16436 json!({
16437 "main.rs": sample_text,
16438 }),
16439 )
16440 .await;
16441 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16442 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16443 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16444 let worktree = project.update(cx, |project, cx| {
16445 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16446 assert_eq!(worktrees.len(), 1);
16447 worktrees.pop().unwrap()
16448 });
16449 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16450
16451 let buffer_1 = project
16452 .update(cx, |project, cx| {
16453 project.open_buffer((worktree_id, "main.rs"), cx)
16454 })
16455 .await
16456 .unwrap();
16457
16458 let multi_buffer = cx.new(|cx| {
16459 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16460 multi_buffer.push_excerpts(
16461 buffer_1.clone(),
16462 [ExcerptRange {
16463 context: Point::new(0, 0)
16464 ..Point::new(
16465 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16466 0,
16467 ),
16468 primary: None,
16469 }],
16470 cx,
16471 );
16472 multi_buffer
16473 });
16474 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16475 Editor::new(
16476 EditorMode::Full,
16477 multi_buffer,
16478 Some(project.clone()),
16479 true,
16480 window,
16481 cx,
16482 )
16483 });
16484
16485 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16486 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16487 enum TestHighlight {}
16488 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16489 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16490 editor.highlight_text::<TestHighlight>(
16491 vec![highlight_range.clone()],
16492 HighlightStyle::color(Hsla::green()),
16493 cx,
16494 );
16495 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16496 });
16497
16498 let full_text = format!("\n\n\n{sample_text}\n");
16499 assert_eq!(
16500 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16501 full_text,
16502 );
16503}
16504
16505#[gpui::test]
16506async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16507 init_test(cx, |_| {});
16508 cx.update(|cx| {
16509 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16510 "keymaps/default-linux.json",
16511 cx,
16512 )
16513 .unwrap();
16514 cx.bind_keys(default_key_bindings);
16515 });
16516
16517 let (editor, cx) = cx.add_window_view(|window, cx| {
16518 let multi_buffer = MultiBuffer::build_multi(
16519 [
16520 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16521 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16522 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16523 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16524 ],
16525 cx,
16526 );
16527 let mut editor = Editor::new(
16528 EditorMode::Full,
16529 multi_buffer.clone(),
16530 None,
16531 true,
16532 window,
16533 cx,
16534 );
16535
16536 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16537 // fold all but the second buffer, so that we test navigating between two
16538 // adjacent folded buffers, as well as folded buffers at the start and
16539 // end the multibuffer
16540 editor.fold_buffer(buffer_ids[0], cx);
16541 editor.fold_buffer(buffer_ids[2], cx);
16542 editor.fold_buffer(buffer_ids[3], cx);
16543
16544 editor
16545 });
16546 cx.simulate_resize(size(px(1000.), px(1000.)));
16547
16548 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16549 cx.assert_excerpts_with_selections(indoc! {"
16550 [EXCERPT]
16551 ˇ[FOLDED]
16552 [EXCERPT]
16553 a1
16554 b1
16555 [EXCERPT]
16556 [FOLDED]
16557 [EXCERPT]
16558 [FOLDED]
16559 "
16560 });
16561 cx.simulate_keystroke("down");
16562 cx.assert_excerpts_with_selections(indoc! {"
16563 [EXCERPT]
16564 [FOLDED]
16565 [EXCERPT]
16566 ˇa1
16567 b1
16568 [EXCERPT]
16569 [FOLDED]
16570 [EXCERPT]
16571 [FOLDED]
16572 "
16573 });
16574 cx.simulate_keystroke("down");
16575 cx.assert_excerpts_with_selections(indoc! {"
16576 [EXCERPT]
16577 [FOLDED]
16578 [EXCERPT]
16579 a1
16580 ˇb1
16581 [EXCERPT]
16582 [FOLDED]
16583 [EXCERPT]
16584 [FOLDED]
16585 "
16586 });
16587 cx.simulate_keystroke("down");
16588 cx.assert_excerpts_with_selections(indoc! {"
16589 [EXCERPT]
16590 [FOLDED]
16591 [EXCERPT]
16592 a1
16593 b1
16594 ˇ[EXCERPT]
16595 [FOLDED]
16596 [EXCERPT]
16597 [FOLDED]
16598 "
16599 });
16600 cx.simulate_keystroke("down");
16601 cx.assert_excerpts_with_selections(indoc! {"
16602 [EXCERPT]
16603 [FOLDED]
16604 [EXCERPT]
16605 a1
16606 b1
16607 [EXCERPT]
16608 ˇ[FOLDED]
16609 [EXCERPT]
16610 [FOLDED]
16611 "
16612 });
16613 for _ in 0..5 {
16614 cx.simulate_keystroke("down");
16615 cx.assert_excerpts_with_selections(indoc! {"
16616 [EXCERPT]
16617 [FOLDED]
16618 [EXCERPT]
16619 a1
16620 b1
16621 [EXCERPT]
16622 [FOLDED]
16623 [EXCERPT]
16624 ˇ[FOLDED]
16625 "
16626 });
16627 }
16628
16629 cx.simulate_keystroke("up");
16630 cx.assert_excerpts_with_selections(indoc! {"
16631 [EXCERPT]
16632 [FOLDED]
16633 [EXCERPT]
16634 a1
16635 b1
16636 [EXCERPT]
16637 ˇ[FOLDED]
16638 [EXCERPT]
16639 [FOLDED]
16640 "
16641 });
16642 cx.simulate_keystroke("up");
16643 cx.assert_excerpts_with_selections(indoc! {"
16644 [EXCERPT]
16645 [FOLDED]
16646 [EXCERPT]
16647 a1
16648 b1
16649 ˇ[EXCERPT]
16650 [FOLDED]
16651 [EXCERPT]
16652 [FOLDED]
16653 "
16654 });
16655 cx.simulate_keystroke("up");
16656 cx.assert_excerpts_with_selections(indoc! {"
16657 [EXCERPT]
16658 [FOLDED]
16659 [EXCERPT]
16660 a1
16661 ˇb1
16662 [EXCERPT]
16663 [FOLDED]
16664 [EXCERPT]
16665 [FOLDED]
16666 "
16667 });
16668 cx.simulate_keystroke("up");
16669 cx.assert_excerpts_with_selections(indoc! {"
16670 [EXCERPT]
16671 [FOLDED]
16672 [EXCERPT]
16673 ˇa1
16674 b1
16675 [EXCERPT]
16676 [FOLDED]
16677 [EXCERPT]
16678 [FOLDED]
16679 "
16680 });
16681 for _ in 0..5 {
16682 cx.simulate_keystroke("up");
16683 cx.assert_excerpts_with_selections(indoc! {"
16684 [EXCERPT]
16685 ˇ[FOLDED]
16686 [EXCERPT]
16687 a1
16688 b1
16689 [EXCERPT]
16690 [FOLDED]
16691 [EXCERPT]
16692 [FOLDED]
16693 "
16694 });
16695 }
16696}
16697
16698#[gpui::test]
16699async fn test_inline_completion_text(cx: &mut TestAppContext) {
16700 init_test(cx, |_| {});
16701
16702 // Simple insertion
16703 assert_highlighted_edits(
16704 "Hello, world!",
16705 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16706 true,
16707 cx,
16708 |highlighted_edits, cx| {
16709 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16710 assert_eq!(highlighted_edits.highlights.len(), 1);
16711 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16712 assert_eq!(
16713 highlighted_edits.highlights[0].1.background_color,
16714 Some(cx.theme().status().created_background)
16715 );
16716 },
16717 )
16718 .await;
16719
16720 // Replacement
16721 assert_highlighted_edits(
16722 "This is a test.",
16723 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16724 false,
16725 cx,
16726 |highlighted_edits, cx| {
16727 assert_eq!(highlighted_edits.text, "That is a test.");
16728 assert_eq!(highlighted_edits.highlights.len(), 1);
16729 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16730 assert_eq!(
16731 highlighted_edits.highlights[0].1.background_color,
16732 Some(cx.theme().status().created_background)
16733 );
16734 },
16735 )
16736 .await;
16737
16738 // Multiple edits
16739 assert_highlighted_edits(
16740 "Hello, world!",
16741 vec![
16742 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16743 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16744 ],
16745 false,
16746 cx,
16747 |highlighted_edits, cx| {
16748 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16749 assert_eq!(highlighted_edits.highlights.len(), 2);
16750 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16751 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16752 assert_eq!(
16753 highlighted_edits.highlights[0].1.background_color,
16754 Some(cx.theme().status().created_background)
16755 );
16756 assert_eq!(
16757 highlighted_edits.highlights[1].1.background_color,
16758 Some(cx.theme().status().created_background)
16759 );
16760 },
16761 )
16762 .await;
16763
16764 // Multiple lines with edits
16765 assert_highlighted_edits(
16766 "First line\nSecond line\nThird line\nFourth line",
16767 vec![
16768 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16769 (
16770 Point::new(2, 0)..Point::new(2, 10),
16771 "New third line".to_string(),
16772 ),
16773 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16774 ],
16775 false,
16776 cx,
16777 |highlighted_edits, cx| {
16778 assert_eq!(
16779 highlighted_edits.text,
16780 "Second modified\nNew third line\nFourth updated line"
16781 );
16782 assert_eq!(highlighted_edits.highlights.len(), 3);
16783 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16784 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16785 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16786 for highlight in &highlighted_edits.highlights {
16787 assert_eq!(
16788 highlight.1.background_color,
16789 Some(cx.theme().status().created_background)
16790 );
16791 }
16792 },
16793 )
16794 .await;
16795}
16796
16797#[gpui::test]
16798async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16799 init_test(cx, |_| {});
16800
16801 // Deletion
16802 assert_highlighted_edits(
16803 "Hello, world!",
16804 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16805 true,
16806 cx,
16807 |highlighted_edits, cx| {
16808 assert_eq!(highlighted_edits.text, "Hello, world!");
16809 assert_eq!(highlighted_edits.highlights.len(), 1);
16810 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16811 assert_eq!(
16812 highlighted_edits.highlights[0].1.background_color,
16813 Some(cx.theme().status().deleted_background)
16814 );
16815 },
16816 )
16817 .await;
16818
16819 // Insertion
16820 assert_highlighted_edits(
16821 "Hello, world!",
16822 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16823 true,
16824 cx,
16825 |highlighted_edits, cx| {
16826 assert_eq!(highlighted_edits.highlights.len(), 1);
16827 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16828 assert_eq!(
16829 highlighted_edits.highlights[0].1.background_color,
16830 Some(cx.theme().status().created_background)
16831 );
16832 },
16833 )
16834 .await;
16835}
16836
16837async fn assert_highlighted_edits(
16838 text: &str,
16839 edits: Vec<(Range<Point>, String)>,
16840 include_deletions: bool,
16841 cx: &mut TestAppContext,
16842 assertion_fn: impl Fn(HighlightedText, &App),
16843) {
16844 let window = cx.add_window(|window, cx| {
16845 let buffer = MultiBuffer::build_simple(text, cx);
16846 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16847 });
16848 let cx = &mut VisualTestContext::from_window(*window, cx);
16849
16850 let (buffer, snapshot) = window
16851 .update(cx, |editor, _window, cx| {
16852 (
16853 editor.buffer().clone(),
16854 editor.buffer().read(cx).snapshot(cx),
16855 )
16856 })
16857 .unwrap();
16858
16859 let edits = edits
16860 .into_iter()
16861 .map(|(range, edit)| {
16862 (
16863 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16864 edit,
16865 )
16866 })
16867 .collect::<Vec<_>>();
16868
16869 let text_anchor_edits = edits
16870 .clone()
16871 .into_iter()
16872 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16873 .collect::<Vec<_>>();
16874
16875 let edit_preview = window
16876 .update(cx, |_, _window, cx| {
16877 buffer
16878 .read(cx)
16879 .as_singleton()
16880 .unwrap()
16881 .read(cx)
16882 .preview_edits(text_anchor_edits.into(), cx)
16883 })
16884 .unwrap()
16885 .await;
16886
16887 cx.update(|_window, cx| {
16888 let highlighted_edits = inline_completion_edit_text(
16889 &snapshot.as_singleton().unwrap().2,
16890 &edits,
16891 &edit_preview,
16892 include_deletions,
16893 cx,
16894 );
16895 assertion_fn(highlighted_edits, cx)
16896 });
16897}
16898
16899#[gpui::test]
16900async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16901 init_test(cx, |_| {});
16902 let capabilities = lsp::ServerCapabilities {
16903 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16904 prepare_provider: Some(true),
16905 work_done_progress_options: Default::default(),
16906 })),
16907 ..Default::default()
16908 };
16909 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16910
16911 cx.set_state(indoc! {"
16912 struct Fˇoo {}
16913 "});
16914
16915 cx.update_editor(|editor, _, cx| {
16916 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16917 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16918 editor.highlight_background::<DocumentHighlightRead>(
16919 &[highlight_range],
16920 |c| c.editor_document_highlight_read_background,
16921 cx,
16922 );
16923 });
16924
16925 let mut prepare_rename_handler =
16926 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16927 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16928 start: lsp::Position {
16929 line: 0,
16930 character: 7,
16931 },
16932 end: lsp::Position {
16933 line: 0,
16934 character: 10,
16935 },
16936 })))
16937 });
16938 let prepare_rename_task = cx
16939 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16940 .expect("Prepare rename was not started");
16941 prepare_rename_handler.next().await.unwrap();
16942 prepare_rename_task.await.expect("Prepare rename failed");
16943
16944 let mut rename_handler =
16945 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16946 let edit = lsp::TextEdit {
16947 range: lsp::Range {
16948 start: lsp::Position {
16949 line: 0,
16950 character: 7,
16951 },
16952 end: lsp::Position {
16953 line: 0,
16954 character: 10,
16955 },
16956 },
16957 new_text: "FooRenamed".to_string(),
16958 };
16959 Ok(Some(lsp::WorkspaceEdit::new(
16960 // Specify the same edit twice
16961 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16962 )))
16963 });
16964 let rename_task = cx
16965 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16966 .expect("Confirm rename was not started");
16967 rename_handler.next().await.unwrap();
16968 rename_task.await.expect("Confirm rename failed");
16969 cx.run_until_parked();
16970
16971 // Despite two edits, only one is actually applied as those are identical
16972 cx.assert_editor_state(indoc! {"
16973 struct FooRenamedˇ {}
16974 "});
16975}
16976
16977#[gpui::test]
16978async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16979 init_test(cx, |_| {});
16980 // These capabilities indicate that the server does not support prepare rename.
16981 let capabilities = lsp::ServerCapabilities {
16982 rename_provider: Some(lsp::OneOf::Left(true)),
16983 ..Default::default()
16984 };
16985 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16986
16987 cx.set_state(indoc! {"
16988 struct Fˇoo {}
16989 "});
16990
16991 cx.update_editor(|editor, _window, cx| {
16992 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16993 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16994 editor.highlight_background::<DocumentHighlightRead>(
16995 &[highlight_range],
16996 |c| c.editor_document_highlight_read_background,
16997 cx,
16998 );
16999 });
17000
17001 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17002 .expect("Prepare rename was not started")
17003 .await
17004 .expect("Prepare rename failed");
17005
17006 let mut rename_handler =
17007 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17008 let edit = lsp::TextEdit {
17009 range: lsp::Range {
17010 start: lsp::Position {
17011 line: 0,
17012 character: 7,
17013 },
17014 end: lsp::Position {
17015 line: 0,
17016 character: 10,
17017 },
17018 },
17019 new_text: "FooRenamed".to_string(),
17020 };
17021 Ok(Some(lsp::WorkspaceEdit::new(
17022 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17023 )))
17024 });
17025 let rename_task = cx
17026 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17027 .expect("Confirm rename was not started");
17028 rename_handler.next().await.unwrap();
17029 rename_task.await.expect("Confirm rename failed");
17030 cx.run_until_parked();
17031
17032 // Correct range is renamed, as `surrounding_word` is used to find it.
17033 cx.assert_editor_state(indoc! {"
17034 struct FooRenamedˇ {}
17035 "});
17036}
17037
17038#[gpui::test]
17039async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17040 init_test(cx, |_| {});
17041 let mut cx = EditorTestContext::new(cx).await;
17042
17043 let language = Arc::new(
17044 Language::new(
17045 LanguageConfig::default(),
17046 Some(tree_sitter_html::LANGUAGE.into()),
17047 )
17048 .with_brackets_query(
17049 r#"
17050 ("<" @open "/>" @close)
17051 ("</" @open ">" @close)
17052 ("<" @open ">" @close)
17053 ("\"" @open "\"" @close)
17054 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17055 "#,
17056 )
17057 .unwrap(),
17058 );
17059 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17060
17061 cx.set_state(indoc! {"
17062 <span>ˇ</span>
17063 "});
17064 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17065 cx.assert_editor_state(indoc! {"
17066 <span>
17067 ˇ
17068 </span>
17069 "});
17070
17071 cx.set_state(indoc! {"
17072 <span><span></span>ˇ</span>
17073 "});
17074 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17075 cx.assert_editor_state(indoc! {"
17076 <span><span></span>
17077 ˇ</span>
17078 "});
17079
17080 cx.set_state(indoc! {"
17081 <span>ˇ
17082 </span>
17083 "});
17084 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17085 cx.assert_editor_state(indoc! {"
17086 <span>
17087 ˇ
17088 </span>
17089 "});
17090}
17091
17092mod autoclose_tags {
17093 use super::*;
17094 use language::language_settings::JsxTagAutoCloseSettings;
17095 use languages::language;
17096
17097 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17098 init_test(cx, |settings| {
17099 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17100 });
17101
17102 let mut cx = EditorTestContext::new(cx).await;
17103 cx.update_buffer(|buffer, cx| {
17104 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17105
17106 buffer.set_language(Some(language), cx)
17107 });
17108
17109 cx
17110 }
17111
17112 macro_rules! check {
17113 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17114 #[gpui::test]
17115 async fn $name(cx: &mut TestAppContext) {
17116 let mut cx = test_setup(cx).await;
17117 cx.set_state($initial);
17118 cx.run_until_parked();
17119
17120 cx.update_editor(|editor, window, cx| {
17121 editor.handle_input($input, window, cx);
17122 });
17123 cx.run_until_parked();
17124 cx.assert_editor_state($expected);
17125 }
17126 };
17127 }
17128
17129 check!(
17130 test_basic,
17131 "<divˇ" + ">" => "<div>ˇ</div>"
17132 );
17133
17134 check!(
17135 test_basic_nested,
17136 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17137 );
17138
17139 check!(
17140 test_basic_ignore_already_closed,
17141 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17142 );
17143
17144 check!(
17145 test_doesnt_autoclose_closing_tag,
17146 "</divˇ" + ">" => "</div>ˇ"
17147 );
17148
17149 check!(
17150 test_jsx_attr,
17151 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17152 );
17153
17154 check!(
17155 test_ignores_closing_tags_in_expr_block,
17156 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17157 );
17158
17159 check!(
17160 test_doesnt_autoclose_on_gt_in_expr,
17161 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17162 );
17163
17164 check!(
17165 test_ignores_closing_tags_with_different_tag_names,
17166 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17167 );
17168
17169 check!(
17170 test_autocloses_in_jsx_expression,
17171 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17172 );
17173
17174 check!(
17175 test_doesnt_autoclose_already_closed_in_jsx_expression,
17176 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17177 );
17178
17179 check!(
17180 test_autocloses_fragment,
17181 "<ˇ" + ">" => "<>ˇ</>"
17182 );
17183
17184 check!(
17185 test_does_not_include_type_argument_in_autoclose_tag_name,
17186 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17187 );
17188
17189 check!(
17190 test_does_not_autoclose_doctype,
17191 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17192 );
17193
17194 check!(
17195 test_does_not_autoclose_comment,
17196 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17197 );
17198
17199 check!(
17200 test_multi_cursor_autoclose_same_tag,
17201 r#"
17202 <divˇ
17203 <divˇ
17204 "#
17205 + ">" =>
17206 r#"
17207 <div>ˇ</div>
17208 <div>ˇ</div>
17209 "#
17210 );
17211
17212 check!(
17213 test_multi_cursor_autoclose_different_tags,
17214 r#"
17215 <divˇ
17216 <spanˇ
17217 "#
17218 + ">" =>
17219 r#"
17220 <div>ˇ</div>
17221 <span>ˇ</span>
17222 "#
17223 );
17224
17225 check!(
17226 test_multi_cursor_autoclose_some_dont_autoclose_others,
17227 r#"
17228 <divˇ
17229 <div /ˇ
17230 <spanˇ</span>
17231 <!DOCTYPE htmlˇ
17232 </headˇ
17233 <Component<T>ˇ
17234 ˇ
17235 "#
17236 + ">" =>
17237 r#"
17238 <div>ˇ</div>
17239 <div />ˇ
17240 <span>ˇ</span>
17241 <!DOCTYPE html>ˇ
17242 </head>ˇ
17243 <Component<T>>ˇ</Component>
17244 >ˇ
17245 "#
17246 );
17247
17248 check!(
17249 test_doesnt_mess_up_trailing_text,
17250 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17251 );
17252
17253 #[gpui::test]
17254 async fn test_multibuffer(cx: &mut TestAppContext) {
17255 init_test(cx, |settings| {
17256 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17257 });
17258
17259 let buffer_a = cx.new(|cx| {
17260 let mut buf = language::Buffer::local("<div", cx);
17261 buf.set_language(
17262 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17263 cx,
17264 );
17265 buf
17266 });
17267 let buffer_b = cx.new(|cx| {
17268 let mut buf = language::Buffer::local("<pre", cx);
17269 buf.set_language(
17270 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17271 cx,
17272 );
17273 buf
17274 });
17275 let buffer_c = cx.new(|cx| {
17276 let buf = language::Buffer::local("<span", cx);
17277 buf
17278 });
17279 let buffer = cx.new(|cx| {
17280 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17281 buf.push_excerpts(
17282 buffer_a,
17283 [ExcerptRange {
17284 context: text::Anchor::MIN..text::Anchor::MAX,
17285 primary: None,
17286 }],
17287 cx,
17288 );
17289 buf.push_excerpts(
17290 buffer_b,
17291 [ExcerptRange {
17292 context: text::Anchor::MIN..text::Anchor::MAX,
17293 primary: None,
17294 }],
17295 cx,
17296 );
17297 buf.push_excerpts(
17298 buffer_c,
17299 [ExcerptRange {
17300 context: text::Anchor::MIN..text::Anchor::MAX,
17301 primary: None,
17302 }],
17303 cx,
17304 );
17305 buf
17306 });
17307 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17308
17309 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17310
17311 cx.update_editor(|editor, window, cx| {
17312 editor.change_selections(None, window, cx, |selections| {
17313 selections.select(vec![
17314 Selection::from_offset(4),
17315 Selection::from_offset(9),
17316 Selection::from_offset(15),
17317 ])
17318 })
17319 });
17320 cx.run_until_parked();
17321
17322 cx.update_editor(|editor, window, cx| {
17323 editor.handle_input(">", window, cx);
17324 });
17325 cx.run_until_parked();
17326
17327 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17328 }
17329}
17330
17331fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17332 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17333 point..point
17334}
17335
17336fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17337 let (text, ranges) = marked_text_ranges(marked_text, true);
17338 assert_eq!(editor.text(cx), text);
17339 assert_eq!(
17340 editor.selections.ranges(cx),
17341 ranges,
17342 "Assert selections are {}",
17343 marked_text
17344 );
17345}
17346
17347pub fn handle_signature_help_request(
17348 cx: &mut EditorLspTestContext,
17349 mocked_response: lsp::SignatureHelp,
17350) -> impl Future<Output = ()> {
17351 let mut request =
17352 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17353 let mocked_response = mocked_response.clone();
17354 async move { Ok(Some(mocked_response)) }
17355 });
17356
17357 async move {
17358 request.next().await;
17359 }
17360}
17361
17362/// Handle completion request passing a marked string specifying where the completion
17363/// should be triggered from using '|' character, what range should be replaced, and what completions
17364/// should be returned using '<' and '>' to delimit the range
17365pub fn handle_completion_request(
17366 cx: &mut EditorLspTestContext,
17367 marked_string: &str,
17368 completions: Vec<&'static str>,
17369 counter: Arc<AtomicUsize>,
17370) -> impl Future<Output = ()> {
17371 let complete_from_marker: TextRangeMarker = '|'.into();
17372 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17373 let (_, mut marked_ranges) = marked_text_ranges_by(
17374 marked_string,
17375 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17376 );
17377
17378 let complete_from_position =
17379 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17380 let replace_range =
17381 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17382
17383 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17384 let completions = completions.clone();
17385 counter.fetch_add(1, atomic::Ordering::Release);
17386 async move {
17387 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17388 assert_eq!(
17389 params.text_document_position.position,
17390 complete_from_position
17391 );
17392 Ok(Some(lsp::CompletionResponse::Array(
17393 completions
17394 .iter()
17395 .map(|completion_text| lsp::CompletionItem {
17396 label: completion_text.to_string(),
17397 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17398 range: replace_range,
17399 new_text: completion_text.to_string(),
17400 })),
17401 ..Default::default()
17402 })
17403 .collect(),
17404 )))
17405 }
17406 });
17407
17408 async move {
17409 request.next().await;
17410 }
17411}
17412
17413fn handle_resolve_completion_request(
17414 cx: &mut EditorLspTestContext,
17415 edits: Option<Vec<(&'static str, &'static str)>>,
17416) -> impl Future<Output = ()> {
17417 let edits = edits.map(|edits| {
17418 edits
17419 .iter()
17420 .map(|(marked_string, new_text)| {
17421 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17422 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17423 lsp::TextEdit::new(replace_range, new_text.to_string())
17424 })
17425 .collect::<Vec<_>>()
17426 });
17427
17428 let mut request =
17429 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17430 let edits = edits.clone();
17431 async move {
17432 Ok(lsp::CompletionItem {
17433 additional_text_edits: edits,
17434 ..Default::default()
17435 })
17436 }
17437 });
17438
17439 async move {
17440 request.next().await;
17441 }
17442}
17443
17444pub(crate) fn update_test_language_settings(
17445 cx: &mut TestAppContext,
17446 f: impl Fn(&mut AllLanguageSettingsContent),
17447) {
17448 cx.update(|cx| {
17449 SettingsStore::update_global(cx, |store, cx| {
17450 store.update_user_settings::<AllLanguageSettings>(cx, f);
17451 });
17452 });
17453}
17454
17455pub(crate) fn update_test_project_settings(
17456 cx: &mut TestAppContext,
17457 f: impl Fn(&mut ProjectSettings),
17458) {
17459 cx.update(|cx| {
17460 SettingsStore::update_global(cx, |store, cx| {
17461 store.update_user_settings::<ProjectSettings>(cx, f);
17462 });
17463 });
17464}
17465
17466pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17467 cx.update(|cx| {
17468 assets::Assets.load_test_fonts(cx);
17469 let store = SettingsStore::test(cx);
17470 cx.set_global(store);
17471 theme::init(theme::LoadThemes::JustBase, cx);
17472 release_channel::init(SemanticVersion::default(), cx);
17473 client::init_settings(cx);
17474 language::init(cx);
17475 Project::init_settings(cx);
17476 workspace::init_settings(cx);
17477 crate::init(cx);
17478 });
17479
17480 update_test_language_settings(cx, f);
17481}
17482
17483#[track_caller]
17484fn assert_hunk_revert(
17485 not_reverted_text_with_selections: &str,
17486 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17487 expected_reverted_text_with_selections: &str,
17488 base_text: &str,
17489 cx: &mut EditorLspTestContext,
17490) {
17491 cx.set_state(not_reverted_text_with_selections);
17492 cx.set_head_text(base_text);
17493 cx.executor().run_until_parked();
17494
17495 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17496 let snapshot = editor.snapshot(window, cx);
17497 let reverted_hunk_statuses = snapshot
17498 .buffer_snapshot
17499 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17500 .map(|hunk| hunk.status().kind)
17501 .collect::<Vec<_>>();
17502
17503 editor.git_restore(&Default::default(), window, cx);
17504 reverted_hunk_statuses
17505 });
17506 cx.executor().run_until_parked();
17507 cx.assert_editor_state(expected_reverted_text_with_selections);
17508 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17509}