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 futures::StreamExt;
11use gpui::{
12 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
13 WindowBounds, WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
23 Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::IndentGuide;
27use parking_lot::Mutex;
28use pretty_assertions::{assert_eq, assert_ne};
29use project::{buffer_store::BufferChangeSet, FakeFs};
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use std::{
37 iter,
38 sync::atomic::{self, AtomicUsize},
39};
40use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
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 { level: 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 { level: 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 { level: 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 };
1516
1517 let move_to_end = MoveToEndOfLine {
1518 stop_at_soft_wraps: true,
1519 };
1520
1521 let editor = cx.add_window(|window, cx| {
1522 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1523 build_editor(buffer, window, cx)
1524 });
1525 _ = editor.update(cx, |editor, window, cx| {
1526 editor.change_selections(None, window, cx, |s| {
1527 s.select_display_ranges([
1528 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1529 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1530 ]);
1531 });
1532 });
1533
1534 _ = editor.update(cx, |editor, window, cx| {
1535 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1536 assert_eq!(
1537 editor.selections.display_ranges(cx),
1538 &[
1539 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1540 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1541 ]
1542 );
1543 });
1544
1545 _ = editor.update(cx, |editor, window, cx| {
1546 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1547 assert_eq!(
1548 editor.selections.display_ranges(cx),
1549 &[
1550 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1551 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1552 ]
1553 );
1554 });
1555
1556 _ = editor.update(cx, |editor, window, cx| {
1557 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1558 assert_eq!(
1559 editor.selections.display_ranges(cx),
1560 &[
1561 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1562 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1563 ]
1564 );
1565 });
1566
1567 _ = editor.update(cx, |editor, window, cx| {
1568 editor.move_to_end_of_line(&move_to_end, window, cx);
1569 assert_eq!(
1570 editor.selections.display_ranges(cx),
1571 &[
1572 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1573 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1574 ]
1575 );
1576 });
1577
1578 // Moving to the end of line again is a no-op.
1579 _ = editor.update(cx, |editor, window, cx| {
1580 editor.move_to_end_of_line(&move_to_end, window, cx);
1581 assert_eq!(
1582 editor.selections.display_ranges(cx),
1583 &[
1584 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1585 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1586 ]
1587 );
1588 });
1589
1590 _ = editor.update(cx, |editor, window, cx| {
1591 editor.move_left(&MoveLeft, window, cx);
1592 editor.select_to_beginning_of_line(
1593 &SelectToBeginningOfLine {
1594 stop_at_soft_wraps: true,
1595 },
1596 window,
1597 cx,
1598 );
1599 assert_eq!(
1600 editor.selections.display_ranges(cx),
1601 &[
1602 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1603 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1604 ]
1605 );
1606 });
1607
1608 _ = editor.update(cx, |editor, window, cx| {
1609 editor.select_to_beginning_of_line(
1610 &SelectToBeginningOfLine {
1611 stop_at_soft_wraps: true,
1612 },
1613 window,
1614 cx,
1615 );
1616 assert_eq!(
1617 editor.selections.display_ranges(cx),
1618 &[
1619 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1620 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1621 ]
1622 );
1623 });
1624
1625 _ = editor.update(cx, |editor, window, cx| {
1626 editor.select_to_beginning_of_line(
1627 &SelectToBeginningOfLine {
1628 stop_at_soft_wraps: true,
1629 },
1630 window,
1631 cx,
1632 );
1633 assert_eq!(
1634 editor.selections.display_ranges(cx),
1635 &[
1636 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1637 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1638 ]
1639 );
1640 });
1641
1642 _ = editor.update(cx, |editor, window, cx| {
1643 editor.select_to_end_of_line(
1644 &SelectToEndOfLine {
1645 stop_at_soft_wraps: true,
1646 },
1647 window,
1648 cx,
1649 );
1650 assert_eq!(
1651 editor.selections.display_ranges(cx),
1652 &[
1653 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1654 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1655 ]
1656 );
1657 });
1658
1659 _ = editor.update(cx, |editor, window, cx| {
1660 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1661 assert_eq!(editor.display_text(cx), "ab\n de");
1662 assert_eq!(
1663 editor.selections.display_ranges(cx),
1664 &[
1665 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1666 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1667 ]
1668 );
1669 });
1670
1671 _ = editor.update(cx, |editor, window, cx| {
1672 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
1673 assert_eq!(editor.display_text(cx), "\n");
1674 assert_eq!(
1675 editor.selections.display_ranges(cx),
1676 &[
1677 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1679 ]
1680 );
1681 });
1682}
1683
1684#[gpui::test]
1685fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1686 init_test(cx, |_| {});
1687 let move_to_beg = MoveToBeginningOfLine {
1688 stop_at_soft_wraps: false,
1689 };
1690
1691 let move_to_end = MoveToEndOfLine {
1692 stop_at_soft_wraps: false,
1693 };
1694
1695 let editor = cx.add_window(|window, cx| {
1696 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1697 build_editor(buffer, window, cx)
1698 });
1699
1700 _ = editor.update(cx, |editor, window, cx| {
1701 editor.set_wrap_width(Some(140.0.into()), cx);
1702
1703 // We expect the following lines after wrapping
1704 // ```
1705 // thequickbrownfox
1706 // jumpedoverthelazydo
1707 // gs
1708 // ```
1709 // The final `gs` was soft-wrapped onto a new line.
1710 assert_eq!(
1711 "thequickbrownfox\njumpedoverthelaz\nydogs",
1712 editor.display_text(cx),
1713 );
1714
1715 // First, let's assert behavior on the first line, that was not soft-wrapped.
1716 // Start the cursor at the `k` on the first line
1717 editor.change_selections(None, window, cx, |s| {
1718 s.select_display_ranges([
1719 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1720 ]);
1721 });
1722
1723 // Moving to the beginning of the line should put us at the beginning of the line.
1724 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1725 assert_eq!(
1726 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1727 editor.selections.display_ranges(cx)
1728 );
1729
1730 // Moving to the end of the line should put us at the end of the line.
1731 editor.move_to_end_of_line(&move_to_end, window, cx);
1732 assert_eq!(
1733 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1734 editor.selections.display_ranges(cx)
1735 );
1736
1737 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1738 // Start the cursor at the last line (`y` that was wrapped to a new line)
1739 editor.change_selections(None, window, cx, |s| {
1740 s.select_display_ranges([
1741 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1742 ]);
1743 });
1744
1745 // Moving to the beginning of the line should put us at the start of the second line of
1746 // display text, i.e., the `j`.
1747 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1748 assert_eq!(
1749 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1750 editor.selections.display_ranges(cx)
1751 );
1752
1753 // Moving to the beginning of the line again should be a no-op.
1754 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1755 assert_eq!(
1756 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1757 editor.selections.display_ranges(cx)
1758 );
1759
1760 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1761 // next display line.
1762 editor.move_to_end_of_line(&move_to_end, window, cx);
1763 assert_eq!(
1764 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1765 editor.selections.display_ranges(cx)
1766 );
1767
1768 // Moving to the end of the line again should be a no-op.
1769 editor.move_to_end_of_line(&move_to_end, window, cx);
1770 assert_eq!(
1771 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1772 editor.selections.display_ranges(cx)
1773 );
1774 });
1775}
1776
1777#[gpui::test]
1778fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1779 init_test(cx, |_| {});
1780
1781 let editor = cx.add_window(|window, cx| {
1782 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1783 build_editor(buffer, window, cx)
1784 });
1785 _ = editor.update(cx, |editor, window, cx| {
1786 editor.change_selections(None, window, cx, |s| {
1787 s.select_display_ranges([
1788 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1789 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1790 ])
1791 });
1792
1793 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1794 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1795
1796 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1797 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1798
1799 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1800 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1801
1802 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1803 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1804
1805 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1806 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1807
1808 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1809 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1810
1811 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1812 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1813
1814 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1815 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1816
1817 editor.move_right(&MoveRight, window, cx);
1818 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1819 assert_selection_ranges(
1820 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1821 editor,
1822 cx,
1823 );
1824
1825 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1826 assert_selection_ranges(
1827 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1828 editor,
1829 cx,
1830 );
1831
1832 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1833 assert_selection_ranges(
1834 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1835 editor,
1836 cx,
1837 );
1838 });
1839}
1840
1841#[gpui::test]
1842fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1843 init_test(cx, |_| {});
1844
1845 let editor = cx.add_window(|window, cx| {
1846 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1847 build_editor(buffer, window, cx)
1848 });
1849
1850 _ = editor.update(cx, |editor, window, cx| {
1851 editor.set_wrap_width(Some(140.0.into()), cx);
1852 assert_eq!(
1853 editor.display_text(cx),
1854 "use one::{\n two::three::\n four::five\n};"
1855 );
1856
1857 editor.change_selections(None, window, cx, |s| {
1858 s.select_display_ranges([
1859 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1860 ]);
1861 });
1862
1863 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1864 assert_eq!(
1865 editor.selections.display_ranges(cx),
1866 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1867 );
1868
1869 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1870 assert_eq!(
1871 editor.selections.display_ranges(cx),
1872 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1873 );
1874
1875 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1876 assert_eq!(
1877 editor.selections.display_ranges(cx),
1878 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1879 );
1880
1881 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1882 assert_eq!(
1883 editor.selections.display_ranges(cx),
1884 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1885 );
1886
1887 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1888 assert_eq!(
1889 editor.selections.display_ranges(cx),
1890 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1891 );
1892
1893 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1894 assert_eq!(
1895 editor.selections.display_ranges(cx),
1896 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1897 );
1898 });
1899}
1900
1901#[gpui::test]
1902async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1903 init_test(cx, |_| {});
1904 let mut cx = EditorTestContext::new(cx).await;
1905
1906 let line_height = cx.editor(|editor, window, _| {
1907 editor
1908 .style()
1909 .unwrap()
1910 .text
1911 .line_height_in_pixels(window.rem_size())
1912 });
1913 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1914
1915 cx.set_state(
1916 &r#"ˇone
1917 two
1918
1919 three
1920 fourˇ
1921 five
1922
1923 six"#
1924 .unindent(),
1925 );
1926
1927 cx.update_editor(|editor, window, cx| {
1928 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1929 });
1930 cx.assert_editor_state(
1931 &r#"one
1932 two
1933 ˇ
1934 three
1935 four
1936 five
1937 ˇ
1938 six"#
1939 .unindent(),
1940 );
1941
1942 cx.update_editor(|editor, window, cx| {
1943 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1944 });
1945 cx.assert_editor_state(
1946 &r#"one
1947 two
1948
1949 three
1950 four
1951 five
1952 ˇ
1953 sixˇ"#
1954 .unindent(),
1955 );
1956
1957 cx.update_editor(|editor, window, cx| {
1958 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1959 });
1960 cx.assert_editor_state(
1961 &r#"one
1962 two
1963
1964 three
1965 four
1966 five
1967
1968 sixˇ"#
1969 .unindent(),
1970 );
1971
1972 cx.update_editor(|editor, window, cx| {
1973 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1974 });
1975 cx.assert_editor_state(
1976 &r#"one
1977 two
1978
1979 three
1980 four
1981 five
1982 ˇ
1983 six"#
1984 .unindent(),
1985 );
1986
1987 cx.update_editor(|editor, window, cx| {
1988 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1989 });
1990 cx.assert_editor_state(
1991 &r#"one
1992 two
1993 ˇ
1994 three
1995 four
1996 five
1997
1998 six"#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, window, cx| {
2003 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2004 });
2005 cx.assert_editor_state(
2006 &r#"ˇone
2007 two
2008
2009 three
2010 four
2011 five
2012
2013 six"#
2014 .unindent(),
2015 );
2016}
2017
2018#[gpui::test]
2019async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
2020 init_test(cx, |_| {});
2021 let mut cx = EditorTestContext::new(cx).await;
2022 let line_height = cx.editor(|editor, window, _| {
2023 editor
2024 .style()
2025 .unwrap()
2026 .text
2027 .line_height_in_pixels(window.rem_size())
2028 });
2029 let window = cx.window;
2030 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2031
2032 cx.set_state(
2033 r#"ˇone
2034 two
2035 three
2036 four
2037 five
2038 six
2039 seven
2040 eight
2041 nine
2042 ten
2043 "#,
2044 );
2045
2046 cx.update_editor(|editor, window, cx| {
2047 assert_eq!(
2048 editor.snapshot(window, cx).scroll_position(),
2049 gpui::Point::new(0., 0.)
2050 );
2051 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2052 assert_eq!(
2053 editor.snapshot(window, cx).scroll_position(),
2054 gpui::Point::new(0., 3.)
2055 );
2056 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2057 assert_eq!(
2058 editor.snapshot(window, cx).scroll_position(),
2059 gpui::Point::new(0., 6.)
2060 );
2061 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2062 assert_eq!(
2063 editor.snapshot(window, cx).scroll_position(),
2064 gpui::Point::new(0., 3.)
2065 );
2066
2067 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2068 assert_eq!(
2069 editor.snapshot(window, cx).scroll_position(),
2070 gpui::Point::new(0., 1.)
2071 );
2072 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2073 assert_eq!(
2074 editor.snapshot(window, cx).scroll_position(),
2075 gpui::Point::new(0., 3.)
2076 );
2077 });
2078}
2079
2080#[gpui::test]
2081async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2082 init_test(cx, |_| {});
2083 let mut cx = EditorTestContext::new(cx).await;
2084
2085 let line_height = cx.update_editor(|editor, window, cx| {
2086 editor.set_vertical_scroll_margin(2, cx);
2087 editor
2088 .style()
2089 .unwrap()
2090 .text
2091 .line_height_in_pixels(window.rem_size())
2092 });
2093 let window = cx.window;
2094 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2095
2096 cx.set_state(
2097 r#"ˇone
2098 two
2099 three
2100 four
2101 five
2102 six
2103 seven
2104 eight
2105 nine
2106 ten
2107 "#,
2108 );
2109 cx.update_editor(|editor, window, cx| {
2110 assert_eq!(
2111 editor.snapshot(window, cx).scroll_position(),
2112 gpui::Point::new(0., 0.0)
2113 );
2114 });
2115
2116 // Add a cursor below the visible area. Since both cursors cannot fit
2117 // on screen, the editor autoscrolls to reveal the newest cursor, and
2118 // allows the vertical scroll margin below that cursor.
2119 cx.update_editor(|editor, window, cx| {
2120 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2121 selections.select_ranges([
2122 Point::new(0, 0)..Point::new(0, 0),
2123 Point::new(6, 0)..Point::new(6, 0),
2124 ]);
2125 })
2126 });
2127 cx.update_editor(|editor, window, cx| {
2128 assert_eq!(
2129 editor.snapshot(window, cx).scroll_position(),
2130 gpui::Point::new(0., 3.0)
2131 );
2132 });
2133
2134 // Move down. The editor cursor scrolls down to track the newest cursor.
2135 cx.update_editor(|editor, window, cx| {
2136 editor.move_down(&Default::default(), window, cx);
2137 });
2138 cx.update_editor(|editor, window, cx| {
2139 assert_eq!(
2140 editor.snapshot(window, cx).scroll_position(),
2141 gpui::Point::new(0., 4.0)
2142 );
2143 });
2144
2145 // Add a cursor above the visible area. Since both cursors fit on screen,
2146 // the editor scrolls to show both.
2147 cx.update_editor(|editor, window, cx| {
2148 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2149 selections.select_ranges([
2150 Point::new(1, 0)..Point::new(1, 0),
2151 Point::new(6, 0)..Point::new(6, 0),
2152 ]);
2153 })
2154 });
2155 cx.update_editor(|editor, window, cx| {
2156 assert_eq!(
2157 editor.snapshot(window, cx).scroll_position(),
2158 gpui::Point::new(0., 1.0)
2159 );
2160 });
2161}
2162
2163#[gpui::test]
2164async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2165 init_test(cx, |_| {});
2166 let mut cx = EditorTestContext::new(cx).await;
2167
2168 let line_height = cx.editor(|editor, window, _cx| {
2169 editor
2170 .style()
2171 .unwrap()
2172 .text
2173 .line_height_in_pixels(window.rem_size())
2174 });
2175 let window = cx.window;
2176 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2177 cx.set_state(
2178 &r#"
2179 ˇone
2180 two
2181 threeˇ
2182 four
2183 five
2184 six
2185 seven
2186 eight
2187 nine
2188 ten
2189 "#
2190 .unindent(),
2191 );
2192
2193 cx.update_editor(|editor, window, cx| {
2194 editor.move_page_down(&MovePageDown::default(), window, cx)
2195 });
2196 cx.assert_editor_state(
2197 &r#"
2198 one
2199 two
2200 three
2201 ˇfour
2202 five
2203 sixˇ
2204 seven
2205 eight
2206 nine
2207 ten
2208 "#
2209 .unindent(),
2210 );
2211
2212 cx.update_editor(|editor, window, cx| {
2213 editor.move_page_down(&MovePageDown::default(), window, cx)
2214 });
2215 cx.assert_editor_state(
2216 &r#"
2217 one
2218 two
2219 three
2220 four
2221 five
2222 six
2223 ˇseven
2224 eight
2225 nineˇ
2226 ten
2227 "#
2228 .unindent(),
2229 );
2230
2231 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2232 cx.assert_editor_state(
2233 &r#"
2234 one
2235 two
2236 three
2237 ˇfour
2238 five
2239 sixˇ
2240 seven
2241 eight
2242 nine
2243 ten
2244 "#
2245 .unindent(),
2246 );
2247
2248 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2249 cx.assert_editor_state(
2250 &r#"
2251 ˇone
2252 two
2253 threeˇ
2254 four
2255 five
2256 six
2257 seven
2258 eight
2259 nine
2260 ten
2261 "#
2262 .unindent(),
2263 );
2264
2265 // Test select collapsing
2266 cx.update_editor(|editor, window, cx| {
2267 editor.move_page_down(&MovePageDown::default(), window, cx);
2268 editor.move_page_down(&MovePageDown::default(), window, cx);
2269 editor.move_page_down(&MovePageDown::default(), window, cx);
2270 });
2271 cx.assert_editor_state(
2272 &r#"
2273 one
2274 two
2275 three
2276 four
2277 five
2278 six
2279 seven
2280 eight
2281 nine
2282 ˇten
2283 ˇ"#
2284 .unindent(),
2285 );
2286}
2287
2288#[gpui::test]
2289async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2290 init_test(cx, |_| {});
2291 let mut cx = EditorTestContext::new(cx).await;
2292 cx.set_state("one «two threeˇ» four");
2293 cx.update_editor(|editor, window, cx| {
2294 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
2295 assert_eq!(editor.text(cx), " four");
2296 });
2297}
2298
2299#[gpui::test]
2300fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2301 init_test(cx, |_| {});
2302
2303 let editor = cx.add_window(|window, cx| {
2304 let buffer = MultiBuffer::build_simple("one two three four", cx);
2305 build_editor(buffer.clone(), window, cx)
2306 });
2307
2308 _ = editor.update(cx, |editor, window, cx| {
2309 editor.change_selections(None, window, cx, |s| {
2310 s.select_display_ranges([
2311 // an empty selection - the preceding word fragment is deleted
2312 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2313 // characters selected - they are deleted
2314 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2315 ])
2316 });
2317 editor.delete_to_previous_word_start(
2318 &DeleteToPreviousWordStart {
2319 ignore_newlines: false,
2320 },
2321 window,
2322 cx,
2323 );
2324 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2325 });
2326
2327 _ = editor.update(cx, |editor, window, cx| {
2328 editor.change_selections(None, window, cx, |s| {
2329 s.select_display_ranges([
2330 // an empty selection - the following word fragment is deleted
2331 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2332 // characters selected - they are deleted
2333 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2334 ])
2335 });
2336 editor.delete_to_next_word_end(
2337 &DeleteToNextWordEnd {
2338 ignore_newlines: false,
2339 },
2340 window,
2341 cx,
2342 );
2343 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2344 });
2345}
2346
2347#[gpui::test]
2348fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2349 init_test(cx, |_| {});
2350
2351 let editor = cx.add_window(|window, cx| {
2352 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2353 build_editor(buffer.clone(), window, cx)
2354 });
2355 let del_to_prev_word_start = DeleteToPreviousWordStart {
2356 ignore_newlines: false,
2357 };
2358 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2359 ignore_newlines: true,
2360 };
2361
2362 _ = editor.update(cx, |editor, window, cx| {
2363 editor.change_selections(None, window, cx, |s| {
2364 s.select_display_ranges([
2365 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2366 ])
2367 });
2368 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2369 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2370 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2371 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2372 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2373 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2374 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2375 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2376 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2377 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2378 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2379 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2380 });
2381}
2382
2383#[gpui::test]
2384fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2385 init_test(cx, |_| {});
2386
2387 let editor = cx.add_window(|window, cx| {
2388 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2389 build_editor(buffer.clone(), window, cx)
2390 });
2391 let del_to_next_word_end = DeleteToNextWordEnd {
2392 ignore_newlines: false,
2393 };
2394 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2395 ignore_newlines: true,
2396 };
2397
2398 _ = editor.update(cx, |editor, window, cx| {
2399 editor.change_selections(None, window, cx, |s| {
2400 s.select_display_ranges([
2401 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2402 ])
2403 });
2404 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2405 assert_eq!(
2406 editor.buffer.read(cx).read(cx).text(),
2407 "one\n two\nthree\n four"
2408 );
2409 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2410 assert_eq!(
2411 editor.buffer.read(cx).read(cx).text(),
2412 "\n two\nthree\n four"
2413 );
2414 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2415 assert_eq!(
2416 editor.buffer.read(cx).read(cx).text(),
2417 "two\nthree\n four"
2418 );
2419 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2420 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2421 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2422 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2423 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2424 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2425 });
2426}
2427
2428#[gpui::test]
2429fn test_newline(cx: &mut TestAppContext) {
2430 init_test(cx, |_| {});
2431
2432 let editor = cx.add_window(|window, cx| {
2433 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2434 build_editor(buffer.clone(), window, cx)
2435 });
2436
2437 _ = editor.update(cx, |editor, window, cx| {
2438 editor.change_selections(None, window, cx, |s| {
2439 s.select_display_ranges([
2440 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2441 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2442 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2443 ])
2444 });
2445
2446 editor.newline(&Newline, window, cx);
2447 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2448 });
2449}
2450
2451#[gpui::test]
2452fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2453 init_test(cx, |_| {});
2454
2455 let editor = cx.add_window(|window, cx| {
2456 let buffer = MultiBuffer::build_simple(
2457 "
2458 a
2459 b(
2460 X
2461 )
2462 c(
2463 X
2464 )
2465 "
2466 .unindent()
2467 .as_str(),
2468 cx,
2469 );
2470 let mut editor = build_editor(buffer.clone(), window, cx);
2471 editor.change_selections(None, window, cx, |s| {
2472 s.select_ranges([
2473 Point::new(2, 4)..Point::new(2, 5),
2474 Point::new(5, 4)..Point::new(5, 5),
2475 ])
2476 });
2477 editor
2478 });
2479
2480 _ = editor.update(cx, |editor, window, cx| {
2481 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2482 editor.buffer.update(cx, |buffer, cx| {
2483 buffer.edit(
2484 [
2485 (Point::new(1, 2)..Point::new(3, 0), ""),
2486 (Point::new(4, 2)..Point::new(6, 0), ""),
2487 ],
2488 None,
2489 cx,
2490 );
2491 assert_eq!(
2492 buffer.read(cx).text(),
2493 "
2494 a
2495 b()
2496 c()
2497 "
2498 .unindent()
2499 );
2500 });
2501 assert_eq!(
2502 editor.selections.ranges(cx),
2503 &[
2504 Point::new(1, 2)..Point::new(1, 2),
2505 Point::new(2, 2)..Point::new(2, 2),
2506 ],
2507 );
2508
2509 editor.newline(&Newline, window, cx);
2510 assert_eq!(
2511 editor.text(cx),
2512 "
2513 a
2514 b(
2515 )
2516 c(
2517 )
2518 "
2519 .unindent()
2520 );
2521
2522 // The selections are moved after the inserted newlines
2523 assert_eq!(
2524 editor.selections.ranges(cx),
2525 &[
2526 Point::new(2, 0)..Point::new(2, 0),
2527 Point::new(4, 0)..Point::new(4, 0),
2528 ],
2529 );
2530 });
2531}
2532
2533#[gpui::test]
2534async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2535 init_test(cx, |settings| {
2536 settings.defaults.tab_size = NonZeroU32::new(4)
2537 });
2538
2539 let language = Arc::new(
2540 Language::new(
2541 LanguageConfig::default(),
2542 Some(tree_sitter_rust::LANGUAGE.into()),
2543 )
2544 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2545 .unwrap(),
2546 );
2547
2548 let mut cx = EditorTestContext::new(cx).await;
2549 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2550 cx.set_state(indoc! {"
2551 const a: ˇA = (
2552 (ˇ
2553 «const_functionˇ»(ˇ),
2554 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2555 )ˇ
2556 ˇ);ˇ
2557 "});
2558
2559 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2560 cx.assert_editor_state(indoc! {"
2561 ˇ
2562 const a: A = (
2563 ˇ
2564 (
2565 ˇ
2566 ˇ
2567 const_function(),
2568 ˇ
2569 ˇ
2570 ˇ
2571 ˇ
2572 something_else,
2573 ˇ
2574 )
2575 ˇ
2576 ˇ
2577 );
2578 "});
2579}
2580
2581#[gpui::test]
2582async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2583 init_test(cx, |settings| {
2584 settings.defaults.tab_size = NonZeroU32::new(4)
2585 });
2586
2587 let language = Arc::new(
2588 Language::new(
2589 LanguageConfig::default(),
2590 Some(tree_sitter_rust::LANGUAGE.into()),
2591 )
2592 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2593 .unwrap(),
2594 );
2595
2596 let mut cx = EditorTestContext::new(cx).await;
2597 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2598 cx.set_state(indoc! {"
2599 const a: ˇA = (
2600 (ˇ
2601 «const_functionˇ»(ˇ),
2602 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2603 )ˇ
2604 ˇ);ˇ
2605 "});
2606
2607 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2608 cx.assert_editor_state(indoc! {"
2609 const a: A = (
2610 ˇ
2611 (
2612 ˇ
2613 const_function(),
2614 ˇ
2615 ˇ
2616 something_else,
2617 ˇ
2618 ˇ
2619 ˇ
2620 ˇ
2621 )
2622 ˇ
2623 );
2624 ˇ
2625 ˇ
2626 "});
2627}
2628
2629#[gpui::test]
2630async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2631 init_test(cx, |settings| {
2632 settings.defaults.tab_size = NonZeroU32::new(4)
2633 });
2634
2635 let language = Arc::new(Language::new(
2636 LanguageConfig {
2637 line_comments: vec!["//".into()],
2638 ..LanguageConfig::default()
2639 },
2640 None,
2641 ));
2642 {
2643 let mut cx = EditorTestContext::new(cx).await;
2644 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2645 cx.set_state(indoc! {"
2646 // Fooˇ
2647 "});
2648
2649 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2650 cx.assert_editor_state(indoc! {"
2651 // Foo
2652 //ˇ
2653 "});
2654 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2655 cx.set_state(indoc! {"
2656 ˇ// Foo
2657 "});
2658 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2659 cx.assert_editor_state(indoc! {"
2660
2661 ˇ// Foo
2662 "});
2663 }
2664 // Ensure that comment continuations can be disabled.
2665 update_test_language_settings(cx, |settings| {
2666 settings.defaults.extend_comment_on_newline = Some(false);
2667 });
2668 let mut cx = EditorTestContext::new(cx).await;
2669 cx.set_state(indoc! {"
2670 // Fooˇ
2671 "});
2672 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2673 cx.assert_editor_state(indoc! {"
2674 // Foo
2675 ˇ
2676 "});
2677}
2678
2679#[gpui::test]
2680fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2681 init_test(cx, |_| {});
2682
2683 let editor = cx.add_window(|window, cx| {
2684 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2685 let mut editor = build_editor(buffer.clone(), window, cx);
2686 editor.change_selections(None, window, cx, |s| {
2687 s.select_ranges([3..4, 11..12, 19..20])
2688 });
2689 editor
2690 });
2691
2692 _ = editor.update(cx, |editor, window, cx| {
2693 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2694 editor.buffer.update(cx, |buffer, cx| {
2695 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2696 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2697 });
2698 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2699
2700 editor.insert("Z", window, cx);
2701 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2702
2703 // The selections are moved after the inserted characters
2704 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2705 });
2706}
2707
2708#[gpui::test]
2709async fn test_tab(cx: &mut gpui::TestAppContext) {
2710 init_test(cx, |settings| {
2711 settings.defaults.tab_size = NonZeroU32::new(3)
2712 });
2713
2714 let mut cx = EditorTestContext::new(cx).await;
2715 cx.set_state(indoc! {"
2716 ˇabˇc
2717 ˇ🏀ˇ🏀ˇefg
2718 dˇ
2719 "});
2720 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2721 cx.assert_editor_state(indoc! {"
2722 ˇab ˇc
2723 ˇ🏀 ˇ🏀 ˇefg
2724 d ˇ
2725 "});
2726
2727 cx.set_state(indoc! {"
2728 a
2729 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2730 "});
2731 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2732 cx.assert_editor_state(indoc! {"
2733 a
2734 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2735 "});
2736}
2737
2738#[gpui::test]
2739async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2740 init_test(cx, |_| {});
2741
2742 let mut cx = EditorTestContext::new(cx).await;
2743 let language = Arc::new(
2744 Language::new(
2745 LanguageConfig::default(),
2746 Some(tree_sitter_rust::LANGUAGE.into()),
2747 )
2748 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2749 .unwrap(),
2750 );
2751 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2752
2753 // cursors that are already at the suggested indent level insert
2754 // a soft tab. cursors that are to the left of the suggested indent
2755 // auto-indent their line.
2756 cx.set_state(indoc! {"
2757 ˇ
2758 const a: B = (
2759 c(
2760 d(
2761 ˇ
2762 )
2763 ˇ
2764 ˇ )
2765 );
2766 "});
2767 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2768 cx.assert_editor_state(indoc! {"
2769 ˇ
2770 const a: B = (
2771 c(
2772 d(
2773 ˇ
2774 )
2775 ˇ
2776 ˇ)
2777 );
2778 "});
2779
2780 // handle auto-indent when there are multiple cursors on the same line
2781 cx.set_state(indoc! {"
2782 const a: B = (
2783 c(
2784 ˇ ˇ
2785 ˇ )
2786 );
2787 "});
2788 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2789 cx.assert_editor_state(indoc! {"
2790 const a: B = (
2791 c(
2792 ˇ
2793 ˇ)
2794 );
2795 "});
2796}
2797
2798#[gpui::test]
2799async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2800 init_test(cx, |settings| {
2801 settings.defaults.tab_size = NonZeroU32::new(4)
2802 });
2803
2804 let language = Arc::new(
2805 Language::new(
2806 LanguageConfig::default(),
2807 Some(tree_sitter_rust::LANGUAGE.into()),
2808 )
2809 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2810 .unwrap(),
2811 );
2812
2813 let mut cx = EditorTestContext::new(cx).await;
2814 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2815 cx.set_state(indoc! {"
2816 fn a() {
2817 if b {
2818 \t ˇc
2819 }
2820 }
2821 "});
2822
2823 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2824 cx.assert_editor_state(indoc! {"
2825 fn a() {
2826 if b {
2827 ˇc
2828 }
2829 }
2830 "});
2831}
2832
2833#[gpui::test]
2834async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2835 init_test(cx, |settings| {
2836 settings.defaults.tab_size = NonZeroU32::new(4);
2837 });
2838
2839 let mut cx = EditorTestContext::new(cx).await;
2840
2841 cx.set_state(indoc! {"
2842 «oneˇ» «twoˇ»
2843 three
2844 four
2845 "});
2846 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2847 cx.assert_editor_state(indoc! {"
2848 «oneˇ» «twoˇ»
2849 three
2850 four
2851 "});
2852
2853 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2854 cx.assert_editor_state(indoc! {"
2855 «oneˇ» «twoˇ»
2856 three
2857 four
2858 "});
2859
2860 // select across line ending
2861 cx.set_state(indoc! {"
2862 one two
2863 t«hree
2864 ˇ» four
2865 "});
2866 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2867 cx.assert_editor_state(indoc! {"
2868 one two
2869 t«hree
2870 ˇ» four
2871 "});
2872
2873 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2874 cx.assert_editor_state(indoc! {"
2875 one two
2876 t«hree
2877 ˇ» four
2878 "});
2879
2880 // Ensure that indenting/outdenting works when the cursor is at column 0.
2881 cx.set_state(indoc! {"
2882 one two
2883 ˇthree
2884 four
2885 "});
2886 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2887 cx.assert_editor_state(indoc! {"
2888 one two
2889 ˇthree
2890 four
2891 "});
2892
2893 cx.set_state(indoc! {"
2894 one two
2895 ˇ three
2896 four
2897 "});
2898 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2899 cx.assert_editor_state(indoc! {"
2900 one two
2901 ˇthree
2902 four
2903 "});
2904}
2905
2906#[gpui::test]
2907async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2908 init_test(cx, |settings| {
2909 settings.defaults.hard_tabs = Some(true);
2910 });
2911
2912 let mut cx = EditorTestContext::new(cx).await;
2913
2914 // select two ranges on one line
2915 cx.set_state(indoc! {"
2916 «oneˇ» «twoˇ»
2917 three
2918 four
2919 "});
2920 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2921 cx.assert_editor_state(indoc! {"
2922 \t«oneˇ» «twoˇ»
2923 three
2924 four
2925 "});
2926 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2927 cx.assert_editor_state(indoc! {"
2928 \t\t«oneˇ» «twoˇ»
2929 three
2930 four
2931 "});
2932 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2933 cx.assert_editor_state(indoc! {"
2934 \t«oneˇ» «twoˇ»
2935 three
2936 four
2937 "});
2938 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2939 cx.assert_editor_state(indoc! {"
2940 «oneˇ» «twoˇ»
2941 three
2942 four
2943 "});
2944
2945 // select across a line ending
2946 cx.set_state(indoc! {"
2947 one two
2948 t«hree
2949 ˇ»four
2950 "});
2951 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2952 cx.assert_editor_state(indoc! {"
2953 one two
2954 \tt«hree
2955 ˇ»four
2956 "});
2957 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2958 cx.assert_editor_state(indoc! {"
2959 one two
2960 \t\tt«hree
2961 ˇ»four
2962 "});
2963 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2964 cx.assert_editor_state(indoc! {"
2965 one two
2966 \tt«hree
2967 ˇ»four
2968 "});
2969 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2970 cx.assert_editor_state(indoc! {"
2971 one two
2972 t«hree
2973 ˇ»four
2974 "});
2975
2976 // Ensure that indenting/outdenting works when the cursor is at column 0.
2977 cx.set_state(indoc! {"
2978 one two
2979 ˇthree
2980 four
2981 "});
2982 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2983 cx.assert_editor_state(indoc! {"
2984 one two
2985 ˇthree
2986 four
2987 "});
2988 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2989 cx.assert_editor_state(indoc! {"
2990 one two
2991 \tˇthree
2992 four
2993 "});
2994 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2995 cx.assert_editor_state(indoc! {"
2996 one two
2997 ˇthree
2998 four
2999 "});
3000}
3001
3002#[gpui::test]
3003fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3004 init_test(cx, |settings| {
3005 settings.languages.extend([
3006 (
3007 "TOML".into(),
3008 LanguageSettingsContent {
3009 tab_size: NonZeroU32::new(2),
3010 ..Default::default()
3011 },
3012 ),
3013 (
3014 "Rust".into(),
3015 LanguageSettingsContent {
3016 tab_size: NonZeroU32::new(4),
3017 ..Default::default()
3018 },
3019 ),
3020 ]);
3021 });
3022
3023 let toml_language = Arc::new(Language::new(
3024 LanguageConfig {
3025 name: "TOML".into(),
3026 ..Default::default()
3027 },
3028 None,
3029 ));
3030 let rust_language = Arc::new(Language::new(
3031 LanguageConfig {
3032 name: "Rust".into(),
3033 ..Default::default()
3034 },
3035 None,
3036 ));
3037
3038 let toml_buffer =
3039 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3040 let rust_buffer =
3041 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3042 let multibuffer = cx.new(|cx| {
3043 let mut multibuffer = MultiBuffer::new(ReadWrite);
3044 multibuffer.push_excerpts(
3045 toml_buffer.clone(),
3046 [ExcerptRange {
3047 context: Point::new(0, 0)..Point::new(2, 0),
3048 primary: None,
3049 }],
3050 cx,
3051 );
3052 multibuffer.push_excerpts(
3053 rust_buffer.clone(),
3054 [ExcerptRange {
3055 context: Point::new(0, 0)..Point::new(1, 0),
3056 primary: None,
3057 }],
3058 cx,
3059 );
3060 multibuffer
3061 });
3062
3063 cx.add_window(|window, cx| {
3064 let mut editor = build_editor(multibuffer, window, cx);
3065
3066 assert_eq!(
3067 editor.text(cx),
3068 indoc! {"
3069 a = 1
3070 b = 2
3071
3072 const c: usize = 3;
3073 "}
3074 );
3075
3076 select_ranges(
3077 &mut editor,
3078 indoc! {"
3079 «aˇ» = 1
3080 b = 2
3081
3082 «const c:ˇ» usize = 3;
3083 "},
3084 window,
3085 cx,
3086 );
3087
3088 editor.tab(&Tab, window, cx);
3089 assert_text_with_selections(
3090 &mut editor,
3091 indoc! {"
3092 «aˇ» = 1
3093 b = 2
3094
3095 «const c:ˇ» usize = 3;
3096 "},
3097 cx,
3098 );
3099 editor.tab_prev(&TabPrev, window, cx);
3100 assert_text_with_selections(
3101 &mut editor,
3102 indoc! {"
3103 «aˇ» = 1
3104 b = 2
3105
3106 «const c:ˇ» usize = 3;
3107 "},
3108 cx,
3109 );
3110
3111 editor
3112 });
3113}
3114
3115#[gpui::test]
3116async fn test_backspace(cx: &mut gpui::TestAppContext) {
3117 init_test(cx, |_| {});
3118
3119 let mut cx = EditorTestContext::new(cx).await;
3120
3121 // Basic backspace
3122 cx.set_state(indoc! {"
3123 onˇe two three
3124 fou«rˇ» five six
3125 seven «ˇeight nine
3126 »ten
3127 "});
3128 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3129 cx.assert_editor_state(indoc! {"
3130 oˇe two three
3131 fouˇ five six
3132 seven ˇten
3133 "});
3134
3135 // Test backspace inside and around indents
3136 cx.set_state(indoc! {"
3137 zero
3138 ˇone
3139 ˇtwo
3140 ˇ ˇ ˇ three
3141 ˇ ˇ four
3142 "});
3143 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3144 cx.assert_editor_state(indoc! {"
3145 zero
3146 ˇone
3147 ˇtwo
3148 ˇ threeˇ four
3149 "});
3150
3151 // Test backspace with line_mode set to true
3152 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3153 cx.set_state(indoc! {"
3154 The ˇquick ˇbrown
3155 fox jumps over
3156 the lazy dog
3157 ˇThe qu«ick bˇ»rown"});
3158 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3159 cx.assert_editor_state(indoc! {"
3160 ˇfox jumps over
3161 the lazy dogˇ"});
3162}
3163
3164#[gpui::test]
3165async fn test_delete(cx: &mut gpui::TestAppContext) {
3166 init_test(cx, |_| {});
3167
3168 let mut cx = EditorTestContext::new(cx).await;
3169 cx.set_state(indoc! {"
3170 onˇe two three
3171 fou«rˇ» five six
3172 seven «ˇeight nine
3173 »ten
3174 "});
3175 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3176 cx.assert_editor_state(indoc! {"
3177 onˇ two three
3178 fouˇ five six
3179 seven ˇten
3180 "});
3181
3182 // Test backspace with line_mode set to true
3183 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3184 cx.set_state(indoc! {"
3185 The ˇquick ˇbrown
3186 fox «ˇjum»ps over
3187 the lazy dog
3188 ˇThe qu«ick bˇ»rown"});
3189 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3190 cx.assert_editor_state("ˇthe lazy dogˇ");
3191}
3192
3193#[gpui::test]
3194fn test_delete_line(cx: &mut TestAppContext) {
3195 init_test(cx, |_| {});
3196
3197 let editor = cx.add_window(|window, cx| {
3198 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3199 build_editor(buffer, window, cx)
3200 });
3201 _ = editor.update(cx, |editor, window, cx| {
3202 editor.change_selections(None, window, cx, |s| {
3203 s.select_display_ranges([
3204 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3205 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3206 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3207 ])
3208 });
3209 editor.delete_line(&DeleteLine, window, cx);
3210 assert_eq!(editor.display_text(cx), "ghi");
3211 assert_eq!(
3212 editor.selections.display_ranges(cx),
3213 vec![
3214 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3215 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3216 ]
3217 );
3218 });
3219
3220 let editor = cx.add_window(|window, cx| {
3221 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3222 build_editor(buffer, window, cx)
3223 });
3224 _ = editor.update(cx, |editor, window, cx| {
3225 editor.change_selections(None, window, cx, |s| {
3226 s.select_display_ranges([
3227 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3228 ])
3229 });
3230 editor.delete_line(&DeleteLine, window, cx);
3231 assert_eq!(editor.display_text(cx), "ghi\n");
3232 assert_eq!(
3233 editor.selections.display_ranges(cx),
3234 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3235 );
3236 });
3237}
3238
3239#[gpui::test]
3240fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3241 init_test(cx, |_| {});
3242
3243 cx.add_window(|window, cx| {
3244 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3245 let mut editor = build_editor(buffer.clone(), window, cx);
3246 let buffer = buffer.read(cx).as_singleton().unwrap();
3247
3248 assert_eq!(
3249 editor.selections.ranges::<Point>(cx),
3250 &[Point::new(0, 0)..Point::new(0, 0)]
3251 );
3252
3253 // When on single line, replace newline at end by space
3254 editor.join_lines(&JoinLines, window, cx);
3255 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3256 assert_eq!(
3257 editor.selections.ranges::<Point>(cx),
3258 &[Point::new(0, 3)..Point::new(0, 3)]
3259 );
3260
3261 // When multiple lines are selected, remove newlines that are spanned by the selection
3262 editor.change_selections(None, window, cx, |s| {
3263 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3264 });
3265 editor.join_lines(&JoinLines, window, cx);
3266 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3267 assert_eq!(
3268 editor.selections.ranges::<Point>(cx),
3269 &[Point::new(0, 11)..Point::new(0, 11)]
3270 );
3271
3272 // Undo should be transactional
3273 editor.undo(&Undo, window, cx);
3274 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3275 assert_eq!(
3276 editor.selections.ranges::<Point>(cx),
3277 &[Point::new(0, 5)..Point::new(2, 2)]
3278 );
3279
3280 // When joining an empty line don't insert a space
3281 editor.change_selections(None, window, cx, |s| {
3282 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3283 });
3284 editor.join_lines(&JoinLines, window, cx);
3285 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3286 assert_eq!(
3287 editor.selections.ranges::<Point>(cx),
3288 [Point::new(2, 3)..Point::new(2, 3)]
3289 );
3290
3291 // We can remove trailing newlines
3292 editor.join_lines(&JoinLines, window, cx);
3293 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3294 assert_eq!(
3295 editor.selections.ranges::<Point>(cx),
3296 [Point::new(2, 3)..Point::new(2, 3)]
3297 );
3298
3299 // We don't blow up on the last line
3300 editor.join_lines(&JoinLines, window, cx);
3301 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3302 assert_eq!(
3303 editor.selections.ranges::<Point>(cx),
3304 [Point::new(2, 3)..Point::new(2, 3)]
3305 );
3306
3307 // reset to test indentation
3308 editor.buffer.update(cx, |buffer, cx| {
3309 buffer.edit(
3310 [
3311 (Point::new(1, 0)..Point::new(1, 2), " "),
3312 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3313 ],
3314 None,
3315 cx,
3316 )
3317 });
3318
3319 // We remove any leading spaces
3320 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3321 editor.change_selections(None, window, cx, |s| {
3322 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3323 });
3324 editor.join_lines(&JoinLines, window, cx);
3325 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3326
3327 // We don't insert a space for a line containing only spaces
3328 editor.join_lines(&JoinLines, window, cx);
3329 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3330
3331 // We ignore any leading tabs
3332 editor.join_lines(&JoinLines, window, cx);
3333 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3334
3335 editor
3336 });
3337}
3338
3339#[gpui::test]
3340fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3341 init_test(cx, |_| {});
3342
3343 cx.add_window(|window, cx| {
3344 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3345 let mut editor = build_editor(buffer.clone(), window, cx);
3346 let buffer = buffer.read(cx).as_singleton().unwrap();
3347
3348 editor.change_selections(None, window, cx, |s| {
3349 s.select_ranges([
3350 Point::new(0, 2)..Point::new(1, 1),
3351 Point::new(1, 2)..Point::new(1, 2),
3352 Point::new(3, 1)..Point::new(3, 2),
3353 ])
3354 });
3355
3356 editor.join_lines(&JoinLines, window, cx);
3357 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3358
3359 assert_eq!(
3360 editor.selections.ranges::<Point>(cx),
3361 [
3362 Point::new(0, 7)..Point::new(0, 7),
3363 Point::new(1, 3)..Point::new(1, 3)
3364 ]
3365 );
3366 editor
3367 });
3368}
3369
3370#[gpui::test]
3371async fn test_join_lines_with_git_diff_base(
3372 executor: BackgroundExecutor,
3373 cx: &mut gpui::TestAppContext,
3374) {
3375 init_test(cx, |_| {});
3376
3377 let mut cx = EditorTestContext::new(cx).await;
3378
3379 let diff_base = r#"
3380 Line 0
3381 Line 1
3382 Line 2
3383 Line 3
3384 "#
3385 .unindent();
3386
3387 cx.set_state(
3388 &r#"
3389 ˇLine 0
3390 Line 1
3391 Line 2
3392 Line 3
3393 "#
3394 .unindent(),
3395 );
3396
3397 cx.set_diff_base(&diff_base);
3398 executor.run_until_parked();
3399
3400 // Join lines
3401 cx.update_editor(|editor, window, cx| {
3402 editor.join_lines(&JoinLines, window, cx);
3403 });
3404 executor.run_until_parked();
3405
3406 cx.assert_editor_state(
3407 &r#"
3408 Line 0ˇ Line 1
3409 Line 2
3410 Line 3
3411 "#
3412 .unindent(),
3413 );
3414 // Join again
3415 cx.update_editor(|editor, window, cx| {
3416 editor.join_lines(&JoinLines, window, cx);
3417 });
3418 executor.run_until_parked();
3419
3420 cx.assert_editor_state(
3421 &r#"
3422 Line 0 Line 1ˇ Line 2
3423 Line 3
3424 "#
3425 .unindent(),
3426 );
3427}
3428
3429#[gpui::test]
3430async fn test_custom_newlines_cause_no_false_positive_diffs(
3431 executor: BackgroundExecutor,
3432 cx: &mut gpui::TestAppContext,
3433) {
3434 init_test(cx, |_| {});
3435 let mut cx = EditorTestContext::new(cx).await;
3436 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3437 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3438 executor.run_until_parked();
3439
3440 cx.update_editor(|editor, window, cx| {
3441 let snapshot = editor.snapshot(window, cx);
3442 assert_eq!(
3443 snapshot
3444 .buffer_snapshot
3445 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3446 .collect::<Vec<_>>(),
3447 Vec::new(),
3448 "Should not have any diffs for files with custom newlines"
3449 );
3450 });
3451}
3452
3453#[gpui::test]
3454async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3455 init_test(cx, |_| {});
3456
3457 let mut cx = EditorTestContext::new(cx).await;
3458
3459 // Test sort_lines_case_insensitive()
3460 cx.set_state(indoc! {"
3461 «z
3462 y
3463 x
3464 Z
3465 Y
3466 Xˇ»
3467 "});
3468 cx.update_editor(|e, window, cx| {
3469 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3470 });
3471 cx.assert_editor_state(indoc! {"
3472 «x
3473 X
3474 y
3475 Y
3476 z
3477 Zˇ»
3478 "});
3479
3480 // Test reverse_lines()
3481 cx.set_state(indoc! {"
3482 «5
3483 4
3484 3
3485 2
3486 1ˇ»
3487 "});
3488 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3489 cx.assert_editor_state(indoc! {"
3490 «1
3491 2
3492 3
3493 4
3494 5ˇ»
3495 "});
3496
3497 // Skip testing shuffle_line()
3498
3499 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3500 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3501
3502 // Don't manipulate when cursor is on single line, but expand the selection
3503 cx.set_state(indoc! {"
3504 ddˇdd
3505 ccc
3506 bb
3507 a
3508 "});
3509 cx.update_editor(|e, window, cx| {
3510 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3511 });
3512 cx.assert_editor_state(indoc! {"
3513 «ddddˇ»
3514 ccc
3515 bb
3516 a
3517 "});
3518
3519 // Basic manipulate case
3520 // Start selection moves to column 0
3521 // End of selection shrinks to fit shorter line
3522 cx.set_state(indoc! {"
3523 dd«d
3524 ccc
3525 bb
3526 aaaaaˇ»
3527 "});
3528 cx.update_editor(|e, window, cx| {
3529 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3530 });
3531 cx.assert_editor_state(indoc! {"
3532 «aaaaa
3533 bb
3534 ccc
3535 dddˇ»
3536 "});
3537
3538 // Manipulate case with newlines
3539 cx.set_state(indoc! {"
3540 dd«d
3541 ccc
3542
3543 bb
3544 aaaaa
3545
3546 ˇ»
3547 "});
3548 cx.update_editor(|e, window, cx| {
3549 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3550 });
3551 cx.assert_editor_state(indoc! {"
3552 «
3553
3554 aaaaa
3555 bb
3556 ccc
3557 dddˇ»
3558
3559 "});
3560
3561 // Adding new line
3562 cx.set_state(indoc! {"
3563 aa«a
3564 bbˇ»b
3565 "});
3566 cx.update_editor(|e, window, cx| {
3567 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3568 });
3569 cx.assert_editor_state(indoc! {"
3570 «aaa
3571 bbb
3572 added_lineˇ»
3573 "});
3574
3575 // Removing line
3576 cx.set_state(indoc! {"
3577 aa«a
3578 bbbˇ»
3579 "});
3580 cx.update_editor(|e, window, cx| {
3581 e.manipulate_lines(window, cx, |lines| {
3582 lines.pop();
3583 })
3584 });
3585 cx.assert_editor_state(indoc! {"
3586 «aaaˇ»
3587 "});
3588
3589 // Removing all lines
3590 cx.set_state(indoc! {"
3591 aa«a
3592 bbbˇ»
3593 "});
3594 cx.update_editor(|e, window, cx| {
3595 e.manipulate_lines(window, cx, |lines| {
3596 lines.drain(..);
3597 })
3598 });
3599 cx.assert_editor_state(indoc! {"
3600 ˇ
3601 "});
3602}
3603
3604#[gpui::test]
3605async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3606 init_test(cx, |_| {});
3607
3608 let mut cx = EditorTestContext::new(cx).await;
3609
3610 // Consider continuous selection as single selection
3611 cx.set_state(indoc! {"
3612 Aaa«aa
3613 cˇ»c«c
3614 bb
3615 aaaˇ»aa
3616 "});
3617 cx.update_editor(|e, window, cx| {
3618 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3619 });
3620 cx.assert_editor_state(indoc! {"
3621 «Aaaaa
3622 ccc
3623 bb
3624 aaaaaˇ»
3625 "});
3626
3627 cx.set_state(indoc! {"
3628 Aaa«aa
3629 cˇ»c«c
3630 bb
3631 aaaˇ»aa
3632 "});
3633 cx.update_editor(|e, window, cx| {
3634 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3635 });
3636 cx.assert_editor_state(indoc! {"
3637 «Aaaaa
3638 ccc
3639 bbˇ»
3640 "});
3641
3642 // Consider non continuous selection as distinct dedup operations
3643 cx.set_state(indoc! {"
3644 «aaaaa
3645 bb
3646 aaaaa
3647 aaaaaˇ»
3648
3649 aaa«aaˇ»
3650 "});
3651 cx.update_editor(|e, window, cx| {
3652 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3653 });
3654 cx.assert_editor_state(indoc! {"
3655 «aaaaa
3656 bbˇ»
3657
3658 «aaaaaˇ»
3659 "});
3660}
3661
3662#[gpui::test]
3663async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3664 init_test(cx, |_| {});
3665
3666 let mut cx = EditorTestContext::new(cx).await;
3667
3668 cx.set_state(indoc! {"
3669 «Aaa
3670 aAa
3671 Aaaˇ»
3672 "});
3673 cx.update_editor(|e, window, cx| {
3674 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3675 });
3676 cx.assert_editor_state(indoc! {"
3677 «Aaa
3678 aAaˇ»
3679 "});
3680
3681 cx.set_state(indoc! {"
3682 «Aaa
3683 aAa
3684 aaAˇ»
3685 "});
3686 cx.update_editor(|e, window, cx| {
3687 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3688 });
3689 cx.assert_editor_state(indoc! {"
3690 «Aaaˇ»
3691 "});
3692}
3693
3694#[gpui::test]
3695async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3696 init_test(cx, |_| {});
3697
3698 let mut cx = EditorTestContext::new(cx).await;
3699
3700 // Manipulate with multiple selections on a single line
3701 cx.set_state(indoc! {"
3702 dd«dd
3703 cˇ»c«c
3704 bb
3705 aaaˇ»aa
3706 "});
3707 cx.update_editor(|e, window, cx| {
3708 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3709 });
3710 cx.assert_editor_state(indoc! {"
3711 «aaaaa
3712 bb
3713 ccc
3714 ddddˇ»
3715 "});
3716
3717 // Manipulate with multiple disjoin selections
3718 cx.set_state(indoc! {"
3719 5«
3720 4
3721 3
3722 2
3723 1ˇ»
3724
3725 dd«dd
3726 ccc
3727 bb
3728 aaaˇ»aa
3729 "});
3730 cx.update_editor(|e, window, cx| {
3731 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3732 });
3733 cx.assert_editor_state(indoc! {"
3734 «1
3735 2
3736 3
3737 4
3738 5ˇ»
3739
3740 «aaaaa
3741 bb
3742 ccc
3743 ddddˇ»
3744 "});
3745
3746 // Adding lines on each selection
3747 cx.set_state(indoc! {"
3748 2«
3749 1ˇ»
3750
3751 bb«bb
3752 aaaˇ»aa
3753 "});
3754 cx.update_editor(|e, window, cx| {
3755 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3756 });
3757 cx.assert_editor_state(indoc! {"
3758 «2
3759 1
3760 added lineˇ»
3761
3762 «bbbb
3763 aaaaa
3764 added lineˇ»
3765 "});
3766
3767 // Removing lines on each selection
3768 cx.set_state(indoc! {"
3769 2«
3770 1ˇ»
3771
3772 bb«bb
3773 aaaˇ»aa
3774 "});
3775 cx.update_editor(|e, window, cx| {
3776 e.manipulate_lines(window, cx, |lines| {
3777 lines.pop();
3778 })
3779 });
3780 cx.assert_editor_state(indoc! {"
3781 «2ˇ»
3782
3783 «bbbbˇ»
3784 "});
3785}
3786
3787#[gpui::test]
3788async fn test_manipulate_text(cx: &mut TestAppContext) {
3789 init_test(cx, |_| {});
3790
3791 let mut cx = EditorTestContext::new(cx).await;
3792
3793 // Test convert_to_upper_case()
3794 cx.set_state(indoc! {"
3795 «hello worldˇ»
3796 "});
3797 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3798 cx.assert_editor_state(indoc! {"
3799 «HELLO WORLDˇ»
3800 "});
3801
3802 // Test convert_to_lower_case()
3803 cx.set_state(indoc! {"
3804 «HELLO WORLDˇ»
3805 "});
3806 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3807 cx.assert_editor_state(indoc! {"
3808 «hello worldˇ»
3809 "});
3810
3811 // Test multiple line, single selection case
3812 cx.set_state(indoc! {"
3813 «The quick brown
3814 fox jumps over
3815 the lazy dogˇ»
3816 "});
3817 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3818 cx.assert_editor_state(indoc! {"
3819 «The Quick Brown
3820 Fox Jumps Over
3821 The Lazy Dogˇ»
3822 "});
3823
3824 // Test multiple line, single selection case
3825 cx.set_state(indoc! {"
3826 «The quick brown
3827 fox jumps over
3828 the lazy dogˇ»
3829 "});
3830 cx.update_editor(|e, window, cx| {
3831 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3832 });
3833 cx.assert_editor_state(indoc! {"
3834 «TheQuickBrown
3835 FoxJumpsOver
3836 TheLazyDogˇ»
3837 "});
3838
3839 // From here on out, test more complex cases of manipulate_text()
3840
3841 // Test no selection case - should affect words cursors are in
3842 // Cursor at beginning, middle, and end of word
3843 cx.set_state(indoc! {"
3844 ˇhello big beauˇtiful worldˇ
3845 "});
3846 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3847 cx.assert_editor_state(indoc! {"
3848 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3849 "});
3850
3851 // Test multiple selections on a single line and across multiple lines
3852 cx.set_state(indoc! {"
3853 «Theˇ» quick «brown
3854 foxˇ» jumps «overˇ»
3855 the «lazyˇ» dog
3856 "});
3857 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3858 cx.assert_editor_state(indoc! {"
3859 «THEˇ» quick «BROWN
3860 FOXˇ» jumps «OVERˇ»
3861 the «LAZYˇ» dog
3862 "});
3863
3864 // Test case where text length grows
3865 cx.set_state(indoc! {"
3866 «tschüߡ»
3867 "});
3868 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3869 cx.assert_editor_state(indoc! {"
3870 «TSCHÜSSˇ»
3871 "});
3872
3873 // Test to make sure we don't crash when text shrinks
3874 cx.set_state(indoc! {"
3875 aaa_bbbˇ
3876 "});
3877 cx.update_editor(|e, window, cx| {
3878 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3879 });
3880 cx.assert_editor_state(indoc! {"
3881 «aaaBbbˇ»
3882 "});
3883
3884 // Test to make sure we all aware of the fact that each word can grow and shrink
3885 // Final selections should be aware of this fact
3886 cx.set_state(indoc! {"
3887 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3888 "});
3889 cx.update_editor(|e, window, cx| {
3890 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3891 });
3892 cx.assert_editor_state(indoc! {"
3893 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3894 "});
3895
3896 cx.set_state(indoc! {"
3897 «hElLo, WoRld!ˇ»
3898 "});
3899 cx.update_editor(|e, window, cx| {
3900 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3901 });
3902 cx.assert_editor_state(indoc! {"
3903 «HeLlO, wOrLD!ˇ»
3904 "});
3905}
3906
3907#[gpui::test]
3908fn test_duplicate_line(cx: &mut TestAppContext) {
3909 init_test(cx, |_| {});
3910
3911 let editor = cx.add_window(|window, cx| {
3912 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3913 build_editor(buffer, window, cx)
3914 });
3915 _ = editor.update(cx, |editor, window, cx| {
3916 editor.change_selections(None, window, cx, |s| {
3917 s.select_display_ranges([
3918 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3919 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3920 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3921 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3922 ])
3923 });
3924 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3925 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3926 assert_eq!(
3927 editor.selections.display_ranges(cx),
3928 vec![
3929 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3930 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3931 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3932 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3933 ]
3934 );
3935 });
3936
3937 let editor = cx.add_window(|window, cx| {
3938 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3939 build_editor(buffer, window, cx)
3940 });
3941 _ = editor.update(cx, |editor, window, cx| {
3942 editor.change_selections(None, window, cx, |s| {
3943 s.select_display_ranges([
3944 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3945 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3946 ])
3947 });
3948 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3949 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3950 assert_eq!(
3951 editor.selections.display_ranges(cx),
3952 vec![
3953 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3954 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3955 ]
3956 );
3957 });
3958
3959 // With `move_upwards` the selections stay in place, except for
3960 // the lines inserted above them
3961 let editor = cx.add_window(|window, cx| {
3962 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3963 build_editor(buffer, window, cx)
3964 });
3965 _ = editor.update(cx, |editor, window, cx| {
3966 editor.change_selections(None, window, cx, |s| {
3967 s.select_display_ranges([
3968 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3969 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3970 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3971 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3972 ])
3973 });
3974 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3975 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3976 assert_eq!(
3977 editor.selections.display_ranges(cx),
3978 vec![
3979 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3980 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3981 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3982 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3983 ]
3984 );
3985 });
3986
3987 let editor = cx.add_window(|window, cx| {
3988 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3989 build_editor(buffer, window, cx)
3990 });
3991 _ = editor.update(cx, |editor, window, cx| {
3992 editor.change_selections(None, window, cx, |s| {
3993 s.select_display_ranges([
3994 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3995 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3996 ])
3997 });
3998 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3999 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4000 assert_eq!(
4001 editor.selections.display_ranges(cx),
4002 vec![
4003 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4004 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4005 ]
4006 );
4007 });
4008
4009 let editor = cx.add_window(|window, cx| {
4010 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4011 build_editor(buffer, window, cx)
4012 });
4013 _ = editor.update(cx, |editor, window, cx| {
4014 editor.change_selections(None, window, cx, |s| {
4015 s.select_display_ranges([
4016 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4017 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4018 ])
4019 });
4020 editor.duplicate_selection(&DuplicateSelection, window, cx);
4021 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4022 assert_eq!(
4023 editor.selections.display_ranges(cx),
4024 vec![
4025 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4026 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4027 ]
4028 );
4029 });
4030}
4031
4032#[gpui::test]
4033fn test_move_line_up_down(cx: &mut TestAppContext) {
4034 init_test(cx, |_| {});
4035
4036 let editor = cx.add_window(|window, cx| {
4037 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4038 build_editor(buffer, window, cx)
4039 });
4040 _ = editor.update(cx, |editor, window, cx| {
4041 editor.fold_creases(
4042 vec![
4043 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4044 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4045 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4046 ],
4047 true,
4048 window,
4049 cx,
4050 );
4051 editor.change_selections(None, window, cx, |s| {
4052 s.select_display_ranges([
4053 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4054 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4055 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4056 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4057 ])
4058 });
4059 assert_eq!(
4060 editor.display_text(cx),
4061 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4062 );
4063
4064 editor.move_line_up(&MoveLineUp, window, cx);
4065 assert_eq!(
4066 editor.display_text(cx),
4067 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4068 );
4069 assert_eq!(
4070 editor.selections.display_ranges(cx),
4071 vec![
4072 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4073 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4074 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4075 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4076 ]
4077 );
4078 });
4079
4080 _ = editor.update(cx, |editor, window, cx| {
4081 editor.move_line_down(&MoveLineDown, window, cx);
4082 assert_eq!(
4083 editor.display_text(cx),
4084 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4085 );
4086 assert_eq!(
4087 editor.selections.display_ranges(cx),
4088 vec![
4089 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4090 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4091 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4092 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4093 ]
4094 );
4095 });
4096
4097 _ = editor.update(cx, |editor, window, cx| {
4098 editor.move_line_down(&MoveLineDown, window, cx);
4099 assert_eq!(
4100 editor.display_text(cx),
4101 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4102 );
4103 assert_eq!(
4104 editor.selections.display_ranges(cx),
4105 vec![
4106 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4107 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4108 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4109 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4110 ]
4111 );
4112 });
4113
4114 _ = editor.update(cx, |editor, window, cx| {
4115 editor.move_line_up(&MoveLineUp, window, cx);
4116 assert_eq!(
4117 editor.display_text(cx),
4118 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4119 );
4120 assert_eq!(
4121 editor.selections.display_ranges(cx),
4122 vec![
4123 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4124 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4125 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4126 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4127 ]
4128 );
4129 });
4130}
4131
4132#[gpui::test]
4133fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4134 init_test(cx, |_| {});
4135
4136 let editor = cx.add_window(|window, cx| {
4137 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4138 build_editor(buffer, window, cx)
4139 });
4140 _ = editor.update(cx, |editor, window, cx| {
4141 let snapshot = editor.buffer.read(cx).snapshot(cx);
4142 editor.insert_blocks(
4143 [BlockProperties {
4144 style: BlockStyle::Fixed,
4145 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4146 height: 1,
4147 render: Arc::new(|_| div().into_any()),
4148 priority: 0,
4149 }],
4150 Some(Autoscroll::fit()),
4151 cx,
4152 );
4153 editor.change_selections(None, window, cx, |s| {
4154 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4155 });
4156 editor.move_line_down(&MoveLineDown, window, cx);
4157 });
4158}
4159
4160#[gpui::test]
4161async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4162 init_test(cx, |_| {});
4163
4164 let mut cx = EditorTestContext::new(cx).await;
4165 cx.set_state(
4166 &"
4167 ˇzero
4168 one
4169 two
4170 three
4171 four
4172 five
4173 "
4174 .unindent(),
4175 );
4176
4177 // Create a four-line block that replaces three lines of text.
4178 cx.update_editor(|editor, window, cx| {
4179 let snapshot = editor.snapshot(window, cx);
4180 let snapshot = &snapshot.buffer_snapshot;
4181 let placement = BlockPlacement::Replace(
4182 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4183 );
4184 editor.insert_blocks(
4185 [BlockProperties {
4186 placement,
4187 height: 4,
4188 style: BlockStyle::Sticky,
4189 render: Arc::new(|_| gpui::div().into_any_element()),
4190 priority: 0,
4191 }],
4192 None,
4193 cx,
4194 );
4195 });
4196
4197 // Move down so that the cursor touches the block.
4198 cx.update_editor(|editor, window, cx| {
4199 editor.move_down(&Default::default(), window, cx);
4200 });
4201 cx.assert_editor_state(
4202 &"
4203 zero
4204 «one
4205 two
4206 threeˇ»
4207 four
4208 five
4209 "
4210 .unindent(),
4211 );
4212
4213 // Move down past the block.
4214 cx.update_editor(|editor, window, cx| {
4215 editor.move_down(&Default::default(), window, cx);
4216 });
4217 cx.assert_editor_state(
4218 &"
4219 zero
4220 one
4221 two
4222 three
4223 ˇfour
4224 five
4225 "
4226 .unindent(),
4227 );
4228}
4229
4230#[gpui::test]
4231fn test_transpose(cx: &mut TestAppContext) {
4232 init_test(cx, |_| {});
4233
4234 _ = cx.add_window(|window, cx| {
4235 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4236 editor.set_style(EditorStyle::default(), window, cx);
4237 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4238 editor.transpose(&Default::default(), window, cx);
4239 assert_eq!(editor.text(cx), "bac");
4240 assert_eq!(editor.selections.ranges(cx), [2..2]);
4241
4242 editor.transpose(&Default::default(), window, cx);
4243 assert_eq!(editor.text(cx), "bca");
4244 assert_eq!(editor.selections.ranges(cx), [3..3]);
4245
4246 editor.transpose(&Default::default(), window, cx);
4247 assert_eq!(editor.text(cx), "bac");
4248 assert_eq!(editor.selections.ranges(cx), [3..3]);
4249
4250 editor
4251 });
4252
4253 _ = cx.add_window(|window, cx| {
4254 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4255 editor.set_style(EditorStyle::default(), window, cx);
4256 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4257 editor.transpose(&Default::default(), window, cx);
4258 assert_eq!(editor.text(cx), "acb\nde");
4259 assert_eq!(editor.selections.ranges(cx), [3..3]);
4260
4261 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4262 editor.transpose(&Default::default(), window, cx);
4263 assert_eq!(editor.text(cx), "acbd\ne");
4264 assert_eq!(editor.selections.ranges(cx), [5..5]);
4265
4266 editor.transpose(&Default::default(), window, cx);
4267 assert_eq!(editor.text(cx), "acbde\n");
4268 assert_eq!(editor.selections.ranges(cx), [6..6]);
4269
4270 editor.transpose(&Default::default(), window, cx);
4271 assert_eq!(editor.text(cx), "acbd\ne");
4272 assert_eq!(editor.selections.ranges(cx), [6..6]);
4273
4274 editor
4275 });
4276
4277 _ = cx.add_window(|window, cx| {
4278 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4279 editor.set_style(EditorStyle::default(), window, cx);
4280 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4281 editor.transpose(&Default::default(), window, cx);
4282 assert_eq!(editor.text(cx), "bacd\ne");
4283 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4284
4285 editor.transpose(&Default::default(), window, cx);
4286 assert_eq!(editor.text(cx), "bcade\n");
4287 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4288
4289 editor.transpose(&Default::default(), window, cx);
4290 assert_eq!(editor.text(cx), "bcda\ne");
4291 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4292
4293 editor.transpose(&Default::default(), window, cx);
4294 assert_eq!(editor.text(cx), "bcade\n");
4295 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4296
4297 editor.transpose(&Default::default(), window, cx);
4298 assert_eq!(editor.text(cx), "bcaed\n");
4299 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4300
4301 editor
4302 });
4303
4304 _ = cx.add_window(|window, cx| {
4305 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4306 editor.set_style(EditorStyle::default(), window, cx);
4307 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4308 editor.transpose(&Default::default(), window, cx);
4309 assert_eq!(editor.text(cx), "🏀🍐✋");
4310 assert_eq!(editor.selections.ranges(cx), [8..8]);
4311
4312 editor.transpose(&Default::default(), window, cx);
4313 assert_eq!(editor.text(cx), "🏀✋🍐");
4314 assert_eq!(editor.selections.ranges(cx), [11..11]);
4315
4316 editor.transpose(&Default::default(), window, cx);
4317 assert_eq!(editor.text(cx), "🏀🍐✋");
4318 assert_eq!(editor.selections.ranges(cx), [11..11]);
4319
4320 editor
4321 });
4322}
4323
4324#[gpui::test]
4325async fn test_rewrap(cx: &mut TestAppContext) {
4326 init_test(cx, |_| {});
4327
4328 let mut cx = EditorTestContext::new(cx).await;
4329
4330 let language_with_c_comments = Arc::new(Language::new(
4331 LanguageConfig {
4332 line_comments: vec!["// ".into()],
4333 ..LanguageConfig::default()
4334 },
4335 None,
4336 ));
4337 let language_with_pound_comments = Arc::new(Language::new(
4338 LanguageConfig {
4339 line_comments: vec!["# ".into()],
4340 ..LanguageConfig::default()
4341 },
4342 None,
4343 ));
4344 let markdown_language = Arc::new(Language::new(
4345 LanguageConfig {
4346 name: "Markdown".into(),
4347 ..LanguageConfig::default()
4348 },
4349 None,
4350 ));
4351 let language_with_doc_comments = Arc::new(Language::new(
4352 LanguageConfig {
4353 line_comments: vec!["// ".into(), "/// ".into()],
4354 ..LanguageConfig::default()
4355 },
4356 Some(tree_sitter_rust::LANGUAGE.into()),
4357 ));
4358
4359 let plaintext_language = Arc::new(Language::new(
4360 LanguageConfig {
4361 name: "Plain Text".into(),
4362 ..LanguageConfig::default()
4363 },
4364 None,
4365 ));
4366
4367 assert_rewrap(
4368 indoc! {"
4369 // ˇ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.
4370 "},
4371 indoc! {"
4372 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4373 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4374 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4375 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4376 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4377 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4378 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4379 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4380 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4381 // porttitor id. Aliquam id accumsan eros.
4382 "},
4383 language_with_c_comments.clone(),
4384 &mut cx,
4385 );
4386
4387 // Test that rewrapping works inside of a selection
4388 assert_rewrap(
4389 indoc! {"
4390 «// 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.ˇ»
4391 "},
4392 indoc! {"
4393 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4394 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4395 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4396 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4397 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4398 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4399 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4400 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4401 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4402 // porttitor id. Aliquam id accumsan eros.ˇ»
4403 "},
4404 language_with_c_comments.clone(),
4405 &mut cx,
4406 );
4407
4408 // Test that cursors that expand to the same region are collapsed.
4409 assert_rewrap(
4410 indoc! {"
4411 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4412 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4413 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4414 // ˇ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.
4415 "},
4416 indoc! {"
4417 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4418 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4419 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4420 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4421 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4422 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4423 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4424 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4425 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4426 // porttitor id. Aliquam id accumsan eros.
4427 "},
4428 language_with_c_comments.clone(),
4429 &mut cx,
4430 );
4431
4432 // Test that non-contiguous selections are treated separately.
4433 assert_rewrap(
4434 indoc! {"
4435 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4436 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4437 //
4438 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4439 // ˇ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.
4440 "},
4441 indoc! {"
4442 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4443 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4444 // auctor, eu lacinia sapien scelerisque.
4445 //
4446 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4447 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4448 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4449 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4450 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4451 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4452 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4453 "},
4454 language_with_c_comments.clone(),
4455 &mut cx,
4456 );
4457
4458 // Test that different comment prefixes are supported.
4459 assert_rewrap(
4460 indoc! {"
4461 # ˇ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.
4462 "},
4463 indoc! {"
4464 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4465 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4466 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4467 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4468 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4469 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4470 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4471 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4472 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4473 # accumsan eros.
4474 "},
4475 language_with_pound_comments.clone(),
4476 &mut cx,
4477 );
4478
4479 // Test that rewrapping is ignored outside of comments in most languages.
4480 assert_rewrap(
4481 indoc! {"
4482 /// Adds two numbers.
4483 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4484 fn add(a: u32, b: u32) -> u32 {
4485 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ˇ
4486 }
4487 "},
4488 indoc! {"
4489 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4490 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4491 fn add(a: u32, b: u32) -> u32 {
4492 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ˇ
4493 }
4494 "},
4495 language_with_doc_comments.clone(),
4496 &mut cx,
4497 );
4498
4499 // Test that rewrapping works in Markdown and Plain Text languages.
4500 assert_rewrap(
4501 indoc! {"
4502 # Hello
4503
4504 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.
4505 "},
4506 indoc! {"
4507 # Hello
4508
4509 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4510 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4511 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4512 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4513 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4514 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4515 Integer sit amet scelerisque nisi.
4516 "},
4517 markdown_language,
4518 &mut cx,
4519 );
4520
4521 assert_rewrap(
4522 indoc! {"
4523 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.
4524 "},
4525 indoc! {"
4526 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4527 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4528 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4529 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4530 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4531 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4532 Integer sit amet scelerisque nisi.
4533 "},
4534 plaintext_language,
4535 &mut cx,
4536 );
4537
4538 // Test rewrapping unaligned comments in a selection.
4539 assert_rewrap(
4540 indoc! {"
4541 fn foo() {
4542 if true {
4543 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4544 // Praesent semper egestas tellus id dignissim.ˇ»
4545 do_something();
4546 } else {
4547 //
4548 }
4549 }
4550 "},
4551 indoc! {"
4552 fn foo() {
4553 if true {
4554 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4555 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4556 // egestas tellus id dignissim.ˇ»
4557 do_something();
4558 } else {
4559 //
4560 }
4561 }
4562 "},
4563 language_with_doc_comments.clone(),
4564 &mut cx,
4565 );
4566
4567 assert_rewrap(
4568 indoc! {"
4569 fn foo() {
4570 if true {
4571 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4572 // Praesent semper egestas tellus id dignissim.»
4573 do_something();
4574 } else {
4575 //
4576 }
4577
4578 }
4579 "},
4580 indoc! {"
4581 fn foo() {
4582 if true {
4583 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4584 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4585 // egestas tellus id dignissim.»
4586 do_something();
4587 } else {
4588 //
4589 }
4590
4591 }
4592 "},
4593 language_with_doc_comments.clone(),
4594 &mut cx,
4595 );
4596
4597 #[track_caller]
4598 fn assert_rewrap(
4599 unwrapped_text: &str,
4600 wrapped_text: &str,
4601 language: Arc<Language>,
4602 cx: &mut EditorTestContext,
4603 ) {
4604 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4605 cx.set_state(unwrapped_text);
4606 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4607 cx.assert_editor_state(wrapped_text);
4608 }
4609}
4610
4611#[gpui::test]
4612async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4613 init_test(cx, |_| {});
4614
4615 let mut cx = EditorTestContext::new(cx).await;
4616
4617 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4618 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4619 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4620
4621 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4622 cx.set_state("two ˇfour ˇsix ˇ");
4623 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4624 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4625
4626 // Paste again but with only two cursors. Since the number of cursors doesn't
4627 // match the number of slices in the clipboard, the entire clipboard text
4628 // is pasted at each cursor.
4629 cx.set_state("ˇtwo one✅ four three six five ˇ");
4630 cx.update_editor(|e, window, cx| {
4631 e.handle_input("( ", window, cx);
4632 e.paste(&Paste, window, cx);
4633 e.handle_input(") ", window, cx);
4634 });
4635 cx.assert_editor_state(
4636 &([
4637 "( one✅ ",
4638 "three ",
4639 "five ) ˇtwo one✅ four three six five ( one✅ ",
4640 "three ",
4641 "five ) ˇ",
4642 ]
4643 .join("\n")),
4644 );
4645
4646 // Cut with three selections, one of which is full-line.
4647 cx.set_state(indoc! {"
4648 1«2ˇ»3
4649 4ˇ567
4650 «8ˇ»9"});
4651 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4652 cx.assert_editor_state(indoc! {"
4653 1ˇ3
4654 ˇ9"});
4655
4656 // Paste with three selections, noticing how the copied selection that was full-line
4657 // gets inserted before the second cursor.
4658 cx.set_state(indoc! {"
4659 1ˇ3
4660 9ˇ
4661 «oˇ»ne"});
4662 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4663 cx.assert_editor_state(indoc! {"
4664 12ˇ3
4665 4567
4666 9ˇ
4667 8ˇne"});
4668
4669 // Copy with a single cursor only, which writes the whole line into the clipboard.
4670 cx.set_state(indoc! {"
4671 The quick brown
4672 fox juˇmps over
4673 the lazy dog"});
4674 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4675 assert_eq!(
4676 cx.read_from_clipboard()
4677 .and_then(|item| item.text().as_deref().map(str::to_string)),
4678 Some("fox jumps over\n".to_string())
4679 );
4680
4681 // Paste with three selections, noticing how the copied full-line selection is inserted
4682 // before the empty selections but replaces the selection that is non-empty.
4683 cx.set_state(indoc! {"
4684 Tˇhe quick brown
4685 «foˇ»x jumps over
4686 tˇhe lazy dog"});
4687 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4688 cx.assert_editor_state(indoc! {"
4689 fox jumps over
4690 Tˇhe quick brown
4691 fox jumps over
4692 ˇx jumps over
4693 fox jumps over
4694 tˇhe lazy dog"});
4695}
4696
4697#[gpui::test]
4698async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4699 init_test(cx, |_| {});
4700
4701 let mut cx = EditorTestContext::new(cx).await;
4702 let language = Arc::new(Language::new(
4703 LanguageConfig::default(),
4704 Some(tree_sitter_rust::LANGUAGE.into()),
4705 ));
4706 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4707
4708 // Cut an indented block, without the leading whitespace.
4709 cx.set_state(indoc! {"
4710 const a: B = (
4711 c(),
4712 «d(
4713 e,
4714 f
4715 )ˇ»
4716 );
4717 "});
4718 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4719 cx.assert_editor_state(indoc! {"
4720 const a: B = (
4721 c(),
4722 ˇ
4723 );
4724 "});
4725
4726 // Paste it at the same position.
4727 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4728 cx.assert_editor_state(indoc! {"
4729 const a: B = (
4730 c(),
4731 d(
4732 e,
4733 f
4734 )ˇ
4735 );
4736 "});
4737
4738 // Paste it at a line with a lower indent level.
4739 cx.set_state(indoc! {"
4740 ˇ
4741 const a: B = (
4742 c(),
4743 );
4744 "});
4745 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4746 cx.assert_editor_state(indoc! {"
4747 d(
4748 e,
4749 f
4750 )ˇ
4751 const a: B = (
4752 c(),
4753 );
4754 "});
4755
4756 // Cut an indented block, with the leading whitespace.
4757 cx.set_state(indoc! {"
4758 const a: B = (
4759 c(),
4760 « d(
4761 e,
4762 f
4763 )
4764 ˇ»);
4765 "});
4766 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4767 cx.assert_editor_state(indoc! {"
4768 const a: B = (
4769 c(),
4770 ˇ);
4771 "});
4772
4773 // Paste it at the same position.
4774 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4775 cx.assert_editor_state(indoc! {"
4776 const a: B = (
4777 c(),
4778 d(
4779 e,
4780 f
4781 )
4782 ˇ);
4783 "});
4784
4785 // Paste it at a line with a higher indent level.
4786 cx.set_state(indoc! {"
4787 const a: B = (
4788 c(),
4789 d(
4790 e,
4791 fˇ
4792 )
4793 );
4794 "});
4795 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4796 cx.assert_editor_state(indoc! {"
4797 const a: B = (
4798 c(),
4799 d(
4800 e,
4801 f d(
4802 e,
4803 f
4804 )
4805 ˇ
4806 )
4807 );
4808 "});
4809}
4810
4811#[gpui::test]
4812fn test_select_all(cx: &mut TestAppContext) {
4813 init_test(cx, |_| {});
4814
4815 let editor = cx.add_window(|window, cx| {
4816 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4817 build_editor(buffer, window, cx)
4818 });
4819 _ = editor.update(cx, |editor, window, cx| {
4820 editor.select_all(&SelectAll, window, cx);
4821 assert_eq!(
4822 editor.selections.display_ranges(cx),
4823 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4824 );
4825 });
4826}
4827
4828#[gpui::test]
4829fn test_select_line(cx: &mut TestAppContext) {
4830 init_test(cx, |_| {});
4831
4832 let editor = cx.add_window(|window, cx| {
4833 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4834 build_editor(buffer, window, cx)
4835 });
4836 _ = editor.update(cx, |editor, window, cx| {
4837 editor.change_selections(None, window, cx, |s| {
4838 s.select_display_ranges([
4839 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4840 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4841 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4842 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4843 ])
4844 });
4845 editor.select_line(&SelectLine, window, cx);
4846 assert_eq!(
4847 editor.selections.display_ranges(cx),
4848 vec![
4849 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4850 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4851 ]
4852 );
4853 });
4854
4855 _ = editor.update(cx, |editor, window, cx| {
4856 editor.select_line(&SelectLine, window, cx);
4857 assert_eq!(
4858 editor.selections.display_ranges(cx),
4859 vec![
4860 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4861 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4862 ]
4863 );
4864 });
4865
4866 _ = editor.update(cx, |editor, window, cx| {
4867 editor.select_line(&SelectLine, window, cx);
4868 assert_eq!(
4869 editor.selections.display_ranges(cx),
4870 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4871 );
4872 });
4873}
4874
4875#[gpui::test]
4876fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4877 init_test(cx, |_| {});
4878
4879 let editor = cx.add_window(|window, cx| {
4880 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4881 build_editor(buffer, window, cx)
4882 });
4883 _ = editor.update(cx, |editor, window, cx| {
4884 editor.fold_creases(
4885 vec![
4886 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4887 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4888 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4889 ],
4890 true,
4891 window,
4892 cx,
4893 );
4894 editor.change_selections(None, window, cx, |s| {
4895 s.select_display_ranges([
4896 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4897 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4898 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4899 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4900 ])
4901 });
4902 assert_eq!(
4903 editor.display_text(cx),
4904 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4905 );
4906 });
4907
4908 _ = editor.update(cx, |editor, window, cx| {
4909 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
4910 assert_eq!(
4911 editor.display_text(cx),
4912 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4913 );
4914 assert_eq!(
4915 editor.selections.display_ranges(cx),
4916 [
4917 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4918 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4919 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4920 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4921 ]
4922 );
4923 });
4924
4925 _ = editor.update(cx, |editor, window, cx| {
4926 editor.change_selections(None, window, cx, |s| {
4927 s.select_display_ranges([
4928 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4929 ])
4930 });
4931 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
4932 assert_eq!(
4933 editor.display_text(cx),
4934 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4935 );
4936 assert_eq!(
4937 editor.selections.display_ranges(cx),
4938 [
4939 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4940 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4941 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4942 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4943 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4944 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4945 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4946 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4947 ]
4948 );
4949 });
4950}
4951
4952#[gpui::test]
4953async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4954 init_test(cx, |_| {});
4955
4956 let mut cx = EditorTestContext::new(cx).await;
4957
4958 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4959 cx.set_state(indoc!(
4960 r#"abc
4961 defˇghi
4962
4963 jk
4964 nlmo
4965 "#
4966 ));
4967
4968 cx.update_editor(|editor, window, cx| {
4969 editor.add_selection_above(&Default::default(), window, cx);
4970 });
4971
4972 cx.assert_editor_state(indoc!(
4973 r#"abcˇ
4974 defˇghi
4975
4976 jk
4977 nlmo
4978 "#
4979 ));
4980
4981 cx.update_editor(|editor, window, cx| {
4982 editor.add_selection_above(&Default::default(), window, cx);
4983 });
4984
4985 cx.assert_editor_state(indoc!(
4986 r#"abcˇ
4987 defˇghi
4988
4989 jk
4990 nlmo
4991 "#
4992 ));
4993
4994 cx.update_editor(|editor, window, cx| {
4995 editor.add_selection_below(&Default::default(), window, cx);
4996 });
4997
4998 cx.assert_editor_state(indoc!(
4999 r#"abc
5000 defˇghi
5001
5002 jk
5003 nlmo
5004 "#
5005 ));
5006
5007 cx.update_editor(|editor, window, cx| {
5008 editor.undo_selection(&Default::default(), window, cx);
5009 });
5010
5011 cx.assert_editor_state(indoc!(
5012 r#"abcˇ
5013 defˇghi
5014
5015 jk
5016 nlmo
5017 "#
5018 ));
5019
5020 cx.update_editor(|editor, window, cx| {
5021 editor.redo_selection(&Default::default(), window, cx);
5022 });
5023
5024 cx.assert_editor_state(indoc!(
5025 r#"abc
5026 defˇghi
5027
5028 jk
5029 nlmo
5030 "#
5031 ));
5032
5033 cx.update_editor(|editor, window, cx| {
5034 editor.add_selection_below(&Default::default(), window, cx);
5035 });
5036
5037 cx.assert_editor_state(indoc!(
5038 r#"abc
5039 defˇghi
5040
5041 jk
5042 nlmˇo
5043 "#
5044 ));
5045
5046 cx.update_editor(|editor, window, cx| {
5047 editor.add_selection_below(&Default::default(), window, cx);
5048 });
5049
5050 cx.assert_editor_state(indoc!(
5051 r#"abc
5052 defˇghi
5053
5054 jk
5055 nlmˇo
5056 "#
5057 ));
5058
5059 // change selections
5060 cx.set_state(indoc!(
5061 r#"abc
5062 def«ˇg»hi
5063
5064 jk
5065 nlmo
5066 "#
5067 ));
5068
5069 cx.update_editor(|editor, window, cx| {
5070 editor.add_selection_below(&Default::default(), window, cx);
5071 });
5072
5073 cx.assert_editor_state(indoc!(
5074 r#"abc
5075 def«ˇg»hi
5076
5077 jk
5078 nlm«ˇo»
5079 "#
5080 ));
5081
5082 cx.update_editor(|editor, window, cx| {
5083 editor.add_selection_below(&Default::default(), window, cx);
5084 });
5085
5086 cx.assert_editor_state(indoc!(
5087 r#"abc
5088 def«ˇg»hi
5089
5090 jk
5091 nlm«ˇo»
5092 "#
5093 ));
5094
5095 cx.update_editor(|editor, window, cx| {
5096 editor.add_selection_above(&Default::default(), window, cx);
5097 });
5098
5099 cx.assert_editor_state(indoc!(
5100 r#"abc
5101 def«ˇg»hi
5102
5103 jk
5104 nlmo
5105 "#
5106 ));
5107
5108 cx.update_editor(|editor, window, cx| {
5109 editor.add_selection_above(&Default::default(), window, cx);
5110 });
5111
5112 cx.assert_editor_state(indoc!(
5113 r#"abc
5114 def«ˇg»hi
5115
5116 jk
5117 nlmo
5118 "#
5119 ));
5120
5121 // Change selections again
5122 cx.set_state(indoc!(
5123 r#"a«bc
5124 defgˇ»hi
5125
5126 jk
5127 nlmo
5128 "#
5129 ));
5130
5131 cx.update_editor(|editor, window, cx| {
5132 editor.add_selection_below(&Default::default(), window, cx);
5133 });
5134
5135 cx.assert_editor_state(indoc!(
5136 r#"a«bcˇ»
5137 d«efgˇ»hi
5138
5139 j«kˇ»
5140 nlmo
5141 "#
5142 ));
5143
5144 cx.update_editor(|editor, window, cx| {
5145 editor.add_selection_below(&Default::default(), window, cx);
5146 });
5147 cx.assert_editor_state(indoc!(
5148 r#"a«bcˇ»
5149 d«efgˇ»hi
5150
5151 j«kˇ»
5152 n«lmoˇ»
5153 "#
5154 ));
5155 cx.update_editor(|editor, window, cx| {
5156 editor.add_selection_above(&Default::default(), window, cx);
5157 });
5158
5159 cx.assert_editor_state(indoc!(
5160 r#"a«bcˇ»
5161 d«efgˇ»hi
5162
5163 j«kˇ»
5164 nlmo
5165 "#
5166 ));
5167
5168 // Change selections again
5169 cx.set_state(indoc!(
5170 r#"abc
5171 d«ˇefghi
5172
5173 jk
5174 nlm»o
5175 "#
5176 ));
5177
5178 cx.update_editor(|editor, window, cx| {
5179 editor.add_selection_above(&Default::default(), window, cx);
5180 });
5181
5182 cx.assert_editor_state(indoc!(
5183 r#"a«ˇbc»
5184 d«ˇef»ghi
5185
5186 j«ˇk»
5187 n«ˇlm»o
5188 "#
5189 ));
5190
5191 cx.update_editor(|editor, window, cx| {
5192 editor.add_selection_below(&Default::default(), window, cx);
5193 });
5194
5195 cx.assert_editor_state(indoc!(
5196 r#"abc
5197 d«ˇef»ghi
5198
5199 j«ˇk»
5200 n«ˇlm»o
5201 "#
5202 ));
5203}
5204
5205#[gpui::test]
5206async fn test_select_next(cx: &mut gpui::TestAppContext) {
5207 init_test(cx, |_| {});
5208
5209 let mut cx = EditorTestContext::new(cx).await;
5210 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5211
5212 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5213 .unwrap();
5214 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5215
5216 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5217 .unwrap();
5218 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5219
5220 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5221 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5222
5223 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5224 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5225
5226 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5227 .unwrap();
5228 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5229
5230 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5231 .unwrap();
5232 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5233}
5234
5235#[gpui::test]
5236async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5237 init_test(cx, |_| {});
5238
5239 let mut cx = EditorTestContext::new(cx).await;
5240
5241 // Test caret-only selections
5242 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5243
5244 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5245 .unwrap();
5246 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5247
5248 // Test left-to-right selections
5249 cx.set_state("abc\n«abcˇ»\nabc");
5250
5251 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5252 .unwrap();
5253 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5254
5255 // Test right-to-left selections
5256 cx.set_state("abc\n«ˇabc»\nabc");
5257
5258 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5259 .unwrap();
5260 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5261}
5262
5263#[gpui::test]
5264async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5265 init_test(cx, |_| {});
5266
5267 let mut cx = EditorTestContext::new(cx).await;
5268 cx.set_state(
5269 r#"let foo = 2;
5270lˇet foo = 2;
5271let fooˇ = 2;
5272let foo = 2;
5273let foo = ˇ2;"#,
5274 );
5275
5276 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5277 .unwrap();
5278 cx.assert_editor_state(
5279 r#"let foo = 2;
5280«letˇ» foo = 2;
5281let «fooˇ» = 2;
5282let foo = 2;
5283let foo = «2ˇ»;"#,
5284 );
5285
5286 // noop for multiple selections with different contents
5287 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5288 .unwrap();
5289 cx.assert_editor_state(
5290 r#"let foo = 2;
5291«letˇ» foo = 2;
5292let «fooˇ» = 2;
5293let foo = 2;
5294let foo = «2ˇ»;"#,
5295 );
5296}
5297
5298#[gpui::test]
5299async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5300 init_test(cx, |_| {});
5301
5302 let mut cx =
5303 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5304
5305 cx.assert_editor_state(indoc! {"
5306 ˇbbb
5307 ccc
5308
5309 bbb
5310 ccc
5311 "});
5312 cx.dispatch_action(SelectPrevious::default());
5313 cx.assert_editor_state(indoc! {"
5314 «bbbˇ»
5315 ccc
5316
5317 bbb
5318 ccc
5319 "});
5320 cx.dispatch_action(SelectPrevious::default());
5321 cx.assert_editor_state(indoc! {"
5322 «bbbˇ»
5323 ccc
5324
5325 «bbbˇ»
5326 ccc
5327 "});
5328}
5329
5330#[gpui::test]
5331async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5332 init_test(cx, |_| {});
5333
5334 let mut cx = EditorTestContext::new(cx).await;
5335 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5336
5337 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5338 .unwrap();
5339 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5340
5341 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5342 .unwrap();
5343 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5344
5345 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5346 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5347
5348 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5349 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5350
5351 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5352 .unwrap();
5353 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5354
5355 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5356 .unwrap();
5357 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5358
5359 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5360 .unwrap();
5361 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5362}
5363
5364#[gpui::test]
5365async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5366 init_test(cx, |_| {});
5367
5368 let mut cx = EditorTestContext::new(cx).await;
5369 cx.set_state(
5370 r#"let foo = 2;
5371lˇet foo = 2;
5372let fooˇ = 2;
5373let foo = 2;
5374let foo = ˇ2;"#,
5375 );
5376
5377 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5378 .unwrap();
5379 cx.assert_editor_state(
5380 r#"let foo = 2;
5381«letˇ» foo = 2;
5382let «fooˇ» = 2;
5383let foo = 2;
5384let foo = «2ˇ»;"#,
5385 );
5386
5387 // noop for multiple selections with different contents
5388 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5389 .unwrap();
5390 cx.assert_editor_state(
5391 r#"let foo = 2;
5392«letˇ» foo = 2;
5393let «fooˇ» = 2;
5394let foo = 2;
5395let foo = «2ˇ»;"#,
5396 );
5397}
5398
5399#[gpui::test]
5400async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5401 init_test(cx, |_| {});
5402
5403 let mut cx = EditorTestContext::new(cx).await;
5404 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5405
5406 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5407 .unwrap();
5408 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5409
5410 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5411 .unwrap();
5412 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5413
5414 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5415 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5416
5417 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5418 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5419
5420 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5421 .unwrap();
5422 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5423
5424 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5425 .unwrap();
5426 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5427}
5428
5429#[gpui::test]
5430async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5431 init_test(cx, |_| {});
5432
5433 let language = Arc::new(Language::new(
5434 LanguageConfig::default(),
5435 Some(tree_sitter_rust::LANGUAGE.into()),
5436 ));
5437
5438 let text = r#"
5439 use mod1::mod2::{mod3, mod4};
5440
5441 fn fn_1(param1: bool, param2: &str) {
5442 let var1 = "text";
5443 }
5444 "#
5445 .unindent();
5446
5447 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5448 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5449 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5450
5451 editor
5452 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5453 .await;
5454
5455 editor.update_in(cx, |editor, window, cx| {
5456 editor.change_selections(None, window, cx, |s| {
5457 s.select_display_ranges([
5458 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5459 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5460 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5461 ]);
5462 });
5463 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5464 });
5465 editor.update(cx, |editor, cx| {
5466 assert_text_with_selections(
5467 editor,
5468 indoc! {r#"
5469 use mod1::mod2::{mod3, «mod4ˇ»};
5470
5471 fn fn_1«ˇ(param1: bool, param2: &str)» {
5472 let var1 = "«textˇ»";
5473 }
5474 "#},
5475 cx,
5476 );
5477 });
5478
5479 editor.update_in(cx, |editor, window, cx| {
5480 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5481 });
5482 editor.update(cx, |editor, cx| {
5483 assert_text_with_selections(
5484 editor,
5485 indoc! {r#"
5486 use mod1::mod2::«{mod3, mod4}ˇ»;
5487
5488 «ˇfn fn_1(param1: bool, param2: &str) {
5489 let var1 = "text";
5490 }»
5491 "#},
5492 cx,
5493 );
5494 });
5495
5496 editor.update_in(cx, |editor, window, cx| {
5497 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5498 });
5499 assert_eq!(
5500 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5501 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5502 );
5503
5504 // Trying to expand the selected syntax node one more time has no effect.
5505 editor.update_in(cx, |editor, window, cx| {
5506 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5507 });
5508 assert_eq!(
5509 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5510 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5511 );
5512
5513 editor.update_in(cx, |editor, window, cx| {
5514 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5515 });
5516 editor.update(cx, |editor, cx| {
5517 assert_text_with_selections(
5518 editor,
5519 indoc! {r#"
5520 use mod1::mod2::«{mod3, mod4}ˇ»;
5521
5522 «ˇfn fn_1(param1: bool, param2: &str) {
5523 let var1 = "text";
5524 }»
5525 "#},
5526 cx,
5527 );
5528 });
5529
5530 editor.update_in(cx, |editor, window, cx| {
5531 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5532 });
5533 editor.update(cx, |editor, cx| {
5534 assert_text_with_selections(
5535 editor,
5536 indoc! {r#"
5537 use mod1::mod2::{mod3, «mod4ˇ»};
5538
5539 fn fn_1«ˇ(param1: bool, param2: &str)» {
5540 let var1 = "«textˇ»";
5541 }
5542 "#},
5543 cx,
5544 );
5545 });
5546
5547 editor.update_in(cx, |editor, window, cx| {
5548 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5549 });
5550 editor.update(cx, |editor, cx| {
5551 assert_text_with_selections(
5552 editor,
5553 indoc! {r#"
5554 use mod1::mod2::{mod3, mo«ˇ»d4};
5555
5556 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5557 let var1 = "te«ˇ»xt";
5558 }
5559 "#},
5560 cx,
5561 );
5562 });
5563
5564 // Trying to shrink the selected syntax node one more time has no effect.
5565 editor.update_in(cx, |editor, window, cx| {
5566 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5567 });
5568 editor.update_in(cx, |editor, _, cx| {
5569 assert_text_with_selections(
5570 editor,
5571 indoc! {r#"
5572 use mod1::mod2::{mod3, mo«ˇ»d4};
5573
5574 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5575 let var1 = "te«ˇ»xt";
5576 }
5577 "#},
5578 cx,
5579 );
5580 });
5581
5582 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5583 // a fold.
5584 editor.update_in(cx, |editor, window, cx| {
5585 editor.fold_creases(
5586 vec![
5587 Crease::simple(
5588 Point::new(0, 21)..Point::new(0, 24),
5589 FoldPlaceholder::test(),
5590 ),
5591 Crease::simple(
5592 Point::new(3, 20)..Point::new(3, 22),
5593 FoldPlaceholder::test(),
5594 ),
5595 ],
5596 true,
5597 window,
5598 cx,
5599 );
5600 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5601 });
5602 editor.update(cx, |editor, cx| {
5603 assert_text_with_selections(
5604 editor,
5605 indoc! {r#"
5606 use mod1::mod2::«{mod3, mod4}ˇ»;
5607
5608 fn fn_1«ˇ(param1: bool, param2: &str)» {
5609 «let var1 = "text";ˇ»
5610 }
5611 "#},
5612 cx,
5613 );
5614 });
5615}
5616
5617#[gpui::test]
5618async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
5619 init_test(cx, |_| {});
5620
5621 let base_text = r#"
5622 impl A {
5623 // this is an uncommitted comment
5624
5625 fn b() {
5626 c();
5627 }
5628
5629 // this is another uncommitted comment
5630
5631 fn d() {
5632 // e
5633 // f
5634 }
5635 }
5636
5637 fn g() {
5638 // h
5639 }
5640 "#
5641 .unindent();
5642
5643 let text = r#"
5644 ˇimpl A {
5645
5646 fn b() {
5647 c();
5648 }
5649
5650 fn d() {
5651 // e
5652 // f
5653 }
5654 }
5655
5656 fn g() {
5657 // h
5658 }
5659 "#
5660 .unindent();
5661
5662 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5663 cx.set_state(&text);
5664 cx.set_diff_base(&base_text);
5665 cx.update_editor(|editor, window, cx| {
5666 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5667 });
5668
5669 cx.assert_state_with_diff(
5670 "
5671 ˇimpl A {
5672 - // this is an uncommitted comment
5673
5674 fn b() {
5675 c();
5676 }
5677
5678 - // this is another uncommitted comment
5679 -
5680 fn d() {
5681 // e
5682 // f
5683 }
5684 }
5685
5686 fn g() {
5687 // h
5688 }
5689 "
5690 .unindent(),
5691 );
5692
5693 let expected_display_text = "
5694 impl A {
5695 // this is an uncommitted comment
5696
5697 fn b() {
5698 ⋯
5699 }
5700
5701 // this is another uncommitted comment
5702
5703 fn d() {
5704 ⋯
5705 }
5706 }
5707
5708 fn g() {
5709 ⋯
5710 }
5711 "
5712 .unindent();
5713
5714 cx.update_editor(|editor, window, cx| {
5715 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5716 assert_eq!(editor.display_text(cx), expected_display_text);
5717 });
5718}
5719
5720#[gpui::test]
5721async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5722 init_test(cx, |_| {});
5723
5724 let language = Arc::new(
5725 Language::new(
5726 LanguageConfig {
5727 brackets: BracketPairConfig {
5728 pairs: vec![
5729 BracketPair {
5730 start: "{".to_string(),
5731 end: "}".to_string(),
5732 close: false,
5733 surround: false,
5734 newline: true,
5735 },
5736 BracketPair {
5737 start: "(".to_string(),
5738 end: ")".to_string(),
5739 close: false,
5740 surround: false,
5741 newline: true,
5742 },
5743 ],
5744 ..Default::default()
5745 },
5746 ..Default::default()
5747 },
5748 Some(tree_sitter_rust::LANGUAGE.into()),
5749 )
5750 .with_indents_query(
5751 r#"
5752 (_ "(" ")" @end) @indent
5753 (_ "{" "}" @end) @indent
5754 "#,
5755 )
5756 .unwrap(),
5757 );
5758
5759 let text = "fn a() {}";
5760
5761 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5762 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5763 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5764 editor
5765 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5766 .await;
5767
5768 editor.update_in(cx, |editor, window, cx| {
5769 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5770 editor.newline(&Newline, window, cx);
5771 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5772 assert_eq!(
5773 editor.selections.ranges(cx),
5774 &[
5775 Point::new(1, 4)..Point::new(1, 4),
5776 Point::new(3, 4)..Point::new(3, 4),
5777 Point::new(5, 0)..Point::new(5, 0)
5778 ]
5779 );
5780 });
5781}
5782
5783#[gpui::test]
5784async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5785 init_test(cx, |_| {});
5786
5787 {
5788 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5789 cx.set_state(indoc! {"
5790 impl A {
5791
5792 fn b() {}
5793
5794 «fn c() {
5795
5796 }ˇ»
5797 }
5798 "});
5799
5800 cx.update_editor(|editor, window, cx| {
5801 editor.autoindent(&Default::default(), window, cx);
5802 });
5803
5804 cx.assert_editor_state(indoc! {"
5805 impl A {
5806
5807 fn b() {}
5808
5809 «fn c() {
5810
5811 }ˇ»
5812 }
5813 "});
5814 }
5815
5816 {
5817 let mut cx = EditorTestContext::new_multibuffer(
5818 cx,
5819 [indoc! { "
5820 impl A {
5821 «
5822 // a
5823 fn b(){}
5824 »
5825 «
5826 }
5827 fn c(){}
5828 »
5829 "}],
5830 );
5831
5832 let buffer = cx.update_editor(|editor, _, cx| {
5833 let buffer = editor.buffer().update(cx, |buffer, _| {
5834 buffer.all_buffers().iter().next().unwrap().clone()
5835 });
5836 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5837 buffer
5838 });
5839
5840 cx.run_until_parked();
5841 cx.update_editor(|editor, window, cx| {
5842 editor.select_all(&Default::default(), window, cx);
5843 editor.autoindent(&Default::default(), window, cx)
5844 });
5845 cx.run_until_parked();
5846
5847 cx.update(|_, cx| {
5848 pretty_assertions::assert_eq!(
5849 buffer.read(cx).text(),
5850 indoc! { "
5851 impl A {
5852
5853 // a
5854 fn b(){}
5855
5856
5857 }
5858 fn c(){}
5859
5860 " }
5861 )
5862 });
5863 }
5864}
5865
5866#[gpui::test]
5867async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5868 init_test(cx, |_| {});
5869
5870 let mut cx = EditorTestContext::new(cx).await;
5871
5872 let language = Arc::new(Language::new(
5873 LanguageConfig {
5874 brackets: BracketPairConfig {
5875 pairs: vec![
5876 BracketPair {
5877 start: "{".to_string(),
5878 end: "}".to_string(),
5879 close: true,
5880 surround: true,
5881 newline: true,
5882 },
5883 BracketPair {
5884 start: "(".to_string(),
5885 end: ")".to_string(),
5886 close: true,
5887 surround: true,
5888 newline: true,
5889 },
5890 BracketPair {
5891 start: "/*".to_string(),
5892 end: " */".to_string(),
5893 close: true,
5894 surround: true,
5895 newline: true,
5896 },
5897 BracketPair {
5898 start: "[".to_string(),
5899 end: "]".to_string(),
5900 close: false,
5901 surround: false,
5902 newline: true,
5903 },
5904 BracketPair {
5905 start: "\"".to_string(),
5906 end: "\"".to_string(),
5907 close: true,
5908 surround: true,
5909 newline: false,
5910 },
5911 BracketPair {
5912 start: "<".to_string(),
5913 end: ">".to_string(),
5914 close: false,
5915 surround: true,
5916 newline: true,
5917 },
5918 ],
5919 ..Default::default()
5920 },
5921 autoclose_before: "})]".to_string(),
5922 ..Default::default()
5923 },
5924 Some(tree_sitter_rust::LANGUAGE.into()),
5925 ));
5926
5927 cx.language_registry().add(language.clone());
5928 cx.update_buffer(|buffer, cx| {
5929 buffer.set_language(Some(language), cx);
5930 });
5931
5932 cx.set_state(
5933 &r#"
5934 🏀ˇ
5935 εˇ
5936 ❤️ˇ
5937 "#
5938 .unindent(),
5939 );
5940
5941 // autoclose multiple nested brackets at multiple cursors
5942 cx.update_editor(|editor, window, cx| {
5943 editor.handle_input("{", window, cx);
5944 editor.handle_input("{", window, cx);
5945 editor.handle_input("{", window, cx);
5946 });
5947 cx.assert_editor_state(
5948 &"
5949 🏀{{{ˇ}}}
5950 ε{{{ˇ}}}
5951 ❤️{{{ˇ}}}
5952 "
5953 .unindent(),
5954 );
5955
5956 // insert a different closing bracket
5957 cx.update_editor(|editor, window, cx| {
5958 editor.handle_input(")", window, cx);
5959 });
5960 cx.assert_editor_state(
5961 &"
5962 🏀{{{)ˇ}}}
5963 ε{{{)ˇ}}}
5964 ❤️{{{)ˇ}}}
5965 "
5966 .unindent(),
5967 );
5968
5969 // skip over the auto-closed brackets when typing a closing bracket
5970 cx.update_editor(|editor, window, cx| {
5971 editor.move_right(&MoveRight, window, cx);
5972 editor.handle_input("}", window, cx);
5973 editor.handle_input("}", window, cx);
5974 editor.handle_input("}", window, cx);
5975 });
5976 cx.assert_editor_state(
5977 &"
5978 🏀{{{)}}}}ˇ
5979 ε{{{)}}}}ˇ
5980 ❤️{{{)}}}}ˇ
5981 "
5982 .unindent(),
5983 );
5984
5985 // autoclose multi-character pairs
5986 cx.set_state(
5987 &"
5988 ˇ
5989 ˇ
5990 "
5991 .unindent(),
5992 );
5993 cx.update_editor(|editor, window, cx| {
5994 editor.handle_input("/", window, cx);
5995 editor.handle_input("*", window, cx);
5996 });
5997 cx.assert_editor_state(
5998 &"
5999 /*ˇ */
6000 /*ˇ */
6001 "
6002 .unindent(),
6003 );
6004
6005 // one cursor autocloses a multi-character pair, one cursor
6006 // does not autoclose.
6007 cx.set_state(
6008 &"
6009 /ˇ
6010 ˇ
6011 "
6012 .unindent(),
6013 );
6014 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6015 cx.assert_editor_state(
6016 &"
6017 /*ˇ */
6018 *ˇ
6019 "
6020 .unindent(),
6021 );
6022
6023 // Don't autoclose if the next character isn't whitespace and isn't
6024 // listed in the language's "autoclose_before" section.
6025 cx.set_state("ˇa b");
6026 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6027 cx.assert_editor_state("{ˇa b");
6028
6029 // Don't autoclose if `close` is false for the bracket pair
6030 cx.set_state("ˇ");
6031 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6032 cx.assert_editor_state("[ˇ");
6033
6034 // Surround with brackets if text is selected
6035 cx.set_state("«aˇ» b");
6036 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6037 cx.assert_editor_state("{«aˇ»} b");
6038
6039 // Autclose pair where the start and end characters are the same
6040 cx.set_state("aˇ");
6041 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6042 cx.assert_editor_state("a\"ˇ\"");
6043 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6044 cx.assert_editor_state("a\"\"ˇ");
6045
6046 // Don't autoclose pair if autoclose is disabled
6047 cx.set_state("ˇ");
6048 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6049 cx.assert_editor_state("<ˇ");
6050
6051 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6052 cx.set_state("«aˇ» b");
6053 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6054 cx.assert_editor_state("<«aˇ»> b");
6055}
6056
6057#[gpui::test]
6058async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
6059 init_test(cx, |settings| {
6060 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6061 });
6062
6063 let mut cx = EditorTestContext::new(cx).await;
6064
6065 let language = Arc::new(Language::new(
6066 LanguageConfig {
6067 brackets: BracketPairConfig {
6068 pairs: vec![
6069 BracketPair {
6070 start: "{".to_string(),
6071 end: "}".to_string(),
6072 close: true,
6073 surround: true,
6074 newline: true,
6075 },
6076 BracketPair {
6077 start: "(".to_string(),
6078 end: ")".to_string(),
6079 close: true,
6080 surround: true,
6081 newline: true,
6082 },
6083 BracketPair {
6084 start: "[".to_string(),
6085 end: "]".to_string(),
6086 close: false,
6087 surround: false,
6088 newline: true,
6089 },
6090 ],
6091 ..Default::default()
6092 },
6093 autoclose_before: "})]".to_string(),
6094 ..Default::default()
6095 },
6096 Some(tree_sitter_rust::LANGUAGE.into()),
6097 ));
6098
6099 cx.language_registry().add(language.clone());
6100 cx.update_buffer(|buffer, cx| {
6101 buffer.set_language(Some(language), cx);
6102 });
6103
6104 cx.set_state(
6105 &"
6106 ˇ
6107 ˇ
6108 ˇ
6109 "
6110 .unindent(),
6111 );
6112
6113 // ensure only matching closing brackets are skipped over
6114 cx.update_editor(|editor, window, cx| {
6115 editor.handle_input("}", window, cx);
6116 editor.move_left(&MoveLeft, window, cx);
6117 editor.handle_input(")", window, cx);
6118 editor.move_left(&MoveLeft, window, cx);
6119 });
6120 cx.assert_editor_state(
6121 &"
6122 ˇ)}
6123 ˇ)}
6124 ˇ)}
6125 "
6126 .unindent(),
6127 );
6128
6129 // skip-over closing brackets at multiple cursors
6130 cx.update_editor(|editor, window, cx| {
6131 editor.handle_input(")", window, cx);
6132 editor.handle_input("}", window, cx);
6133 });
6134 cx.assert_editor_state(
6135 &"
6136 )}ˇ
6137 )}ˇ
6138 )}ˇ
6139 "
6140 .unindent(),
6141 );
6142
6143 // ignore non-close brackets
6144 cx.update_editor(|editor, window, cx| {
6145 editor.handle_input("]", window, cx);
6146 editor.move_left(&MoveLeft, window, cx);
6147 editor.handle_input("]", window, cx);
6148 });
6149 cx.assert_editor_state(
6150 &"
6151 )}]ˇ]
6152 )}]ˇ]
6153 )}]ˇ]
6154 "
6155 .unindent(),
6156 );
6157}
6158
6159#[gpui::test]
6160async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
6161 init_test(cx, |_| {});
6162
6163 let mut cx = EditorTestContext::new(cx).await;
6164
6165 let html_language = Arc::new(
6166 Language::new(
6167 LanguageConfig {
6168 name: "HTML".into(),
6169 brackets: BracketPairConfig {
6170 pairs: vec![
6171 BracketPair {
6172 start: "<".into(),
6173 end: ">".into(),
6174 close: true,
6175 ..Default::default()
6176 },
6177 BracketPair {
6178 start: "{".into(),
6179 end: "}".into(),
6180 close: true,
6181 ..Default::default()
6182 },
6183 BracketPair {
6184 start: "(".into(),
6185 end: ")".into(),
6186 close: true,
6187 ..Default::default()
6188 },
6189 ],
6190 ..Default::default()
6191 },
6192 autoclose_before: "})]>".into(),
6193 ..Default::default()
6194 },
6195 Some(tree_sitter_html::language()),
6196 )
6197 .with_injection_query(
6198 r#"
6199 (script_element
6200 (raw_text) @injection.content
6201 (#set! injection.language "javascript"))
6202 "#,
6203 )
6204 .unwrap(),
6205 );
6206
6207 let javascript_language = Arc::new(Language::new(
6208 LanguageConfig {
6209 name: "JavaScript".into(),
6210 brackets: BracketPairConfig {
6211 pairs: vec![
6212 BracketPair {
6213 start: "/*".into(),
6214 end: " */".into(),
6215 close: true,
6216 ..Default::default()
6217 },
6218 BracketPair {
6219 start: "{".into(),
6220 end: "}".into(),
6221 close: true,
6222 ..Default::default()
6223 },
6224 BracketPair {
6225 start: "(".into(),
6226 end: ")".into(),
6227 close: true,
6228 ..Default::default()
6229 },
6230 ],
6231 ..Default::default()
6232 },
6233 autoclose_before: "})]>".into(),
6234 ..Default::default()
6235 },
6236 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6237 ));
6238
6239 cx.language_registry().add(html_language.clone());
6240 cx.language_registry().add(javascript_language.clone());
6241
6242 cx.update_buffer(|buffer, cx| {
6243 buffer.set_language(Some(html_language), cx);
6244 });
6245
6246 cx.set_state(
6247 &r#"
6248 <body>ˇ
6249 <script>
6250 var x = 1;ˇ
6251 </script>
6252 </body>ˇ
6253 "#
6254 .unindent(),
6255 );
6256
6257 // Precondition: different languages are active at different locations.
6258 cx.update_editor(|editor, window, cx| {
6259 let snapshot = editor.snapshot(window, cx);
6260 let cursors = editor.selections.ranges::<usize>(cx);
6261 let languages = cursors
6262 .iter()
6263 .map(|c| snapshot.language_at(c.start).unwrap().name())
6264 .collect::<Vec<_>>();
6265 assert_eq!(
6266 languages,
6267 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6268 );
6269 });
6270
6271 // Angle brackets autoclose in HTML, but not JavaScript.
6272 cx.update_editor(|editor, window, cx| {
6273 editor.handle_input("<", window, cx);
6274 editor.handle_input("a", window, cx);
6275 });
6276 cx.assert_editor_state(
6277 &r#"
6278 <body><aˇ>
6279 <script>
6280 var x = 1;<aˇ
6281 </script>
6282 </body><aˇ>
6283 "#
6284 .unindent(),
6285 );
6286
6287 // Curly braces and parens autoclose in both HTML and JavaScript.
6288 cx.update_editor(|editor, window, cx| {
6289 editor.handle_input(" b=", window, cx);
6290 editor.handle_input("{", window, cx);
6291 editor.handle_input("c", window, cx);
6292 editor.handle_input("(", window, cx);
6293 });
6294 cx.assert_editor_state(
6295 &r#"
6296 <body><a b={c(ˇ)}>
6297 <script>
6298 var x = 1;<a b={c(ˇ)}
6299 </script>
6300 </body><a b={c(ˇ)}>
6301 "#
6302 .unindent(),
6303 );
6304
6305 // Brackets that were already autoclosed are skipped.
6306 cx.update_editor(|editor, window, cx| {
6307 editor.handle_input(")", window, cx);
6308 editor.handle_input("d", window, cx);
6309 editor.handle_input("}", window, cx);
6310 });
6311 cx.assert_editor_state(
6312 &r#"
6313 <body><a b={c()d}ˇ>
6314 <script>
6315 var x = 1;<a b={c()d}ˇ
6316 </script>
6317 </body><a b={c()d}ˇ>
6318 "#
6319 .unindent(),
6320 );
6321 cx.update_editor(|editor, window, cx| {
6322 editor.handle_input(">", window, cx);
6323 });
6324 cx.assert_editor_state(
6325 &r#"
6326 <body><a b={c()d}>ˇ
6327 <script>
6328 var x = 1;<a b={c()d}>ˇ
6329 </script>
6330 </body><a b={c()d}>ˇ
6331 "#
6332 .unindent(),
6333 );
6334
6335 // Reset
6336 cx.set_state(
6337 &r#"
6338 <body>ˇ
6339 <script>
6340 var x = 1;ˇ
6341 </script>
6342 </body>ˇ
6343 "#
6344 .unindent(),
6345 );
6346
6347 cx.update_editor(|editor, window, cx| {
6348 editor.handle_input("<", window, cx);
6349 });
6350 cx.assert_editor_state(
6351 &r#"
6352 <body><ˇ>
6353 <script>
6354 var x = 1;<ˇ
6355 </script>
6356 </body><ˇ>
6357 "#
6358 .unindent(),
6359 );
6360
6361 // When backspacing, the closing angle brackets are removed.
6362 cx.update_editor(|editor, window, cx| {
6363 editor.backspace(&Backspace, window, cx);
6364 });
6365 cx.assert_editor_state(
6366 &r#"
6367 <body>ˇ
6368 <script>
6369 var x = 1;ˇ
6370 </script>
6371 </body>ˇ
6372 "#
6373 .unindent(),
6374 );
6375
6376 // Block comments autoclose in JavaScript, but not HTML.
6377 cx.update_editor(|editor, window, cx| {
6378 editor.handle_input("/", window, cx);
6379 editor.handle_input("*", window, cx);
6380 });
6381 cx.assert_editor_state(
6382 &r#"
6383 <body>/*ˇ
6384 <script>
6385 var x = 1;/*ˇ */
6386 </script>
6387 </body>/*ˇ
6388 "#
6389 .unindent(),
6390 );
6391}
6392
6393#[gpui::test]
6394async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6395 init_test(cx, |_| {});
6396
6397 let mut cx = EditorTestContext::new(cx).await;
6398
6399 let rust_language = Arc::new(
6400 Language::new(
6401 LanguageConfig {
6402 name: "Rust".into(),
6403 brackets: serde_json::from_value(json!([
6404 { "start": "{", "end": "}", "close": true, "newline": true },
6405 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6406 ]))
6407 .unwrap(),
6408 autoclose_before: "})]>".into(),
6409 ..Default::default()
6410 },
6411 Some(tree_sitter_rust::LANGUAGE.into()),
6412 )
6413 .with_override_query("(string_literal) @string")
6414 .unwrap(),
6415 );
6416
6417 cx.language_registry().add(rust_language.clone());
6418 cx.update_buffer(|buffer, cx| {
6419 buffer.set_language(Some(rust_language), cx);
6420 });
6421
6422 cx.set_state(
6423 &r#"
6424 let x = ˇ
6425 "#
6426 .unindent(),
6427 );
6428
6429 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6430 cx.update_editor(|editor, window, cx| {
6431 editor.handle_input("\"", window, cx);
6432 });
6433 cx.assert_editor_state(
6434 &r#"
6435 let x = "ˇ"
6436 "#
6437 .unindent(),
6438 );
6439
6440 // Inserting another quotation mark. The cursor moves across the existing
6441 // automatically-inserted quotation mark.
6442 cx.update_editor(|editor, window, cx| {
6443 editor.handle_input("\"", window, cx);
6444 });
6445 cx.assert_editor_state(
6446 &r#"
6447 let x = ""ˇ
6448 "#
6449 .unindent(),
6450 );
6451
6452 // Reset
6453 cx.set_state(
6454 &r#"
6455 let x = ˇ
6456 "#
6457 .unindent(),
6458 );
6459
6460 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6461 cx.update_editor(|editor, window, cx| {
6462 editor.handle_input("\"", window, cx);
6463 editor.handle_input(" ", window, cx);
6464 editor.move_left(&Default::default(), window, cx);
6465 editor.handle_input("\\", window, cx);
6466 editor.handle_input("\"", window, cx);
6467 });
6468 cx.assert_editor_state(
6469 &r#"
6470 let x = "\"ˇ "
6471 "#
6472 .unindent(),
6473 );
6474
6475 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6476 // mark. Nothing is inserted.
6477 cx.update_editor(|editor, window, cx| {
6478 editor.move_right(&Default::default(), window, cx);
6479 editor.handle_input("\"", window, cx);
6480 });
6481 cx.assert_editor_state(
6482 &r#"
6483 let x = "\" "ˇ
6484 "#
6485 .unindent(),
6486 );
6487}
6488
6489#[gpui::test]
6490async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6491 init_test(cx, |_| {});
6492
6493 let language = Arc::new(Language::new(
6494 LanguageConfig {
6495 brackets: BracketPairConfig {
6496 pairs: vec![
6497 BracketPair {
6498 start: "{".to_string(),
6499 end: "}".to_string(),
6500 close: true,
6501 surround: true,
6502 newline: true,
6503 },
6504 BracketPair {
6505 start: "/* ".to_string(),
6506 end: "*/".to_string(),
6507 close: true,
6508 surround: true,
6509 ..Default::default()
6510 },
6511 ],
6512 ..Default::default()
6513 },
6514 ..Default::default()
6515 },
6516 Some(tree_sitter_rust::LANGUAGE.into()),
6517 ));
6518
6519 let text = r#"
6520 a
6521 b
6522 c
6523 "#
6524 .unindent();
6525
6526 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6527 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6528 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6529 editor
6530 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6531 .await;
6532
6533 editor.update_in(cx, |editor, window, cx| {
6534 editor.change_selections(None, window, cx, |s| {
6535 s.select_display_ranges([
6536 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6537 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6538 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6539 ])
6540 });
6541
6542 editor.handle_input("{", window, cx);
6543 editor.handle_input("{", window, cx);
6544 editor.handle_input("{", window, cx);
6545 assert_eq!(
6546 editor.text(cx),
6547 "
6548 {{{a}}}
6549 {{{b}}}
6550 {{{c}}}
6551 "
6552 .unindent()
6553 );
6554 assert_eq!(
6555 editor.selections.display_ranges(cx),
6556 [
6557 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6558 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6559 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6560 ]
6561 );
6562
6563 editor.undo(&Undo, window, cx);
6564 editor.undo(&Undo, window, cx);
6565 editor.undo(&Undo, window, cx);
6566 assert_eq!(
6567 editor.text(cx),
6568 "
6569 a
6570 b
6571 c
6572 "
6573 .unindent()
6574 );
6575 assert_eq!(
6576 editor.selections.display_ranges(cx),
6577 [
6578 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6579 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6580 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6581 ]
6582 );
6583
6584 // Ensure inserting the first character of a multi-byte bracket pair
6585 // doesn't surround the selections with the bracket.
6586 editor.handle_input("/", window, cx);
6587 assert_eq!(
6588 editor.text(cx),
6589 "
6590 /
6591 /
6592 /
6593 "
6594 .unindent()
6595 );
6596 assert_eq!(
6597 editor.selections.display_ranges(cx),
6598 [
6599 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6600 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6601 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6602 ]
6603 );
6604
6605 editor.undo(&Undo, window, cx);
6606 assert_eq!(
6607 editor.text(cx),
6608 "
6609 a
6610 b
6611 c
6612 "
6613 .unindent()
6614 );
6615 assert_eq!(
6616 editor.selections.display_ranges(cx),
6617 [
6618 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6619 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6620 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6621 ]
6622 );
6623
6624 // Ensure inserting the last character of a multi-byte bracket pair
6625 // doesn't surround the selections with the bracket.
6626 editor.handle_input("*", window, cx);
6627 assert_eq!(
6628 editor.text(cx),
6629 "
6630 *
6631 *
6632 *
6633 "
6634 .unindent()
6635 );
6636 assert_eq!(
6637 editor.selections.display_ranges(cx),
6638 [
6639 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6640 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6641 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6642 ]
6643 );
6644 });
6645}
6646
6647#[gpui::test]
6648async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6649 init_test(cx, |_| {});
6650
6651 let language = Arc::new(Language::new(
6652 LanguageConfig {
6653 brackets: BracketPairConfig {
6654 pairs: vec![BracketPair {
6655 start: "{".to_string(),
6656 end: "}".to_string(),
6657 close: true,
6658 surround: true,
6659 newline: true,
6660 }],
6661 ..Default::default()
6662 },
6663 autoclose_before: "}".to_string(),
6664 ..Default::default()
6665 },
6666 Some(tree_sitter_rust::LANGUAGE.into()),
6667 ));
6668
6669 let text = r#"
6670 a
6671 b
6672 c
6673 "#
6674 .unindent();
6675
6676 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6678 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6679 editor
6680 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6681 .await;
6682
6683 editor.update_in(cx, |editor, window, cx| {
6684 editor.change_selections(None, window, cx, |s| {
6685 s.select_ranges([
6686 Point::new(0, 1)..Point::new(0, 1),
6687 Point::new(1, 1)..Point::new(1, 1),
6688 Point::new(2, 1)..Point::new(2, 1),
6689 ])
6690 });
6691
6692 editor.handle_input("{", window, cx);
6693 editor.handle_input("{", window, cx);
6694 editor.handle_input("_", window, cx);
6695 assert_eq!(
6696 editor.text(cx),
6697 "
6698 a{{_}}
6699 b{{_}}
6700 c{{_}}
6701 "
6702 .unindent()
6703 );
6704 assert_eq!(
6705 editor.selections.ranges::<Point>(cx),
6706 [
6707 Point::new(0, 4)..Point::new(0, 4),
6708 Point::new(1, 4)..Point::new(1, 4),
6709 Point::new(2, 4)..Point::new(2, 4)
6710 ]
6711 );
6712
6713 editor.backspace(&Default::default(), window, cx);
6714 editor.backspace(&Default::default(), window, cx);
6715 assert_eq!(
6716 editor.text(cx),
6717 "
6718 a{}
6719 b{}
6720 c{}
6721 "
6722 .unindent()
6723 );
6724 assert_eq!(
6725 editor.selections.ranges::<Point>(cx),
6726 [
6727 Point::new(0, 2)..Point::new(0, 2),
6728 Point::new(1, 2)..Point::new(1, 2),
6729 Point::new(2, 2)..Point::new(2, 2)
6730 ]
6731 );
6732
6733 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6734 assert_eq!(
6735 editor.text(cx),
6736 "
6737 a
6738 b
6739 c
6740 "
6741 .unindent()
6742 );
6743 assert_eq!(
6744 editor.selections.ranges::<Point>(cx),
6745 [
6746 Point::new(0, 1)..Point::new(0, 1),
6747 Point::new(1, 1)..Point::new(1, 1),
6748 Point::new(2, 1)..Point::new(2, 1)
6749 ]
6750 );
6751 });
6752}
6753
6754#[gpui::test]
6755async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6756 init_test(cx, |settings| {
6757 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6758 });
6759
6760 let mut cx = EditorTestContext::new(cx).await;
6761
6762 let language = Arc::new(Language::new(
6763 LanguageConfig {
6764 brackets: BracketPairConfig {
6765 pairs: vec![
6766 BracketPair {
6767 start: "{".to_string(),
6768 end: "}".to_string(),
6769 close: true,
6770 surround: true,
6771 newline: true,
6772 },
6773 BracketPair {
6774 start: "(".to_string(),
6775 end: ")".to_string(),
6776 close: true,
6777 surround: true,
6778 newline: true,
6779 },
6780 BracketPair {
6781 start: "[".to_string(),
6782 end: "]".to_string(),
6783 close: false,
6784 surround: true,
6785 newline: true,
6786 },
6787 ],
6788 ..Default::default()
6789 },
6790 autoclose_before: "})]".to_string(),
6791 ..Default::default()
6792 },
6793 Some(tree_sitter_rust::LANGUAGE.into()),
6794 ));
6795
6796 cx.language_registry().add(language.clone());
6797 cx.update_buffer(|buffer, cx| {
6798 buffer.set_language(Some(language), cx);
6799 });
6800
6801 cx.set_state(
6802 &"
6803 {(ˇ)}
6804 [[ˇ]]
6805 {(ˇ)}
6806 "
6807 .unindent(),
6808 );
6809
6810 cx.update_editor(|editor, window, cx| {
6811 editor.backspace(&Default::default(), window, cx);
6812 editor.backspace(&Default::default(), window, cx);
6813 });
6814
6815 cx.assert_editor_state(
6816 &"
6817 ˇ
6818 ˇ]]
6819 ˇ
6820 "
6821 .unindent(),
6822 );
6823
6824 cx.update_editor(|editor, window, cx| {
6825 editor.handle_input("{", window, cx);
6826 editor.handle_input("{", window, cx);
6827 editor.move_right(&MoveRight, window, cx);
6828 editor.move_right(&MoveRight, window, cx);
6829 editor.move_left(&MoveLeft, window, cx);
6830 editor.move_left(&MoveLeft, window, cx);
6831 editor.backspace(&Default::default(), window, cx);
6832 });
6833
6834 cx.assert_editor_state(
6835 &"
6836 {ˇ}
6837 {ˇ}]]
6838 {ˇ}
6839 "
6840 .unindent(),
6841 );
6842
6843 cx.update_editor(|editor, window, cx| {
6844 editor.backspace(&Default::default(), window, cx);
6845 });
6846
6847 cx.assert_editor_state(
6848 &"
6849 ˇ
6850 ˇ]]
6851 ˇ
6852 "
6853 .unindent(),
6854 );
6855}
6856
6857#[gpui::test]
6858async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6859 init_test(cx, |_| {});
6860
6861 let language = Arc::new(Language::new(
6862 LanguageConfig::default(),
6863 Some(tree_sitter_rust::LANGUAGE.into()),
6864 ));
6865
6866 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
6867 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6868 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6869 editor
6870 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6871 .await;
6872
6873 editor.update_in(cx, |editor, window, cx| {
6874 editor.set_auto_replace_emoji_shortcode(true);
6875
6876 editor.handle_input("Hello ", window, cx);
6877 editor.handle_input(":wave", window, cx);
6878 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6879
6880 editor.handle_input(":", window, cx);
6881 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6882
6883 editor.handle_input(" :smile", window, cx);
6884 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6885
6886 editor.handle_input(":", window, cx);
6887 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6888
6889 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6890 editor.handle_input(":wave", window, cx);
6891 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6892
6893 editor.handle_input(":", window, cx);
6894 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6895
6896 editor.handle_input(":1", window, cx);
6897 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6898
6899 editor.handle_input(":", window, cx);
6900 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6901
6902 // Ensure shortcode does not get replaced when it is part of a word
6903 editor.handle_input(" Test:wave", window, cx);
6904 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6905
6906 editor.handle_input(":", window, cx);
6907 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6908
6909 editor.set_auto_replace_emoji_shortcode(false);
6910
6911 // Ensure shortcode does not get replaced when auto replace is off
6912 editor.handle_input(" :wave", window, cx);
6913 assert_eq!(
6914 editor.text(cx),
6915 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6916 );
6917
6918 editor.handle_input(":", window, cx);
6919 assert_eq!(
6920 editor.text(cx),
6921 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6922 );
6923 });
6924}
6925
6926#[gpui::test]
6927async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6928 init_test(cx, |_| {});
6929
6930 let (text, insertion_ranges) = marked_text_ranges(
6931 indoc! {"
6932 ˇ
6933 "},
6934 false,
6935 );
6936
6937 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6938 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6939
6940 _ = editor.update_in(cx, |editor, window, cx| {
6941 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6942
6943 editor
6944 .insert_snippet(&insertion_ranges, snippet, window, cx)
6945 .unwrap();
6946
6947 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
6948 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6949 assert_eq!(editor.text(cx), expected_text);
6950 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6951 }
6952
6953 assert(
6954 editor,
6955 cx,
6956 indoc! {"
6957 type «» =•
6958 "},
6959 );
6960
6961 assert!(editor.context_menu_visible(), "There should be a matches");
6962 });
6963}
6964
6965#[gpui::test]
6966async fn test_snippets(cx: &mut gpui::TestAppContext) {
6967 init_test(cx, |_| {});
6968
6969 let (text, insertion_ranges) = marked_text_ranges(
6970 indoc! {"
6971 a.ˇ b
6972 a.ˇ b
6973 a.ˇ b
6974 "},
6975 false,
6976 );
6977
6978 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6979 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6980
6981 editor.update_in(cx, |editor, window, cx| {
6982 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6983
6984 editor
6985 .insert_snippet(&insertion_ranges, snippet, window, cx)
6986 .unwrap();
6987
6988 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
6989 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6990 assert_eq!(editor.text(cx), expected_text);
6991 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6992 }
6993
6994 assert(
6995 editor,
6996 cx,
6997 indoc! {"
6998 a.f(«one», two, «three») b
6999 a.f(«one», two, «three») b
7000 a.f(«one», two, «three») b
7001 "},
7002 );
7003
7004 // Can't move earlier than the first tab stop
7005 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7006 assert(
7007 editor,
7008 cx,
7009 indoc! {"
7010 a.f(«one», two, «three») b
7011 a.f(«one», two, «three») b
7012 a.f(«one», two, «three») b
7013 "},
7014 );
7015
7016 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7017 assert(
7018 editor,
7019 cx,
7020 indoc! {"
7021 a.f(one, «two», three) b
7022 a.f(one, «two», three) b
7023 a.f(one, «two», three) b
7024 "},
7025 );
7026
7027 editor.move_to_prev_snippet_tabstop(window, cx);
7028 assert(
7029 editor,
7030 cx,
7031 indoc! {"
7032 a.f(«one», two, «three») b
7033 a.f(«one», two, «three») b
7034 a.f(«one», two, «three») b
7035 "},
7036 );
7037
7038 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7039 assert(
7040 editor,
7041 cx,
7042 indoc! {"
7043 a.f(one, «two», three) b
7044 a.f(one, «two», three) b
7045 a.f(one, «two», three) b
7046 "},
7047 );
7048 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7049 assert(
7050 editor,
7051 cx,
7052 indoc! {"
7053 a.f(one, two, three)ˇ b
7054 a.f(one, two, three)ˇ b
7055 a.f(one, two, three)ˇ b
7056 "},
7057 );
7058
7059 // As soon as the last tab stop is reached, snippet state is gone
7060 editor.move_to_prev_snippet_tabstop(window, cx);
7061 assert(
7062 editor,
7063 cx,
7064 indoc! {"
7065 a.f(one, two, three)ˇ b
7066 a.f(one, two, three)ˇ b
7067 a.f(one, two, three)ˇ b
7068 "},
7069 );
7070 });
7071}
7072
7073#[gpui::test]
7074async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
7075 init_test(cx, |_| {});
7076
7077 let fs = FakeFs::new(cx.executor());
7078 fs.insert_file(path!("/file.rs"), Default::default()).await;
7079
7080 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7081
7082 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7083 language_registry.add(rust_lang());
7084 let mut fake_servers = language_registry.register_fake_lsp(
7085 "Rust",
7086 FakeLspAdapter {
7087 capabilities: lsp::ServerCapabilities {
7088 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7089 ..Default::default()
7090 },
7091 ..Default::default()
7092 },
7093 );
7094
7095 let buffer = project
7096 .update(cx, |project, cx| {
7097 project.open_local_buffer(path!("/file.rs"), cx)
7098 })
7099 .await
7100 .unwrap();
7101
7102 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7103 let (editor, cx) = cx.add_window_view(|window, cx| {
7104 build_editor_with_project(project.clone(), buffer, window, cx)
7105 });
7106 editor.update_in(cx, |editor, window, cx| {
7107 editor.set_text("one\ntwo\nthree\n", window, cx)
7108 });
7109 assert!(cx.read(|cx| editor.is_dirty(cx)));
7110
7111 cx.executor().start_waiting();
7112 let fake_server = fake_servers.next().await.unwrap();
7113
7114 let save = editor
7115 .update_in(cx, |editor, window, cx| {
7116 editor.save(true, project.clone(), window, cx)
7117 })
7118 .unwrap();
7119 fake_server
7120 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7121 assert_eq!(
7122 params.text_document.uri,
7123 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7124 );
7125 assert_eq!(params.options.tab_size, 4);
7126 Ok(Some(vec![lsp::TextEdit::new(
7127 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7128 ", ".to_string(),
7129 )]))
7130 })
7131 .next()
7132 .await;
7133 cx.executor().start_waiting();
7134 save.await;
7135
7136 assert_eq!(
7137 editor.update(cx, |editor, cx| editor.text(cx)),
7138 "one, two\nthree\n"
7139 );
7140 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7141
7142 editor.update_in(cx, |editor, window, cx| {
7143 editor.set_text("one\ntwo\nthree\n", window, cx)
7144 });
7145 assert!(cx.read(|cx| editor.is_dirty(cx)));
7146
7147 // Ensure we can still save even if formatting hangs.
7148 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7149 assert_eq!(
7150 params.text_document.uri,
7151 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7152 );
7153 futures::future::pending::<()>().await;
7154 unreachable!()
7155 });
7156 let save = editor
7157 .update_in(cx, |editor, window, cx| {
7158 editor.save(true, project.clone(), window, cx)
7159 })
7160 .unwrap();
7161 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7162 cx.executor().start_waiting();
7163 save.await;
7164 assert_eq!(
7165 editor.update(cx, |editor, cx| editor.text(cx)),
7166 "one\ntwo\nthree\n"
7167 );
7168 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7169
7170 // For non-dirty buffer, no formatting request should be sent
7171 let save = editor
7172 .update_in(cx, |editor, window, cx| {
7173 editor.save(true, project.clone(), window, cx)
7174 })
7175 .unwrap();
7176 let _pending_format_request = fake_server
7177 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7178 panic!("Should not be invoked on non-dirty buffer");
7179 })
7180 .next();
7181 cx.executor().start_waiting();
7182 save.await;
7183
7184 // Set rust language override and assert overridden tabsize is sent to language server
7185 update_test_language_settings(cx, |settings| {
7186 settings.languages.insert(
7187 "Rust".into(),
7188 LanguageSettingsContent {
7189 tab_size: NonZeroU32::new(8),
7190 ..Default::default()
7191 },
7192 );
7193 });
7194
7195 editor.update_in(cx, |editor, window, cx| {
7196 editor.set_text("somehting_new\n", window, cx)
7197 });
7198 assert!(cx.read(|cx| editor.is_dirty(cx)));
7199 let save = editor
7200 .update_in(cx, |editor, window, cx| {
7201 editor.save(true, project.clone(), window, cx)
7202 })
7203 .unwrap();
7204 fake_server
7205 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7206 assert_eq!(
7207 params.text_document.uri,
7208 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7209 );
7210 assert_eq!(params.options.tab_size, 8);
7211 Ok(Some(vec![]))
7212 })
7213 .next()
7214 .await;
7215 cx.executor().start_waiting();
7216 save.await;
7217}
7218
7219#[gpui::test]
7220async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
7221 init_test(cx, |_| {});
7222
7223 let cols = 4;
7224 let rows = 10;
7225 let sample_text_1 = sample_text(rows, cols, 'a');
7226 assert_eq!(
7227 sample_text_1,
7228 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7229 );
7230 let sample_text_2 = sample_text(rows, cols, 'l');
7231 assert_eq!(
7232 sample_text_2,
7233 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7234 );
7235 let sample_text_3 = sample_text(rows, cols, 'v');
7236 assert_eq!(
7237 sample_text_3,
7238 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7239 );
7240
7241 let fs = FakeFs::new(cx.executor());
7242 fs.insert_tree(
7243 path!("/a"),
7244 json!({
7245 "main.rs": sample_text_1,
7246 "other.rs": sample_text_2,
7247 "lib.rs": sample_text_3,
7248 }),
7249 )
7250 .await;
7251
7252 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7253 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7254 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7255
7256 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7257 language_registry.add(rust_lang());
7258 let mut fake_servers = language_registry.register_fake_lsp(
7259 "Rust",
7260 FakeLspAdapter {
7261 capabilities: lsp::ServerCapabilities {
7262 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7263 ..Default::default()
7264 },
7265 ..Default::default()
7266 },
7267 );
7268
7269 let worktree = project.update(cx, |project, cx| {
7270 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7271 assert_eq!(worktrees.len(), 1);
7272 worktrees.pop().unwrap()
7273 });
7274 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7275
7276 let buffer_1 = project
7277 .update(cx, |project, cx| {
7278 project.open_buffer((worktree_id, "main.rs"), cx)
7279 })
7280 .await
7281 .unwrap();
7282 let buffer_2 = project
7283 .update(cx, |project, cx| {
7284 project.open_buffer((worktree_id, "other.rs"), cx)
7285 })
7286 .await
7287 .unwrap();
7288 let buffer_3 = project
7289 .update(cx, |project, cx| {
7290 project.open_buffer((worktree_id, "lib.rs"), cx)
7291 })
7292 .await
7293 .unwrap();
7294
7295 let multi_buffer = cx.new(|cx| {
7296 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7297 multi_buffer.push_excerpts(
7298 buffer_1.clone(),
7299 [
7300 ExcerptRange {
7301 context: Point::new(0, 0)..Point::new(3, 0),
7302 primary: None,
7303 },
7304 ExcerptRange {
7305 context: Point::new(5, 0)..Point::new(7, 0),
7306 primary: None,
7307 },
7308 ExcerptRange {
7309 context: Point::new(9, 0)..Point::new(10, 4),
7310 primary: None,
7311 },
7312 ],
7313 cx,
7314 );
7315 multi_buffer.push_excerpts(
7316 buffer_2.clone(),
7317 [
7318 ExcerptRange {
7319 context: Point::new(0, 0)..Point::new(3, 0),
7320 primary: None,
7321 },
7322 ExcerptRange {
7323 context: Point::new(5, 0)..Point::new(7, 0),
7324 primary: None,
7325 },
7326 ExcerptRange {
7327 context: Point::new(9, 0)..Point::new(10, 4),
7328 primary: None,
7329 },
7330 ],
7331 cx,
7332 );
7333 multi_buffer.push_excerpts(
7334 buffer_3.clone(),
7335 [
7336 ExcerptRange {
7337 context: Point::new(0, 0)..Point::new(3, 0),
7338 primary: None,
7339 },
7340 ExcerptRange {
7341 context: Point::new(5, 0)..Point::new(7, 0),
7342 primary: None,
7343 },
7344 ExcerptRange {
7345 context: Point::new(9, 0)..Point::new(10, 4),
7346 primary: None,
7347 },
7348 ],
7349 cx,
7350 );
7351 multi_buffer
7352 });
7353 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7354 Editor::new(
7355 EditorMode::Full,
7356 multi_buffer,
7357 Some(project.clone()),
7358 true,
7359 window,
7360 cx,
7361 )
7362 });
7363
7364 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7365 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7366 s.select_ranges(Some(1..2))
7367 });
7368 editor.insert("|one|two|three|", window, cx);
7369 });
7370 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7371 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7372 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7373 s.select_ranges(Some(60..70))
7374 });
7375 editor.insert("|four|five|six|", window, cx);
7376 });
7377 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7378
7379 // First two buffers should be edited, but not the third one.
7380 assert_eq!(
7381 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7382 "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}",
7383 );
7384 buffer_1.update(cx, |buffer, _| {
7385 assert!(buffer.is_dirty());
7386 assert_eq!(
7387 buffer.text(),
7388 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7389 )
7390 });
7391 buffer_2.update(cx, |buffer, _| {
7392 assert!(buffer.is_dirty());
7393 assert_eq!(
7394 buffer.text(),
7395 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7396 )
7397 });
7398 buffer_3.update(cx, |buffer, _| {
7399 assert!(!buffer.is_dirty());
7400 assert_eq!(buffer.text(), sample_text_3,)
7401 });
7402 cx.executor().run_until_parked();
7403
7404 cx.executor().start_waiting();
7405 let save = multi_buffer_editor
7406 .update_in(cx, |editor, window, cx| {
7407 editor.save(true, project.clone(), window, cx)
7408 })
7409 .unwrap();
7410
7411 let fake_server = fake_servers.next().await.unwrap();
7412 fake_server
7413 .server
7414 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7415 Ok(Some(vec![lsp::TextEdit::new(
7416 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7417 format!("[{} formatted]", params.text_document.uri),
7418 )]))
7419 })
7420 .detach();
7421 save.await;
7422
7423 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7424 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7425 assert_eq!(
7426 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7427 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}"),
7428 );
7429 buffer_1.update(cx, |buffer, _| {
7430 assert!(!buffer.is_dirty());
7431 assert_eq!(
7432 buffer.text(),
7433 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7434 )
7435 });
7436 buffer_2.update(cx, |buffer, _| {
7437 assert!(!buffer.is_dirty());
7438 assert_eq!(
7439 buffer.text(),
7440 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7441 )
7442 });
7443 buffer_3.update(cx, |buffer, _| {
7444 assert!(!buffer.is_dirty());
7445 assert_eq!(buffer.text(), sample_text_3,)
7446 });
7447}
7448
7449#[gpui::test]
7450async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7451 init_test(cx, |_| {});
7452
7453 let fs = FakeFs::new(cx.executor());
7454 fs.insert_file(path!("/file.rs"), Default::default()).await;
7455
7456 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7457
7458 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7459 language_registry.add(rust_lang());
7460 let mut fake_servers = language_registry.register_fake_lsp(
7461 "Rust",
7462 FakeLspAdapter {
7463 capabilities: lsp::ServerCapabilities {
7464 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7465 ..Default::default()
7466 },
7467 ..Default::default()
7468 },
7469 );
7470
7471 let buffer = project
7472 .update(cx, |project, cx| {
7473 project.open_local_buffer(path!("/file.rs"), cx)
7474 })
7475 .await
7476 .unwrap();
7477
7478 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7479 let (editor, cx) = cx.add_window_view(|window, cx| {
7480 build_editor_with_project(project.clone(), buffer, window, cx)
7481 });
7482 editor.update_in(cx, |editor, window, cx| {
7483 editor.set_text("one\ntwo\nthree\n", window, cx)
7484 });
7485 assert!(cx.read(|cx| editor.is_dirty(cx)));
7486
7487 cx.executor().start_waiting();
7488 let fake_server = fake_servers.next().await.unwrap();
7489
7490 let save = editor
7491 .update_in(cx, |editor, window, cx| {
7492 editor.save(true, project.clone(), window, cx)
7493 })
7494 .unwrap();
7495 fake_server
7496 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7497 assert_eq!(
7498 params.text_document.uri,
7499 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7500 );
7501 assert_eq!(params.options.tab_size, 4);
7502 Ok(Some(vec![lsp::TextEdit::new(
7503 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7504 ", ".to_string(),
7505 )]))
7506 })
7507 .next()
7508 .await;
7509 cx.executor().start_waiting();
7510 save.await;
7511 assert_eq!(
7512 editor.update(cx, |editor, cx| editor.text(cx)),
7513 "one, two\nthree\n"
7514 );
7515 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7516
7517 editor.update_in(cx, |editor, window, cx| {
7518 editor.set_text("one\ntwo\nthree\n", window, cx)
7519 });
7520 assert!(cx.read(|cx| editor.is_dirty(cx)));
7521
7522 // Ensure we can still save even if formatting hangs.
7523 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7524 move |params, _| async move {
7525 assert_eq!(
7526 params.text_document.uri,
7527 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7528 );
7529 futures::future::pending::<()>().await;
7530 unreachable!()
7531 },
7532 );
7533 let save = editor
7534 .update_in(cx, |editor, window, cx| {
7535 editor.save(true, project.clone(), window, cx)
7536 })
7537 .unwrap();
7538 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7539 cx.executor().start_waiting();
7540 save.await;
7541 assert_eq!(
7542 editor.update(cx, |editor, cx| editor.text(cx)),
7543 "one\ntwo\nthree\n"
7544 );
7545 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7546
7547 // For non-dirty buffer, no formatting request should be sent
7548 let save = editor
7549 .update_in(cx, |editor, window, cx| {
7550 editor.save(true, project.clone(), window, cx)
7551 })
7552 .unwrap();
7553 let _pending_format_request = fake_server
7554 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7555 panic!("Should not be invoked on non-dirty buffer");
7556 })
7557 .next();
7558 cx.executor().start_waiting();
7559 save.await;
7560
7561 // Set Rust language override and assert overridden tabsize is sent to language server
7562 update_test_language_settings(cx, |settings| {
7563 settings.languages.insert(
7564 "Rust".into(),
7565 LanguageSettingsContent {
7566 tab_size: NonZeroU32::new(8),
7567 ..Default::default()
7568 },
7569 );
7570 });
7571
7572 editor.update_in(cx, |editor, window, cx| {
7573 editor.set_text("somehting_new\n", window, cx)
7574 });
7575 assert!(cx.read(|cx| editor.is_dirty(cx)));
7576 let save = editor
7577 .update_in(cx, |editor, window, cx| {
7578 editor.save(true, project.clone(), window, cx)
7579 })
7580 .unwrap();
7581 fake_server
7582 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7583 assert_eq!(
7584 params.text_document.uri,
7585 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7586 );
7587 assert_eq!(params.options.tab_size, 8);
7588 Ok(Some(vec![]))
7589 })
7590 .next()
7591 .await;
7592 cx.executor().start_waiting();
7593 save.await;
7594}
7595
7596#[gpui::test]
7597async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7598 init_test(cx, |settings| {
7599 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7600 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7601 ))
7602 });
7603
7604 let fs = FakeFs::new(cx.executor());
7605 fs.insert_file(path!("/file.rs"), Default::default()).await;
7606
7607 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7608
7609 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7610 language_registry.add(Arc::new(Language::new(
7611 LanguageConfig {
7612 name: "Rust".into(),
7613 matcher: LanguageMatcher {
7614 path_suffixes: vec!["rs".to_string()],
7615 ..Default::default()
7616 },
7617 ..LanguageConfig::default()
7618 },
7619 Some(tree_sitter_rust::LANGUAGE.into()),
7620 )));
7621 update_test_language_settings(cx, |settings| {
7622 // Enable Prettier formatting for the same buffer, and ensure
7623 // LSP is called instead of Prettier.
7624 settings.defaults.prettier = Some(PrettierSettings {
7625 allowed: true,
7626 ..PrettierSettings::default()
7627 });
7628 });
7629 let mut fake_servers = language_registry.register_fake_lsp(
7630 "Rust",
7631 FakeLspAdapter {
7632 capabilities: lsp::ServerCapabilities {
7633 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7634 ..Default::default()
7635 },
7636 ..Default::default()
7637 },
7638 );
7639
7640 let buffer = project
7641 .update(cx, |project, cx| {
7642 project.open_local_buffer(path!("/file.rs"), cx)
7643 })
7644 .await
7645 .unwrap();
7646
7647 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7648 let (editor, cx) = cx.add_window_view(|window, cx| {
7649 build_editor_with_project(project.clone(), buffer, window, cx)
7650 });
7651 editor.update_in(cx, |editor, window, cx| {
7652 editor.set_text("one\ntwo\nthree\n", window, cx)
7653 });
7654
7655 cx.executor().start_waiting();
7656 let fake_server = fake_servers.next().await.unwrap();
7657
7658 let format = editor
7659 .update_in(cx, |editor, window, cx| {
7660 editor.perform_format(
7661 project.clone(),
7662 FormatTrigger::Manual,
7663 FormatTarget::Buffers,
7664 window,
7665 cx,
7666 )
7667 })
7668 .unwrap();
7669 fake_server
7670 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7671 assert_eq!(
7672 params.text_document.uri,
7673 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7674 );
7675 assert_eq!(params.options.tab_size, 4);
7676 Ok(Some(vec![lsp::TextEdit::new(
7677 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7678 ", ".to_string(),
7679 )]))
7680 })
7681 .next()
7682 .await;
7683 cx.executor().start_waiting();
7684 format.await;
7685 assert_eq!(
7686 editor.update(cx, |editor, cx| editor.text(cx)),
7687 "one, two\nthree\n"
7688 );
7689
7690 editor.update_in(cx, |editor, window, cx| {
7691 editor.set_text("one\ntwo\nthree\n", window, cx)
7692 });
7693 // Ensure we don't lock if formatting hangs.
7694 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7695 assert_eq!(
7696 params.text_document.uri,
7697 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7698 );
7699 futures::future::pending::<()>().await;
7700 unreachable!()
7701 });
7702 let format = editor
7703 .update_in(cx, |editor, window, cx| {
7704 editor.perform_format(
7705 project,
7706 FormatTrigger::Manual,
7707 FormatTarget::Buffers,
7708 window,
7709 cx,
7710 )
7711 })
7712 .unwrap();
7713 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7714 cx.executor().start_waiting();
7715 format.await;
7716 assert_eq!(
7717 editor.update(cx, |editor, cx| editor.text(cx)),
7718 "one\ntwo\nthree\n"
7719 );
7720}
7721
7722#[gpui::test]
7723async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7724 init_test(cx, |_| {});
7725
7726 let mut cx = EditorLspTestContext::new_rust(
7727 lsp::ServerCapabilities {
7728 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7729 ..Default::default()
7730 },
7731 cx,
7732 )
7733 .await;
7734
7735 cx.set_state(indoc! {"
7736 one.twoˇ
7737 "});
7738
7739 // The format request takes a long time. When it completes, it inserts
7740 // a newline and an indent before the `.`
7741 cx.lsp
7742 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7743 let executor = cx.background_executor().clone();
7744 async move {
7745 executor.timer(Duration::from_millis(100)).await;
7746 Ok(Some(vec![lsp::TextEdit {
7747 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7748 new_text: "\n ".into(),
7749 }]))
7750 }
7751 });
7752
7753 // Submit a format request.
7754 let format_1 = cx
7755 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7756 .unwrap();
7757 cx.executor().run_until_parked();
7758
7759 // Submit a second format request.
7760 let format_2 = cx
7761 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7762 .unwrap();
7763 cx.executor().run_until_parked();
7764
7765 // Wait for both format requests to complete
7766 cx.executor().advance_clock(Duration::from_millis(200));
7767 cx.executor().start_waiting();
7768 format_1.await.unwrap();
7769 cx.executor().start_waiting();
7770 format_2.await.unwrap();
7771
7772 // The formatting edits only happens once.
7773 cx.assert_editor_state(indoc! {"
7774 one
7775 .twoˇ
7776 "});
7777}
7778
7779#[gpui::test]
7780async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7781 init_test(cx, |settings| {
7782 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7783 });
7784
7785 let mut cx = EditorLspTestContext::new_rust(
7786 lsp::ServerCapabilities {
7787 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7788 ..Default::default()
7789 },
7790 cx,
7791 )
7792 .await;
7793
7794 // Set up a buffer white some trailing whitespace and no trailing newline.
7795 cx.set_state(
7796 &[
7797 "one ", //
7798 "twoˇ", //
7799 "three ", //
7800 "four", //
7801 ]
7802 .join("\n"),
7803 );
7804
7805 // Submit a format request.
7806 let format = cx
7807 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7808 .unwrap();
7809
7810 // Record which buffer changes have been sent to the language server
7811 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7812 cx.lsp
7813 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7814 let buffer_changes = buffer_changes.clone();
7815 move |params, _| {
7816 buffer_changes.lock().extend(
7817 params
7818 .content_changes
7819 .into_iter()
7820 .map(|e| (e.range.unwrap(), e.text)),
7821 );
7822 }
7823 });
7824
7825 // Handle formatting requests to the language server.
7826 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7827 let buffer_changes = buffer_changes.clone();
7828 move |_, _| {
7829 // When formatting is requested, trailing whitespace has already been stripped,
7830 // and the trailing newline has already been added.
7831 assert_eq!(
7832 &buffer_changes.lock()[1..],
7833 &[
7834 (
7835 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7836 "".into()
7837 ),
7838 (
7839 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7840 "".into()
7841 ),
7842 (
7843 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7844 "\n".into()
7845 ),
7846 ]
7847 );
7848
7849 // Insert blank lines between each line of the buffer.
7850 async move {
7851 Ok(Some(vec![
7852 lsp::TextEdit {
7853 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7854 new_text: "\n".into(),
7855 },
7856 lsp::TextEdit {
7857 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7858 new_text: "\n".into(),
7859 },
7860 ]))
7861 }
7862 }
7863 });
7864
7865 // After formatting the buffer, the trailing whitespace is stripped,
7866 // a newline is appended, and the edits provided by the language server
7867 // have been applied.
7868 format.await.unwrap();
7869 cx.assert_editor_state(
7870 &[
7871 "one", //
7872 "", //
7873 "twoˇ", //
7874 "", //
7875 "three", //
7876 "four", //
7877 "", //
7878 ]
7879 .join("\n"),
7880 );
7881
7882 // Undoing the formatting undoes the trailing whitespace removal, the
7883 // trailing newline, and the LSP edits.
7884 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7885 cx.assert_editor_state(
7886 &[
7887 "one ", //
7888 "twoˇ", //
7889 "three ", //
7890 "four", //
7891 ]
7892 .join("\n"),
7893 );
7894}
7895
7896#[gpui::test]
7897async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7898 cx: &mut gpui::TestAppContext,
7899) {
7900 init_test(cx, |_| {});
7901
7902 cx.update(|cx| {
7903 cx.update_global::<SettingsStore, _>(|settings, cx| {
7904 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7905 settings.auto_signature_help = Some(true);
7906 });
7907 });
7908 });
7909
7910 let mut cx = EditorLspTestContext::new_rust(
7911 lsp::ServerCapabilities {
7912 signature_help_provider: Some(lsp::SignatureHelpOptions {
7913 ..Default::default()
7914 }),
7915 ..Default::default()
7916 },
7917 cx,
7918 )
7919 .await;
7920
7921 let language = Language::new(
7922 LanguageConfig {
7923 name: "Rust".into(),
7924 brackets: BracketPairConfig {
7925 pairs: vec![
7926 BracketPair {
7927 start: "{".to_string(),
7928 end: "}".to_string(),
7929 close: true,
7930 surround: true,
7931 newline: true,
7932 },
7933 BracketPair {
7934 start: "(".to_string(),
7935 end: ")".to_string(),
7936 close: true,
7937 surround: true,
7938 newline: true,
7939 },
7940 BracketPair {
7941 start: "/*".to_string(),
7942 end: " */".to_string(),
7943 close: true,
7944 surround: true,
7945 newline: true,
7946 },
7947 BracketPair {
7948 start: "[".to_string(),
7949 end: "]".to_string(),
7950 close: false,
7951 surround: false,
7952 newline: true,
7953 },
7954 BracketPair {
7955 start: "\"".to_string(),
7956 end: "\"".to_string(),
7957 close: true,
7958 surround: true,
7959 newline: false,
7960 },
7961 BracketPair {
7962 start: "<".to_string(),
7963 end: ">".to_string(),
7964 close: false,
7965 surround: true,
7966 newline: true,
7967 },
7968 ],
7969 ..Default::default()
7970 },
7971 autoclose_before: "})]".to_string(),
7972 ..Default::default()
7973 },
7974 Some(tree_sitter_rust::LANGUAGE.into()),
7975 );
7976 let language = Arc::new(language);
7977
7978 cx.language_registry().add(language.clone());
7979 cx.update_buffer(|buffer, cx| {
7980 buffer.set_language(Some(language), cx);
7981 });
7982
7983 cx.set_state(
7984 &r#"
7985 fn main() {
7986 sampleˇ
7987 }
7988 "#
7989 .unindent(),
7990 );
7991
7992 cx.update_editor(|editor, window, cx| {
7993 editor.handle_input("(", window, cx);
7994 });
7995 cx.assert_editor_state(
7996 &"
7997 fn main() {
7998 sample(ˇ)
7999 }
8000 "
8001 .unindent(),
8002 );
8003
8004 let mocked_response = lsp::SignatureHelp {
8005 signatures: vec![lsp::SignatureInformation {
8006 label: "fn sample(param1: u8, param2: u8)".to_string(),
8007 documentation: None,
8008 parameters: Some(vec![
8009 lsp::ParameterInformation {
8010 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8011 documentation: None,
8012 },
8013 lsp::ParameterInformation {
8014 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8015 documentation: None,
8016 },
8017 ]),
8018 active_parameter: None,
8019 }],
8020 active_signature: Some(0),
8021 active_parameter: Some(0),
8022 };
8023 handle_signature_help_request(&mut cx, mocked_response).await;
8024
8025 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8026 .await;
8027
8028 cx.editor(|editor, _, _| {
8029 let signature_help_state = editor.signature_help_state.popover().cloned();
8030 assert!(signature_help_state.is_some());
8031 let ParsedMarkdown {
8032 text, highlights, ..
8033 } = signature_help_state.unwrap().parsed_content;
8034 assert_eq!(text, "param1: u8, param2: u8");
8035 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8036 });
8037}
8038
8039#[gpui::test]
8040async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
8041 init_test(cx, |_| {});
8042
8043 cx.update(|cx| {
8044 cx.update_global::<SettingsStore, _>(|settings, cx| {
8045 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8046 settings.auto_signature_help = Some(false);
8047 settings.show_signature_help_after_edits = Some(false);
8048 });
8049 });
8050 });
8051
8052 let mut cx = EditorLspTestContext::new_rust(
8053 lsp::ServerCapabilities {
8054 signature_help_provider: Some(lsp::SignatureHelpOptions {
8055 ..Default::default()
8056 }),
8057 ..Default::default()
8058 },
8059 cx,
8060 )
8061 .await;
8062
8063 let language = Language::new(
8064 LanguageConfig {
8065 name: "Rust".into(),
8066 brackets: BracketPairConfig {
8067 pairs: vec![
8068 BracketPair {
8069 start: "{".to_string(),
8070 end: "}".to_string(),
8071 close: true,
8072 surround: true,
8073 newline: true,
8074 },
8075 BracketPair {
8076 start: "(".to_string(),
8077 end: ")".to_string(),
8078 close: true,
8079 surround: true,
8080 newline: true,
8081 },
8082 BracketPair {
8083 start: "/*".to_string(),
8084 end: " */".to_string(),
8085 close: true,
8086 surround: true,
8087 newline: true,
8088 },
8089 BracketPair {
8090 start: "[".to_string(),
8091 end: "]".to_string(),
8092 close: false,
8093 surround: false,
8094 newline: true,
8095 },
8096 BracketPair {
8097 start: "\"".to_string(),
8098 end: "\"".to_string(),
8099 close: true,
8100 surround: true,
8101 newline: false,
8102 },
8103 BracketPair {
8104 start: "<".to_string(),
8105 end: ">".to_string(),
8106 close: false,
8107 surround: true,
8108 newline: true,
8109 },
8110 ],
8111 ..Default::default()
8112 },
8113 autoclose_before: "})]".to_string(),
8114 ..Default::default()
8115 },
8116 Some(tree_sitter_rust::LANGUAGE.into()),
8117 );
8118 let language = Arc::new(language);
8119
8120 cx.language_registry().add(language.clone());
8121 cx.update_buffer(|buffer, cx| {
8122 buffer.set_language(Some(language), cx);
8123 });
8124
8125 // Ensure that signature_help is not called when no signature help is enabled.
8126 cx.set_state(
8127 &r#"
8128 fn main() {
8129 sampleˇ
8130 }
8131 "#
8132 .unindent(),
8133 );
8134 cx.update_editor(|editor, window, cx| {
8135 editor.handle_input("(", window, cx);
8136 });
8137 cx.assert_editor_state(
8138 &"
8139 fn main() {
8140 sample(ˇ)
8141 }
8142 "
8143 .unindent(),
8144 );
8145 cx.editor(|editor, _, _| {
8146 assert!(editor.signature_help_state.task().is_none());
8147 });
8148
8149 let mocked_response = lsp::SignatureHelp {
8150 signatures: vec![lsp::SignatureInformation {
8151 label: "fn sample(param1: u8, param2: u8)".to_string(),
8152 documentation: None,
8153 parameters: Some(vec![
8154 lsp::ParameterInformation {
8155 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8156 documentation: None,
8157 },
8158 lsp::ParameterInformation {
8159 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8160 documentation: None,
8161 },
8162 ]),
8163 active_parameter: None,
8164 }],
8165 active_signature: Some(0),
8166 active_parameter: Some(0),
8167 };
8168
8169 // Ensure that signature_help is called when enabled afte edits
8170 cx.update(|_, cx| {
8171 cx.update_global::<SettingsStore, _>(|settings, cx| {
8172 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8173 settings.auto_signature_help = Some(false);
8174 settings.show_signature_help_after_edits = Some(true);
8175 });
8176 });
8177 });
8178 cx.set_state(
8179 &r#"
8180 fn main() {
8181 sampleˇ
8182 }
8183 "#
8184 .unindent(),
8185 );
8186 cx.update_editor(|editor, window, cx| {
8187 editor.handle_input("(", window, cx);
8188 });
8189 cx.assert_editor_state(
8190 &"
8191 fn main() {
8192 sample(ˇ)
8193 }
8194 "
8195 .unindent(),
8196 );
8197 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8198 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8199 .await;
8200 cx.update_editor(|editor, _, _| {
8201 let signature_help_state = editor.signature_help_state.popover().cloned();
8202 assert!(signature_help_state.is_some());
8203 let ParsedMarkdown {
8204 text, highlights, ..
8205 } = signature_help_state.unwrap().parsed_content;
8206 assert_eq!(text, "param1: u8, param2: u8");
8207 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8208 editor.signature_help_state = SignatureHelpState::default();
8209 });
8210
8211 // Ensure that signature_help is called when auto signature help override is enabled
8212 cx.update(|_, cx| {
8213 cx.update_global::<SettingsStore, _>(|settings, cx| {
8214 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8215 settings.auto_signature_help = Some(true);
8216 settings.show_signature_help_after_edits = Some(false);
8217 });
8218 });
8219 });
8220 cx.set_state(
8221 &r#"
8222 fn main() {
8223 sampleˇ
8224 }
8225 "#
8226 .unindent(),
8227 );
8228 cx.update_editor(|editor, window, cx| {
8229 editor.handle_input("(", window, cx);
8230 });
8231 cx.assert_editor_state(
8232 &"
8233 fn main() {
8234 sample(ˇ)
8235 }
8236 "
8237 .unindent(),
8238 );
8239 handle_signature_help_request(&mut cx, mocked_response).await;
8240 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8241 .await;
8242 cx.editor(|editor, _, _| {
8243 let signature_help_state = editor.signature_help_state.popover().cloned();
8244 assert!(signature_help_state.is_some());
8245 let ParsedMarkdown {
8246 text, highlights, ..
8247 } = signature_help_state.unwrap().parsed_content;
8248 assert_eq!(text, "param1: u8, param2: u8");
8249 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8250 });
8251}
8252
8253#[gpui::test]
8254async fn test_signature_help(cx: &mut gpui::TestAppContext) {
8255 init_test(cx, |_| {});
8256 cx.update(|cx| {
8257 cx.update_global::<SettingsStore, _>(|settings, cx| {
8258 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8259 settings.auto_signature_help = Some(true);
8260 });
8261 });
8262 });
8263
8264 let mut cx = EditorLspTestContext::new_rust(
8265 lsp::ServerCapabilities {
8266 signature_help_provider: Some(lsp::SignatureHelpOptions {
8267 ..Default::default()
8268 }),
8269 ..Default::default()
8270 },
8271 cx,
8272 )
8273 .await;
8274
8275 // A test that directly calls `show_signature_help`
8276 cx.update_editor(|editor, window, cx| {
8277 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8278 });
8279
8280 let mocked_response = lsp::SignatureHelp {
8281 signatures: vec![lsp::SignatureInformation {
8282 label: "fn sample(param1: u8, param2: u8)".to_string(),
8283 documentation: None,
8284 parameters: Some(vec![
8285 lsp::ParameterInformation {
8286 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8287 documentation: None,
8288 },
8289 lsp::ParameterInformation {
8290 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8291 documentation: None,
8292 },
8293 ]),
8294 active_parameter: None,
8295 }],
8296 active_signature: Some(0),
8297 active_parameter: Some(0),
8298 };
8299 handle_signature_help_request(&mut cx, mocked_response).await;
8300
8301 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8302 .await;
8303
8304 cx.editor(|editor, _, _| {
8305 let signature_help_state = editor.signature_help_state.popover().cloned();
8306 assert!(signature_help_state.is_some());
8307 let ParsedMarkdown {
8308 text, highlights, ..
8309 } = signature_help_state.unwrap().parsed_content;
8310 assert_eq!(text, "param1: u8, param2: u8");
8311 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8312 });
8313
8314 // When exiting outside from inside the brackets, `signature_help` is closed.
8315 cx.set_state(indoc! {"
8316 fn main() {
8317 sample(ˇ);
8318 }
8319
8320 fn sample(param1: u8, param2: u8) {}
8321 "});
8322
8323 cx.update_editor(|editor, window, cx| {
8324 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8325 });
8326
8327 let mocked_response = lsp::SignatureHelp {
8328 signatures: Vec::new(),
8329 active_signature: None,
8330 active_parameter: None,
8331 };
8332 handle_signature_help_request(&mut cx, mocked_response).await;
8333
8334 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8335 .await;
8336
8337 cx.editor(|editor, _, _| {
8338 assert!(!editor.signature_help_state.is_shown());
8339 });
8340
8341 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8342 cx.set_state(indoc! {"
8343 fn main() {
8344 sample(ˇ);
8345 }
8346
8347 fn sample(param1: u8, param2: u8) {}
8348 "});
8349
8350 let mocked_response = lsp::SignatureHelp {
8351 signatures: vec![lsp::SignatureInformation {
8352 label: "fn sample(param1: u8, param2: u8)".to_string(),
8353 documentation: None,
8354 parameters: Some(vec![
8355 lsp::ParameterInformation {
8356 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8357 documentation: None,
8358 },
8359 lsp::ParameterInformation {
8360 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8361 documentation: None,
8362 },
8363 ]),
8364 active_parameter: None,
8365 }],
8366 active_signature: Some(0),
8367 active_parameter: Some(0),
8368 };
8369 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8370 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8371 .await;
8372 cx.editor(|editor, _, _| {
8373 assert!(editor.signature_help_state.is_shown());
8374 });
8375
8376 // Restore the popover with more parameter input
8377 cx.set_state(indoc! {"
8378 fn main() {
8379 sample(param1, param2ˇ);
8380 }
8381
8382 fn sample(param1: u8, param2: u8) {}
8383 "});
8384
8385 let mocked_response = lsp::SignatureHelp {
8386 signatures: vec![lsp::SignatureInformation {
8387 label: "fn sample(param1: u8, param2: u8)".to_string(),
8388 documentation: None,
8389 parameters: Some(vec![
8390 lsp::ParameterInformation {
8391 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8392 documentation: None,
8393 },
8394 lsp::ParameterInformation {
8395 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8396 documentation: None,
8397 },
8398 ]),
8399 active_parameter: None,
8400 }],
8401 active_signature: Some(0),
8402 active_parameter: Some(1),
8403 };
8404 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8405 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8406 .await;
8407
8408 // When selecting a range, the popover is gone.
8409 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8410 cx.update_editor(|editor, window, cx| {
8411 editor.change_selections(None, window, cx, |s| {
8412 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8413 })
8414 });
8415 cx.assert_editor_state(indoc! {"
8416 fn main() {
8417 sample(param1, «ˇparam2»);
8418 }
8419
8420 fn sample(param1: u8, param2: u8) {}
8421 "});
8422 cx.editor(|editor, _, _| {
8423 assert!(!editor.signature_help_state.is_shown());
8424 });
8425
8426 // When unselecting again, the popover is back if within the brackets.
8427 cx.update_editor(|editor, window, cx| {
8428 editor.change_selections(None, window, cx, |s| {
8429 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8430 })
8431 });
8432 cx.assert_editor_state(indoc! {"
8433 fn main() {
8434 sample(param1, ˇparam2);
8435 }
8436
8437 fn sample(param1: u8, param2: u8) {}
8438 "});
8439 handle_signature_help_request(&mut cx, mocked_response).await;
8440 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8441 .await;
8442 cx.editor(|editor, _, _| {
8443 assert!(editor.signature_help_state.is_shown());
8444 });
8445
8446 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8447 cx.update_editor(|editor, window, cx| {
8448 editor.change_selections(None, window, cx, |s| {
8449 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8450 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8451 })
8452 });
8453 cx.assert_editor_state(indoc! {"
8454 fn main() {
8455 sample(param1, ˇparam2);
8456 }
8457
8458 fn sample(param1: u8, param2: u8) {}
8459 "});
8460
8461 let mocked_response = lsp::SignatureHelp {
8462 signatures: vec![lsp::SignatureInformation {
8463 label: "fn sample(param1: u8, param2: u8)".to_string(),
8464 documentation: None,
8465 parameters: Some(vec![
8466 lsp::ParameterInformation {
8467 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8468 documentation: None,
8469 },
8470 lsp::ParameterInformation {
8471 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8472 documentation: None,
8473 },
8474 ]),
8475 active_parameter: None,
8476 }],
8477 active_signature: Some(0),
8478 active_parameter: Some(1),
8479 };
8480 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8481 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8482 .await;
8483 cx.update_editor(|editor, _, cx| {
8484 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8485 });
8486 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8487 .await;
8488 cx.update_editor(|editor, window, cx| {
8489 editor.change_selections(None, window, cx, |s| {
8490 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8491 })
8492 });
8493 cx.assert_editor_state(indoc! {"
8494 fn main() {
8495 sample(param1, «ˇparam2»);
8496 }
8497
8498 fn sample(param1: u8, param2: u8) {}
8499 "});
8500 cx.update_editor(|editor, window, cx| {
8501 editor.change_selections(None, window, cx, |s| {
8502 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8503 })
8504 });
8505 cx.assert_editor_state(indoc! {"
8506 fn main() {
8507 sample(param1, ˇparam2);
8508 }
8509
8510 fn sample(param1: u8, param2: u8) {}
8511 "});
8512 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8513 .await;
8514}
8515
8516#[gpui::test]
8517async fn test_completion(cx: &mut gpui::TestAppContext) {
8518 init_test(cx, |_| {});
8519
8520 let mut cx = EditorLspTestContext::new_rust(
8521 lsp::ServerCapabilities {
8522 completion_provider: Some(lsp::CompletionOptions {
8523 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8524 resolve_provider: Some(true),
8525 ..Default::default()
8526 }),
8527 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8528 ..Default::default()
8529 },
8530 cx,
8531 )
8532 .await;
8533 let counter = Arc::new(AtomicUsize::new(0));
8534
8535 cx.set_state(indoc! {"
8536 oneˇ
8537 two
8538 three
8539 "});
8540 cx.simulate_keystroke(".");
8541 handle_completion_request(
8542 &mut cx,
8543 indoc! {"
8544 one.|<>
8545 two
8546 three
8547 "},
8548 vec!["first_completion", "second_completion"],
8549 counter.clone(),
8550 )
8551 .await;
8552 cx.condition(|editor, _| editor.context_menu_visible())
8553 .await;
8554 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8555
8556 let _handler = handle_signature_help_request(
8557 &mut cx,
8558 lsp::SignatureHelp {
8559 signatures: vec![lsp::SignatureInformation {
8560 label: "test signature".to_string(),
8561 documentation: None,
8562 parameters: Some(vec![lsp::ParameterInformation {
8563 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8564 documentation: None,
8565 }]),
8566 active_parameter: None,
8567 }],
8568 active_signature: None,
8569 active_parameter: None,
8570 },
8571 );
8572 cx.update_editor(|editor, window, cx| {
8573 assert!(
8574 !editor.signature_help_state.is_shown(),
8575 "No signature help was called for"
8576 );
8577 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8578 });
8579 cx.run_until_parked();
8580 cx.update_editor(|editor, _, _| {
8581 assert!(
8582 !editor.signature_help_state.is_shown(),
8583 "No signature help should be shown when completions menu is open"
8584 );
8585 });
8586
8587 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8588 editor.context_menu_next(&Default::default(), window, cx);
8589 editor
8590 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8591 .unwrap()
8592 });
8593 cx.assert_editor_state(indoc! {"
8594 one.second_completionˇ
8595 two
8596 three
8597 "});
8598
8599 handle_resolve_completion_request(
8600 &mut cx,
8601 Some(vec![
8602 (
8603 //This overlaps with the primary completion edit which is
8604 //misbehavior from the LSP spec, test that we filter it out
8605 indoc! {"
8606 one.second_ˇcompletion
8607 two
8608 threeˇ
8609 "},
8610 "overlapping additional edit",
8611 ),
8612 (
8613 indoc! {"
8614 one.second_completion
8615 two
8616 threeˇ
8617 "},
8618 "\nadditional edit",
8619 ),
8620 ]),
8621 )
8622 .await;
8623 apply_additional_edits.await.unwrap();
8624 cx.assert_editor_state(indoc! {"
8625 one.second_completionˇ
8626 two
8627 three
8628 additional edit
8629 "});
8630
8631 cx.set_state(indoc! {"
8632 one.second_completion
8633 twoˇ
8634 threeˇ
8635 additional edit
8636 "});
8637 cx.simulate_keystroke(" ");
8638 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8639 cx.simulate_keystroke("s");
8640 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8641
8642 cx.assert_editor_state(indoc! {"
8643 one.second_completion
8644 two sˇ
8645 three sˇ
8646 additional edit
8647 "});
8648 handle_completion_request(
8649 &mut cx,
8650 indoc! {"
8651 one.second_completion
8652 two s
8653 three <s|>
8654 additional edit
8655 "},
8656 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8657 counter.clone(),
8658 )
8659 .await;
8660 cx.condition(|editor, _| editor.context_menu_visible())
8661 .await;
8662 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8663
8664 cx.simulate_keystroke("i");
8665
8666 handle_completion_request(
8667 &mut cx,
8668 indoc! {"
8669 one.second_completion
8670 two si
8671 three <si|>
8672 additional edit
8673 "},
8674 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8675 counter.clone(),
8676 )
8677 .await;
8678 cx.condition(|editor, _| editor.context_menu_visible())
8679 .await;
8680 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8681
8682 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8683 editor
8684 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8685 .unwrap()
8686 });
8687 cx.assert_editor_state(indoc! {"
8688 one.second_completion
8689 two sixth_completionˇ
8690 three sixth_completionˇ
8691 additional edit
8692 "});
8693
8694 apply_additional_edits.await.unwrap();
8695
8696 update_test_language_settings(&mut cx, |settings| {
8697 settings.defaults.show_completions_on_input = Some(false);
8698 });
8699 cx.set_state("editorˇ");
8700 cx.simulate_keystroke(".");
8701 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8702 cx.simulate_keystroke("c");
8703 cx.simulate_keystroke("l");
8704 cx.simulate_keystroke("o");
8705 cx.assert_editor_state("editor.cloˇ");
8706 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8707 cx.update_editor(|editor, window, cx| {
8708 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8709 });
8710 handle_completion_request(
8711 &mut cx,
8712 "editor.<clo|>",
8713 vec!["close", "clobber"],
8714 counter.clone(),
8715 )
8716 .await;
8717 cx.condition(|editor, _| editor.context_menu_visible())
8718 .await;
8719 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8720
8721 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8722 editor
8723 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8724 .unwrap()
8725 });
8726 cx.assert_editor_state("editor.closeˇ");
8727 handle_resolve_completion_request(&mut cx, None).await;
8728 apply_additional_edits.await.unwrap();
8729}
8730
8731#[gpui::test]
8732async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
8733 init_test(cx, |_| {});
8734
8735 let fs = FakeFs::new(cx.executor());
8736 fs.insert_tree(
8737 path!("/a"),
8738 json!({
8739 "main.ts": "a",
8740 }),
8741 )
8742 .await;
8743
8744 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8745 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8746 let typescript_language = Arc::new(Language::new(
8747 LanguageConfig {
8748 name: "TypeScript".into(),
8749 matcher: LanguageMatcher {
8750 path_suffixes: vec!["ts".to_string()],
8751 ..LanguageMatcher::default()
8752 },
8753 line_comments: vec!["// ".into()],
8754 ..LanguageConfig::default()
8755 },
8756 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8757 ));
8758 language_registry.add(typescript_language.clone());
8759 let mut fake_servers = language_registry.register_fake_lsp(
8760 "TypeScript",
8761 FakeLspAdapter {
8762 capabilities: lsp::ServerCapabilities {
8763 completion_provider: Some(lsp::CompletionOptions {
8764 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8765 ..lsp::CompletionOptions::default()
8766 }),
8767 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8768 ..lsp::ServerCapabilities::default()
8769 },
8770 // Emulate vtsls label generation
8771 label_for_completion: Some(Box::new(|item, _| {
8772 let text = if let Some(description) = item
8773 .label_details
8774 .as_ref()
8775 .and_then(|label_details| label_details.description.as_ref())
8776 {
8777 format!("{} {}", item.label, description)
8778 } else if let Some(detail) = &item.detail {
8779 format!("{} {}", item.label, detail)
8780 } else {
8781 item.label.clone()
8782 };
8783 let len = text.len();
8784 Some(language::CodeLabel {
8785 text,
8786 runs: Vec::new(),
8787 filter_range: 0..len,
8788 })
8789 })),
8790 ..FakeLspAdapter::default()
8791 },
8792 );
8793 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8794 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8795 let worktree_id = workspace
8796 .update(cx, |workspace, _window, cx| {
8797 workspace.project().update(cx, |project, cx| {
8798 project.worktrees(cx).next().unwrap().read(cx).id()
8799 })
8800 })
8801 .unwrap();
8802 let _buffer = project
8803 .update(cx, |project, cx| {
8804 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
8805 })
8806 .await
8807 .unwrap();
8808 let editor = workspace
8809 .update(cx, |workspace, window, cx| {
8810 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8811 })
8812 .unwrap()
8813 .await
8814 .unwrap()
8815 .downcast::<Editor>()
8816 .unwrap();
8817 let fake_server = fake_servers.next().await.unwrap();
8818
8819 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8820 let multiline_label_2 = "a\nb\nc\n";
8821 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8822 let multiline_description = "d\ne\nf\n";
8823 let multiline_detail_2 = "g\nh\ni\n";
8824
8825 let mut completion_handle =
8826 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8827 Ok(Some(lsp::CompletionResponse::Array(vec![
8828 lsp::CompletionItem {
8829 label: multiline_label.to_string(),
8830 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8831 range: lsp::Range {
8832 start: lsp::Position {
8833 line: params.text_document_position.position.line,
8834 character: params.text_document_position.position.character,
8835 },
8836 end: lsp::Position {
8837 line: params.text_document_position.position.line,
8838 character: params.text_document_position.position.character,
8839 },
8840 },
8841 new_text: "new_text_1".to_string(),
8842 })),
8843 ..lsp::CompletionItem::default()
8844 },
8845 lsp::CompletionItem {
8846 label: "single line label 1".to_string(),
8847 detail: Some(multiline_detail.to_string()),
8848 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8849 range: lsp::Range {
8850 start: lsp::Position {
8851 line: params.text_document_position.position.line,
8852 character: params.text_document_position.position.character,
8853 },
8854 end: lsp::Position {
8855 line: params.text_document_position.position.line,
8856 character: params.text_document_position.position.character,
8857 },
8858 },
8859 new_text: "new_text_2".to_string(),
8860 })),
8861 ..lsp::CompletionItem::default()
8862 },
8863 lsp::CompletionItem {
8864 label: "single line label 2".to_string(),
8865 label_details: Some(lsp::CompletionItemLabelDetails {
8866 description: Some(multiline_description.to_string()),
8867 detail: None,
8868 }),
8869 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8870 range: lsp::Range {
8871 start: lsp::Position {
8872 line: params.text_document_position.position.line,
8873 character: params.text_document_position.position.character,
8874 },
8875 end: lsp::Position {
8876 line: params.text_document_position.position.line,
8877 character: params.text_document_position.position.character,
8878 },
8879 },
8880 new_text: "new_text_2".to_string(),
8881 })),
8882 ..lsp::CompletionItem::default()
8883 },
8884 lsp::CompletionItem {
8885 label: multiline_label_2.to_string(),
8886 detail: Some(multiline_detail_2.to_string()),
8887 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8888 range: lsp::Range {
8889 start: lsp::Position {
8890 line: params.text_document_position.position.line,
8891 character: params.text_document_position.position.character,
8892 },
8893 end: lsp::Position {
8894 line: params.text_document_position.position.line,
8895 character: params.text_document_position.position.character,
8896 },
8897 },
8898 new_text: "new_text_3".to_string(),
8899 })),
8900 ..lsp::CompletionItem::default()
8901 },
8902 lsp::CompletionItem {
8903 label: "Label with many spaces and \t but without newlines".to_string(),
8904 detail: Some(
8905 "Details with many spaces and \t but without newlines".to_string(),
8906 ),
8907 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8908 range: lsp::Range {
8909 start: lsp::Position {
8910 line: params.text_document_position.position.line,
8911 character: params.text_document_position.position.character,
8912 },
8913 end: lsp::Position {
8914 line: params.text_document_position.position.line,
8915 character: params.text_document_position.position.character,
8916 },
8917 },
8918 new_text: "new_text_4".to_string(),
8919 })),
8920 ..lsp::CompletionItem::default()
8921 },
8922 ])))
8923 });
8924
8925 editor.update_in(cx, |editor, window, cx| {
8926 cx.focus_self(window);
8927 editor.move_to_end(&MoveToEnd, window, cx);
8928 editor.handle_input(".", window, cx);
8929 });
8930 cx.run_until_parked();
8931 completion_handle.next().await.unwrap();
8932
8933 editor.update(cx, |editor, _| {
8934 assert!(editor.context_menu_visible());
8935 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8936 {
8937 let completion_labels = menu
8938 .completions
8939 .borrow()
8940 .iter()
8941 .map(|c| c.label.text.clone())
8942 .collect::<Vec<_>>();
8943 assert_eq!(
8944 completion_labels,
8945 &[
8946 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
8947 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
8948 "single line label 2 d e f ",
8949 "a b c g h i ",
8950 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
8951 ],
8952 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
8953 );
8954
8955 for completion in menu
8956 .completions
8957 .borrow()
8958 .iter() {
8959 assert_eq!(
8960 completion.label.filter_range,
8961 0..completion.label.text.len(),
8962 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
8963 );
8964 }
8965
8966 } else {
8967 panic!("expected completion menu to be open");
8968 }
8969 });
8970}
8971
8972#[gpui::test]
8973async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8974 init_test(cx, |_| {});
8975 let mut cx = EditorLspTestContext::new_rust(
8976 lsp::ServerCapabilities {
8977 completion_provider: Some(lsp::CompletionOptions {
8978 trigger_characters: Some(vec![".".to_string()]),
8979 ..Default::default()
8980 }),
8981 ..Default::default()
8982 },
8983 cx,
8984 )
8985 .await;
8986 cx.lsp
8987 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8988 Ok(Some(lsp::CompletionResponse::Array(vec![
8989 lsp::CompletionItem {
8990 label: "first".into(),
8991 ..Default::default()
8992 },
8993 lsp::CompletionItem {
8994 label: "last".into(),
8995 ..Default::default()
8996 },
8997 ])))
8998 });
8999 cx.set_state("variableˇ");
9000 cx.simulate_keystroke(".");
9001 cx.executor().run_until_parked();
9002
9003 cx.update_editor(|editor, _, _| {
9004 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9005 {
9006 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9007 } else {
9008 panic!("expected completion menu to be open");
9009 }
9010 });
9011
9012 cx.update_editor(|editor, window, cx| {
9013 editor.move_page_down(&MovePageDown::default(), window, cx);
9014 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9015 {
9016 assert!(
9017 menu.selected_item == 1,
9018 "expected PageDown to select the last item from the context menu"
9019 );
9020 } else {
9021 panic!("expected completion menu to stay open after PageDown");
9022 }
9023 });
9024
9025 cx.update_editor(|editor, window, cx| {
9026 editor.move_page_up(&MovePageUp::default(), window, cx);
9027 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9028 {
9029 assert!(
9030 menu.selected_item == 0,
9031 "expected PageUp to select the first item from the context menu"
9032 );
9033 } else {
9034 panic!("expected completion menu to stay open after PageUp");
9035 }
9036 });
9037}
9038
9039#[gpui::test]
9040async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
9041 init_test(cx, |_| {});
9042 let mut cx = EditorLspTestContext::new_rust(
9043 lsp::ServerCapabilities {
9044 completion_provider: Some(lsp::CompletionOptions {
9045 trigger_characters: Some(vec![".".to_string()]),
9046 ..Default::default()
9047 }),
9048 ..Default::default()
9049 },
9050 cx,
9051 )
9052 .await;
9053 cx.lsp
9054 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9055 Ok(Some(lsp::CompletionResponse::Array(vec![
9056 lsp::CompletionItem {
9057 label: "Range".into(),
9058 sort_text: Some("a".into()),
9059 ..Default::default()
9060 },
9061 lsp::CompletionItem {
9062 label: "r".into(),
9063 sort_text: Some("b".into()),
9064 ..Default::default()
9065 },
9066 lsp::CompletionItem {
9067 label: "ret".into(),
9068 sort_text: Some("c".into()),
9069 ..Default::default()
9070 },
9071 lsp::CompletionItem {
9072 label: "return".into(),
9073 sort_text: Some("d".into()),
9074 ..Default::default()
9075 },
9076 lsp::CompletionItem {
9077 label: "slice".into(),
9078 sort_text: Some("d".into()),
9079 ..Default::default()
9080 },
9081 ])))
9082 });
9083 cx.set_state("rˇ");
9084 cx.executor().run_until_parked();
9085 cx.update_editor(|editor, window, cx| {
9086 editor.show_completions(
9087 &ShowCompletions {
9088 trigger: Some("r".into()),
9089 },
9090 window,
9091 cx,
9092 );
9093 });
9094 cx.executor().run_until_parked();
9095
9096 cx.update_editor(|editor, _, _| {
9097 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9098 {
9099 assert_eq!(
9100 completion_menu_entries(&menu),
9101 &["r", "ret", "Range", "return"]
9102 );
9103 } else {
9104 panic!("expected completion menu to be open");
9105 }
9106 });
9107}
9108
9109#[gpui::test]
9110async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
9111 init_test(cx, |_| {});
9112
9113 let mut cx = EditorLspTestContext::new_rust(
9114 lsp::ServerCapabilities {
9115 completion_provider: Some(lsp::CompletionOptions {
9116 trigger_characters: Some(vec![".".to_string()]),
9117 resolve_provider: Some(true),
9118 ..Default::default()
9119 }),
9120 ..Default::default()
9121 },
9122 cx,
9123 )
9124 .await;
9125
9126 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9127 cx.simulate_keystroke(".");
9128 let completion_item = lsp::CompletionItem {
9129 label: "Some".into(),
9130 kind: Some(lsp::CompletionItemKind::SNIPPET),
9131 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9132 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9133 kind: lsp::MarkupKind::Markdown,
9134 value: "```rust\nSome(2)\n```".to_string(),
9135 })),
9136 deprecated: Some(false),
9137 sort_text: Some("Some".to_string()),
9138 filter_text: Some("Some".to_string()),
9139 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9140 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9141 range: lsp::Range {
9142 start: lsp::Position {
9143 line: 0,
9144 character: 22,
9145 },
9146 end: lsp::Position {
9147 line: 0,
9148 character: 22,
9149 },
9150 },
9151 new_text: "Some(2)".to_string(),
9152 })),
9153 additional_text_edits: Some(vec![lsp::TextEdit {
9154 range: lsp::Range {
9155 start: lsp::Position {
9156 line: 0,
9157 character: 20,
9158 },
9159 end: lsp::Position {
9160 line: 0,
9161 character: 22,
9162 },
9163 },
9164 new_text: "".to_string(),
9165 }]),
9166 ..Default::default()
9167 };
9168
9169 let closure_completion_item = completion_item.clone();
9170 let counter = Arc::new(AtomicUsize::new(0));
9171 let counter_clone = counter.clone();
9172 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9173 let task_completion_item = closure_completion_item.clone();
9174 counter_clone.fetch_add(1, atomic::Ordering::Release);
9175 async move {
9176 Ok(Some(lsp::CompletionResponse::Array(vec![
9177 task_completion_item,
9178 ])))
9179 }
9180 });
9181
9182 cx.condition(|editor, _| editor.context_menu_visible())
9183 .await;
9184 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9185 assert!(request.next().await.is_some());
9186 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9187
9188 cx.simulate_keystroke("S");
9189 cx.simulate_keystroke("o");
9190 cx.simulate_keystroke("m");
9191 cx.condition(|editor, _| editor.context_menu_visible())
9192 .await;
9193 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9194 assert!(request.next().await.is_some());
9195 assert!(request.next().await.is_some());
9196 assert!(request.next().await.is_some());
9197 request.close();
9198 assert!(request.next().await.is_none());
9199 assert_eq!(
9200 counter.load(atomic::Ordering::Acquire),
9201 4,
9202 "With the completions menu open, only one LSP request should happen per input"
9203 );
9204}
9205
9206#[gpui::test]
9207async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
9208 init_test(cx, |_| {});
9209 let mut cx = EditorTestContext::new(cx).await;
9210 let language = Arc::new(Language::new(
9211 LanguageConfig {
9212 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9213 ..Default::default()
9214 },
9215 Some(tree_sitter_rust::LANGUAGE.into()),
9216 ));
9217 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9218
9219 // If multiple selections intersect a line, the line is only toggled once.
9220 cx.set_state(indoc! {"
9221 fn a() {
9222 «//b();
9223 ˇ»// «c();
9224 //ˇ» d();
9225 }
9226 "});
9227
9228 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9229
9230 cx.assert_editor_state(indoc! {"
9231 fn a() {
9232 «b();
9233 c();
9234 ˇ» d();
9235 }
9236 "});
9237
9238 // The comment prefix is inserted at the same column for every line in a
9239 // selection.
9240 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9241
9242 cx.assert_editor_state(indoc! {"
9243 fn a() {
9244 // «b();
9245 // c();
9246 ˇ»// d();
9247 }
9248 "});
9249
9250 // If a selection ends at the beginning of a line, that line is not toggled.
9251 cx.set_selections_state(indoc! {"
9252 fn a() {
9253 // b();
9254 «// c();
9255 ˇ» // d();
9256 }
9257 "});
9258
9259 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9260
9261 cx.assert_editor_state(indoc! {"
9262 fn a() {
9263 // b();
9264 «c();
9265 ˇ» // d();
9266 }
9267 "});
9268
9269 // If a selection span a single line and is empty, the line is toggled.
9270 cx.set_state(indoc! {"
9271 fn a() {
9272 a();
9273 b();
9274 ˇ
9275 }
9276 "});
9277
9278 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9279
9280 cx.assert_editor_state(indoc! {"
9281 fn a() {
9282 a();
9283 b();
9284 //•ˇ
9285 }
9286 "});
9287
9288 // If a selection span multiple lines, empty lines are not toggled.
9289 cx.set_state(indoc! {"
9290 fn a() {
9291 «a();
9292
9293 c();ˇ»
9294 }
9295 "});
9296
9297 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9298
9299 cx.assert_editor_state(indoc! {"
9300 fn a() {
9301 // «a();
9302
9303 // c();ˇ»
9304 }
9305 "});
9306
9307 // If a selection includes multiple comment prefixes, all lines are uncommented.
9308 cx.set_state(indoc! {"
9309 fn a() {
9310 «// a();
9311 /// b();
9312 //! c();ˇ»
9313 }
9314 "});
9315
9316 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9317
9318 cx.assert_editor_state(indoc! {"
9319 fn a() {
9320 «a();
9321 b();
9322 c();ˇ»
9323 }
9324 "});
9325}
9326
9327#[gpui::test]
9328async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
9329 init_test(cx, |_| {});
9330 let mut cx = EditorTestContext::new(cx).await;
9331 let language = Arc::new(Language::new(
9332 LanguageConfig {
9333 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9334 ..Default::default()
9335 },
9336 Some(tree_sitter_rust::LANGUAGE.into()),
9337 ));
9338 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9339
9340 let toggle_comments = &ToggleComments {
9341 advance_downwards: false,
9342 ignore_indent: true,
9343 };
9344
9345 // If multiple selections intersect a line, the line is only toggled once.
9346 cx.set_state(indoc! {"
9347 fn a() {
9348 // «b();
9349 // c();
9350 // ˇ» d();
9351 }
9352 "});
9353
9354 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9355
9356 cx.assert_editor_state(indoc! {"
9357 fn a() {
9358 «b();
9359 c();
9360 ˇ» d();
9361 }
9362 "});
9363
9364 // The comment prefix is inserted at the beginning of each line
9365 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9366
9367 cx.assert_editor_state(indoc! {"
9368 fn a() {
9369 // «b();
9370 // c();
9371 // ˇ» d();
9372 }
9373 "});
9374
9375 // If a selection ends at the beginning of a line, that line is not toggled.
9376 cx.set_selections_state(indoc! {"
9377 fn a() {
9378 // b();
9379 // «c();
9380 ˇ»// d();
9381 }
9382 "});
9383
9384 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9385
9386 cx.assert_editor_state(indoc! {"
9387 fn a() {
9388 // b();
9389 «c();
9390 ˇ»// d();
9391 }
9392 "});
9393
9394 // If a selection span a single line and is empty, the line is toggled.
9395 cx.set_state(indoc! {"
9396 fn a() {
9397 a();
9398 b();
9399 ˇ
9400 }
9401 "});
9402
9403 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9404
9405 cx.assert_editor_state(indoc! {"
9406 fn a() {
9407 a();
9408 b();
9409 //ˇ
9410 }
9411 "});
9412
9413 // If a selection span multiple lines, empty lines are not toggled.
9414 cx.set_state(indoc! {"
9415 fn a() {
9416 «a();
9417
9418 c();ˇ»
9419 }
9420 "});
9421
9422 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9423
9424 cx.assert_editor_state(indoc! {"
9425 fn a() {
9426 // «a();
9427
9428 // c();ˇ»
9429 }
9430 "});
9431
9432 // If a selection includes multiple comment prefixes, all lines are uncommented.
9433 cx.set_state(indoc! {"
9434 fn a() {
9435 // «a();
9436 /// b();
9437 //! c();ˇ»
9438 }
9439 "});
9440
9441 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9442
9443 cx.assert_editor_state(indoc! {"
9444 fn a() {
9445 «a();
9446 b();
9447 c();ˇ»
9448 }
9449 "});
9450}
9451
9452#[gpui::test]
9453async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
9454 init_test(cx, |_| {});
9455
9456 let language = Arc::new(Language::new(
9457 LanguageConfig {
9458 line_comments: vec!["// ".into()],
9459 ..Default::default()
9460 },
9461 Some(tree_sitter_rust::LANGUAGE.into()),
9462 ));
9463
9464 let mut cx = EditorTestContext::new(cx).await;
9465
9466 cx.language_registry().add(language.clone());
9467 cx.update_buffer(|buffer, cx| {
9468 buffer.set_language(Some(language), cx);
9469 });
9470
9471 let toggle_comments = &ToggleComments {
9472 advance_downwards: true,
9473 ignore_indent: false,
9474 };
9475
9476 // Single cursor on one line -> advance
9477 // Cursor moves horizontally 3 characters as well on non-blank line
9478 cx.set_state(indoc!(
9479 "fn a() {
9480 ˇdog();
9481 cat();
9482 }"
9483 ));
9484 cx.update_editor(|editor, window, cx| {
9485 editor.toggle_comments(toggle_comments, window, cx);
9486 });
9487 cx.assert_editor_state(indoc!(
9488 "fn a() {
9489 // dog();
9490 catˇ();
9491 }"
9492 ));
9493
9494 // Single selection on one line -> don't advance
9495 cx.set_state(indoc!(
9496 "fn a() {
9497 «dog()ˇ»;
9498 cat();
9499 }"
9500 ));
9501 cx.update_editor(|editor, window, cx| {
9502 editor.toggle_comments(toggle_comments, window, cx);
9503 });
9504 cx.assert_editor_state(indoc!(
9505 "fn a() {
9506 // «dog()ˇ»;
9507 cat();
9508 }"
9509 ));
9510
9511 // Multiple cursors on one line -> advance
9512 cx.set_state(indoc!(
9513 "fn a() {
9514 ˇdˇog();
9515 cat();
9516 }"
9517 ));
9518 cx.update_editor(|editor, window, cx| {
9519 editor.toggle_comments(toggle_comments, window, cx);
9520 });
9521 cx.assert_editor_state(indoc!(
9522 "fn a() {
9523 // dog();
9524 catˇ(ˇ);
9525 }"
9526 ));
9527
9528 // Multiple cursors on one line, with selection -> don't advance
9529 cx.set_state(indoc!(
9530 "fn a() {
9531 ˇdˇog«()ˇ»;
9532 cat();
9533 }"
9534 ));
9535 cx.update_editor(|editor, window, cx| {
9536 editor.toggle_comments(toggle_comments, window, cx);
9537 });
9538 cx.assert_editor_state(indoc!(
9539 "fn a() {
9540 // ˇdˇog«()ˇ»;
9541 cat();
9542 }"
9543 ));
9544
9545 // Single cursor on one line -> advance
9546 // Cursor moves to column 0 on blank line
9547 cx.set_state(indoc!(
9548 "fn a() {
9549 ˇdog();
9550
9551 cat();
9552 }"
9553 ));
9554 cx.update_editor(|editor, window, cx| {
9555 editor.toggle_comments(toggle_comments, window, cx);
9556 });
9557 cx.assert_editor_state(indoc!(
9558 "fn a() {
9559 // dog();
9560 ˇ
9561 cat();
9562 }"
9563 ));
9564
9565 // Single cursor on one line -> advance
9566 // Cursor starts and ends at column 0
9567 cx.set_state(indoc!(
9568 "fn a() {
9569 ˇ dog();
9570 cat();
9571 }"
9572 ));
9573 cx.update_editor(|editor, window, cx| {
9574 editor.toggle_comments(toggle_comments, window, cx);
9575 });
9576 cx.assert_editor_state(indoc!(
9577 "fn a() {
9578 // dog();
9579 ˇ cat();
9580 }"
9581 ));
9582}
9583
9584#[gpui::test]
9585async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9586 init_test(cx, |_| {});
9587
9588 let mut cx = EditorTestContext::new(cx).await;
9589
9590 let html_language = Arc::new(
9591 Language::new(
9592 LanguageConfig {
9593 name: "HTML".into(),
9594 block_comment: Some(("<!-- ".into(), " -->".into())),
9595 ..Default::default()
9596 },
9597 Some(tree_sitter_html::language()),
9598 )
9599 .with_injection_query(
9600 r#"
9601 (script_element
9602 (raw_text) @injection.content
9603 (#set! injection.language "javascript"))
9604 "#,
9605 )
9606 .unwrap(),
9607 );
9608
9609 let javascript_language = Arc::new(Language::new(
9610 LanguageConfig {
9611 name: "JavaScript".into(),
9612 line_comments: vec!["// ".into()],
9613 ..Default::default()
9614 },
9615 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9616 ));
9617
9618 cx.language_registry().add(html_language.clone());
9619 cx.language_registry().add(javascript_language.clone());
9620 cx.update_buffer(|buffer, cx| {
9621 buffer.set_language(Some(html_language), cx);
9622 });
9623
9624 // Toggle comments for empty selections
9625 cx.set_state(
9626 &r#"
9627 <p>A</p>ˇ
9628 <p>B</p>ˇ
9629 <p>C</p>ˇ
9630 "#
9631 .unindent(),
9632 );
9633 cx.update_editor(|editor, window, cx| {
9634 editor.toggle_comments(&ToggleComments::default(), window, cx)
9635 });
9636 cx.assert_editor_state(
9637 &r#"
9638 <!-- <p>A</p>ˇ -->
9639 <!-- <p>B</p>ˇ -->
9640 <!-- <p>C</p>ˇ -->
9641 "#
9642 .unindent(),
9643 );
9644 cx.update_editor(|editor, window, cx| {
9645 editor.toggle_comments(&ToggleComments::default(), window, cx)
9646 });
9647 cx.assert_editor_state(
9648 &r#"
9649 <p>A</p>ˇ
9650 <p>B</p>ˇ
9651 <p>C</p>ˇ
9652 "#
9653 .unindent(),
9654 );
9655
9656 // Toggle comments for mixture of empty and non-empty selections, where
9657 // multiple selections occupy a given line.
9658 cx.set_state(
9659 &r#"
9660 <p>A«</p>
9661 <p>ˇ»B</p>ˇ
9662 <p>C«</p>
9663 <p>ˇ»D</p>ˇ
9664 "#
9665 .unindent(),
9666 );
9667
9668 cx.update_editor(|editor, window, cx| {
9669 editor.toggle_comments(&ToggleComments::default(), window, cx)
9670 });
9671 cx.assert_editor_state(
9672 &r#"
9673 <!-- <p>A«</p>
9674 <p>ˇ»B</p>ˇ -->
9675 <!-- <p>C«</p>
9676 <p>ˇ»D</p>ˇ -->
9677 "#
9678 .unindent(),
9679 );
9680 cx.update_editor(|editor, window, cx| {
9681 editor.toggle_comments(&ToggleComments::default(), window, cx)
9682 });
9683 cx.assert_editor_state(
9684 &r#"
9685 <p>A«</p>
9686 <p>ˇ»B</p>ˇ
9687 <p>C«</p>
9688 <p>ˇ»D</p>ˇ
9689 "#
9690 .unindent(),
9691 );
9692
9693 // Toggle comments when different languages are active for different
9694 // selections.
9695 cx.set_state(
9696 &r#"
9697 ˇ<script>
9698 ˇvar x = new Y();
9699 ˇ</script>
9700 "#
9701 .unindent(),
9702 );
9703 cx.executor().run_until_parked();
9704 cx.update_editor(|editor, window, cx| {
9705 editor.toggle_comments(&ToggleComments::default(), window, cx)
9706 });
9707 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9708 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9709 cx.assert_editor_state(
9710 &r#"
9711 <!-- ˇ<script> -->
9712 // ˇvar x = new Y();
9713 // ˇ</script>
9714 "#
9715 .unindent(),
9716 );
9717}
9718
9719#[gpui::test]
9720fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9721 init_test(cx, |_| {});
9722
9723 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9724 let multibuffer = cx.new(|cx| {
9725 let mut multibuffer = MultiBuffer::new(ReadWrite);
9726 multibuffer.push_excerpts(
9727 buffer.clone(),
9728 [
9729 ExcerptRange {
9730 context: Point::new(0, 0)..Point::new(0, 4),
9731 primary: None,
9732 },
9733 ExcerptRange {
9734 context: Point::new(1, 0)..Point::new(1, 4),
9735 primary: None,
9736 },
9737 ],
9738 cx,
9739 );
9740 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9741 multibuffer
9742 });
9743
9744 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9745 editor.update_in(cx, |editor, window, cx| {
9746 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9747 editor.change_selections(None, window, cx, |s| {
9748 s.select_ranges([
9749 Point::new(0, 0)..Point::new(0, 0),
9750 Point::new(1, 0)..Point::new(1, 0),
9751 ])
9752 });
9753
9754 editor.handle_input("X", window, cx);
9755 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9756 assert_eq!(
9757 editor.selections.ranges(cx),
9758 [
9759 Point::new(0, 1)..Point::new(0, 1),
9760 Point::new(1, 1)..Point::new(1, 1),
9761 ]
9762 );
9763
9764 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9765 editor.change_selections(None, window, cx, |s| {
9766 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9767 });
9768 editor.backspace(&Default::default(), window, cx);
9769 assert_eq!(editor.text(cx), "Xa\nbbb");
9770 assert_eq!(
9771 editor.selections.ranges(cx),
9772 [Point::new(1, 0)..Point::new(1, 0)]
9773 );
9774
9775 editor.change_selections(None, window, cx, |s| {
9776 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9777 });
9778 editor.backspace(&Default::default(), window, cx);
9779 assert_eq!(editor.text(cx), "X\nbb");
9780 assert_eq!(
9781 editor.selections.ranges(cx),
9782 [Point::new(0, 1)..Point::new(0, 1)]
9783 );
9784 });
9785}
9786
9787#[gpui::test]
9788fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9789 init_test(cx, |_| {});
9790
9791 let markers = vec![('[', ']').into(), ('(', ')').into()];
9792 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9793 indoc! {"
9794 [aaaa
9795 (bbbb]
9796 cccc)",
9797 },
9798 markers.clone(),
9799 );
9800 let excerpt_ranges = markers.into_iter().map(|marker| {
9801 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9802 ExcerptRange {
9803 context,
9804 primary: None,
9805 }
9806 });
9807 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9808 let multibuffer = cx.new(|cx| {
9809 let mut multibuffer = MultiBuffer::new(ReadWrite);
9810 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9811 multibuffer
9812 });
9813
9814 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9815 editor.update_in(cx, |editor, window, cx| {
9816 let (expected_text, selection_ranges) = marked_text_ranges(
9817 indoc! {"
9818 aaaa
9819 bˇbbb
9820 bˇbbˇb
9821 cccc"
9822 },
9823 true,
9824 );
9825 assert_eq!(editor.text(cx), expected_text);
9826 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9827
9828 editor.handle_input("X", window, cx);
9829
9830 let (expected_text, expected_selections) = marked_text_ranges(
9831 indoc! {"
9832 aaaa
9833 bXˇbbXb
9834 bXˇbbXˇb
9835 cccc"
9836 },
9837 false,
9838 );
9839 assert_eq!(editor.text(cx), expected_text);
9840 assert_eq!(editor.selections.ranges(cx), expected_selections);
9841
9842 editor.newline(&Newline, window, cx);
9843 let (expected_text, expected_selections) = marked_text_ranges(
9844 indoc! {"
9845 aaaa
9846 bX
9847 ˇbbX
9848 b
9849 bX
9850 ˇbbX
9851 ˇb
9852 cccc"
9853 },
9854 false,
9855 );
9856 assert_eq!(editor.text(cx), expected_text);
9857 assert_eq!(editor.selections.ranges(cx), expected_selections);
9858 });
9859}
9860
9861#[gpui::test]
9862fn test_refresh_selections(cx: &mut TestAppContext) {
9863 init_test(cx, |_| {});
9864
9865 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9866 let mut excerpt1_id = None;
9867 let multibuffer = cx.new(|cx| {
9868 let mut multibuffer = MultiBuffer::new(ReadWrite);
9869 excerpt1_id = multibuffer
9870 .push_excerpts(
9871 buffer.clone(),
9872 [
9873 ExcerptRange {
9874 context: Point::new(0, 0)..Point::new(1, 4),
9875 primary: None,
9876 },
9877 ExcerptRange {
9878 context: Point::new(1, 0)..Point::new(2, 4),
9879 primary: None,
9880 },
9881 ],
9882 cx,
9883 )
9884 .into_iter()
9885 .next();
9886 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9887 multibuffer
9888 });
9889
9890 let editor = cx.add_window(|window, cx| {
9891 let mut editor = build_editor(multibuffer.clone(), window, cx);
9892 let snapshot = editor.snapshot(window, cx);
9893 editor.change_selections(None, window, cx, |s| {
9894 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9895 });
9896 editor.begin_selection(
9897 Point::new(2, 1).to_display_point(&snapshot),
9898 true,
9899 1,
9900 window,
9901 cx,
9902 );
9903 assert_eq!(
9904 editor.selections.ranges(cx),
9905 [
9906 Point::new(1, 3)..Point::new(1, 3),
9907 Point::new(2, 1)..Point::new(2, 1),
9908 ]
9909 );
9910 editor
9911 });
9912
9913 // Refreshing selections is a no-op when excerpts haven't changed.
9914 _ = editor.update(cx, |editor, window, cx| {
9915 editor.change_selections(None, window, cx, |s| s.refresh());
9916 assert_eq!(
9917 editor.selections.ranges(cx),
9918 [
9919 Point::new(1, 3)..Point::new(1, 3),
9920 Point::new(2, 1)..Point::new(2, 1),
9921 ]
9922 );
9923 });
9924
9925 multibuffer.update(cx, |multibuffer, cx| {
9926 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9927 });
9928 _ = editor.update(cx, |editor, window, cx| {
9929 // Removing an excerpt causes the first selection to become degenerate.
9930 assert_eq!(
9931 editor.selections.ranges(cx),
9932 [
9933 Point::new(0, 0)..Point::new(0, 0),
9934 Point::new(0, 1)..Point::new(0, 1)
9935 ]
9936 );
9937
9938 // Refreshing selections will relocate the first selection to the original buffer
9939 // location.
9940 editor.change_selections(None, window, cx, |s| s.refresh());
9941 assert_eq!(
9942 editor.selections.ranges(cx),
9943 [
9944 Point::new(0, 1)..Point::new(0, 1),
9945 Point::new(0, 3)..Point::new(0, 3)
9946 ]
9947 );
9948 assert!(editor.selections.pending_anchor().is_some());
9949 });
9950}
9951
9952#[gpui::test]
9953fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9954 init_test(cx, |_| {});
9955
9956 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9957 let mut excerpt1_id = None;
9958 let multibuffer = cx.new(|cx| {
9959 let mut multibuffer = MultiBuffer::new(ReadWrite);
9960 excerpt1_id = multibuffer
9961 .push_excerpts(
9962 buffer.clone(),
9963 [
9964 ExcerptRange {
9965 context: Point::new(0, 0)..Point::new(1, 4),
9966 primary: None,
9967 },
9968 ExcerptRange {
9969 context: Point::new(1, 0)..Point::new(2, 4),
9970 primary: None,
9971 },
9972 ],
9973 cx,
9974 )
9975 .into_iter()
9976 .next();
9977 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9978 multibuffer
9979 });
9980
9981 let editor = cx.add_window(|window, cx| {
9982 let mut editor = build_editor(multibuffer.clone(), window, cx);
9983 let snapshot = editor.snapshot(window, cx);
9984 editor.begin_selection(
9985 Point::new(1, 3).to_display_point(&snapshot),
9986 false,
9987 1,
9988 window,
9989 cx,
9990 );
9991 assert_eq!(
9992 editor.selections.ranges(cx),
9993 [Point::new(1, 3)..Point::new(1, 3)]
9994 );
9995 editor
9996 });
9997
9998 multibuffer.update(cx, |multibuffer, cx| {
9999 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10000 });
10001 _ = editor.update(cx, |editor, window, cx| {
10002 assert_eq!(
10003 editor.selections.ranges(cx),
10004 [Point::new(0, 0)..Point::new(0, 0)]
10005 );
10006
10007 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10008 editor.change_selections(None, window, cx, |s| s.refresh());
10009 assert_eq!(
10010 editor.selections.ranges(cx),
10011 [Point::new(0, 3)..Point::new(0, 3)]
10012 );
10013 assert!(editor.selections.pending_anchor().is_some());
10014 });
10015}
10016
10017#[gpui::test]
10018async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
10019 init_test(cx, |_| {});
10020
10021 let language = Arc::new(
10022 Language::new(
10023 LanguageConfig {
10024 brackets: BracketPairConfig {
10025 pairs: vec![
10026 BracketPair {
10027 start: "{".to_string(),
10028 end: "}".to_string(),
10029 close: true,
10030 surround: true,
10031 newline: true,
10032 },
10033 BracketPair {
10034 start: "/* ".to_string(),
10035 end: " */".to_string(),
10036 close: true,
10037 surround: true,
10038 newline: true,
10039 },
10040 ],
10041 ..Default::default()
10042 },
10043 ..Default::default()
10044 },
10045 Some(tree_sitter_rust::LANGUAGE.into()),
10046 )
10047 .with_indents_query("")
10048 .unwrap(),
10049 );
10050
10051 let text = concat!(
10052 "{ }\n", //
10053 " x\n", //
10054 " /* */\n", //
10055 "x\n", //
10056 "{{} }\n", //
10057 );
10058
10059 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10060 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10061 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10062 editor
10063 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10064 .await;
10065
10066 editor.update_in(cx, |editor, window, cx| {
10067 editor.change_selections(None, window, cx, |s| {
10068 s.select_display_ranges([
10069 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10070 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10071 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10072 ])
10073 });
10074 editor.newline(&Newline, window, cx);
10075
10076 assert_eq!(
10077 editor.buffer().read(cx).read(cx).text(),
10078 concat!(
10079 "{ \n", // Suppress rustfmt
10080 "\n", //
10081 "}\n", //
10082 " x\n", //
10083 " /* \n", //
10084 " \n", //
10085 " */\n", //
10086 "x\n", //
10087 "{{} \n", //
10088 "}\n", //
10089 )
10090 );
10091 });
10092}
10093
10094#[gpui::test]
10095fn test_highlighted_ranges(cx: &mut TestAppContext) {
10096 init_test(cx, |_| {});
10097
10098 let editor = cx.add_window(|window, cx| {
10099 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10100 build_editor(buffer.clone(), window, cx)
10101 });
10102
10103 _ = editor.update(cx, |editor, window, cx| {
10104 struct Type1;
10105 struct Type2;
10106
10107 let buffer = editor.buffer.read(cx).snapshot(cx);
10108
10109 let anchor_range =
10110 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10111
10112 editor.highlight_background::<Type1>(
10113 &[
10114 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10115 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10116 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10117 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10118 ],
10119 |_| Hsla::red(),
10120 cx,
10121 );
10122 editor.highlight_background::<Type2>(
10123 &[
10124 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10125 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10126 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10127 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10128 ],
10129 |_| Hsla::green(),
10130 cx,
10131 );
10132
10133 let snapshot = editor.snapshot(window, cx);
10134 let mut highlighted_ranges = editor.background_highlights_in_range(
10135 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10136 &snapshot,
10137 cx.theme().colors(),
10138 );
10139 // Enforce a consistent ordering based on color without relying on the ordering of the
10140 // highlight's `TypeId` which is non-executor.
10141 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10142 assert_eq!(
10143 highlighted_ranges,
10144 &[
10145 (
10146 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10147 Hsla::red(),
10148 ),
10149 (
10150 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10151 Hsla::red(),
10152 ),
10153 (
10154 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10155 Hsla::green(),
10156 ),
10157 (
10158 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10159 Hsla::green(),
10160 ),
10161 ]
10162 );
10163 assert_eq!(
10164 editor.background_highlights_in_range(
10165 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10166 &snapshot,
10167 cx.theme().colors(),
10168 ),
10169 &[(
10170 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10171 Hsla::red(),
10172 )]
10173 );
10174 });
10175}
10176
10177#[gpui::test]
10178async fn test_following(cx: &mut gpui::TestAppContext) {
10179 init_test(cx, |_| {});
10180
10181 let fs = FakeFs::new(cx.executor());
10182 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10183
10184 let buffer = project.update(cx, |project, cx| {
10185 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10186 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10187 });
10188 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10189 let follower = cx.update(|cx| {
10190 cx.open_window(
10191 WindowOptions {
10192 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10193 gpui::Point::new(px(0.), px(0.)),
10194 gpui::Point::new(px(10.), px(80.)),
10195 ))),
10196 ..Default::default()
10197 },
10198 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10199 )
10200 .unwrap()
10201 });
10202
10203 let is_still_following = Rc::new(RefCell::new(true));
10204 let follower_edit_event_count = Rc::new(RefCell::new(0));
10205 let pending_update = Rc::new(RefCell::new(None));
10206 let leader_entity = leader.root(cx).unwrap();
10207 let follower_entity = follower.root(cx).unwrap();
10208 _ = follower.update(cx, {
10209 let update = pending_update.clone();
10210 let is_still_following = is_still_following.clone();
10211 let follower_edit_event_count = follower_edit_event_count.clone();
10212 |_, window, cx| {
10213 cx.subscribe_in(
10214 &leader_entity,
10215 window,
10216 move |_, leader, event, window, cx| {
10217 leader.read(cx).add_event_to_update_proto(
10218 event,
10219 &mut update.borrow_mut(),
10220 window,
10221 cx,
10222 );
10223 },
10224 )
10225 .detach();
10226
10227 cx.subscribe_in(
10228 &follower_entity,
10229 window,
10230 move |_, _, event: &EditorEvent, _window, _cx| {
10231 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10232 *is_still_following.borrow_mut() = false;
10233 }
10234
10235 if let EditorEvent::BufferEdited = event {
10236 *follower_edit_event_count.borrow_mut() += 1;
10237 }
10238 },
10239 )
10240 .detach();
10241 }
10242 });
10243
10244 // Update the selections only
10245 _ = leader.update(cx, |leader, window, cx| {
10246 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10247 });
10248 follower
10249 .update(cx, |follower, window, cx| {
10250 follower.apply_update_proto(
10251 &project,
10252 pending_update.borrow_mut().take().unwrap(),
10253 window,
10254 cx,
10255 )
10256 })
10257 .unwrap()
10258 .await
10259 .unwrap();
10260 _ = follower.update(cx, |follower, _, cx| {
10261 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10262 });
10263 assert!(*is_still_following.borrow());
10264 assert_eq!(*follower_edit_event_count.borrow(), 0);
10265
10266 // Update the scroll position only
10267 _ = leader.update(cx, |leader, window, cx| {
10268 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10269 });
10270 follower
10271 .update(cx, |follower, window, cx| {
10272 follower.apply_update_proto(
10273 &project,
10274 pending_update.borrow_mut().take().unwrap(),
10275 window,
10276 cx,
10277 )
10278 })
10279 .unwrap()
10280 .await
10281 .unwrap();
10282 assert_eq!(
10283 follower
10284 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10285 .unwrap(),
10286 gpui::Point::new(1.5, 3.5)
10287 );
10288 assert!(*is_still_following.borrow());
10289 assert_eq!(*follower_edit_event_count.borrow(), 0);
10290
10291 // Update the selections and scroll position. The follower's scroll position is updated
10292 // via autoscroll, not via the leader's exact scroll position.
10293 _ = leader.update(cx, |leader, window, cx| {
10294 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10295 leader.request_autoscroll(Autoscroll::newest(), cx);
10296 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10297 });
10298 follower
10299 .update(cx, |follower, window, cx| {
10300 follower.apply_update_proto(
10301 &project,
10302 pending_update.borrow_mut().take().unwrap(),
10303 window,
10304 cx,
10305 )
10306 })
10307 .unwrap()
10308 .await
10309 .unwrap();
10310 _ = follower.update(cx, |follower, _, cx| {
10311 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10312 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10313 });
10314 assert!(*is_still_following.borrow());
10315
10316 // Creating a pending selection that precedes another selection
10317 _ = leader.update(cx, |leader, window, cx| {
10318 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10319 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10320 });
10321 follower
10322 .update(cx, |follower, window, cx| {
10323 follower.apply_update_proto(
10324 &project,
10325 pending_update.borrow_mut().take().unwrap(),
10326 window,
10327 cx,
10328 )
10329 })
10330 .unwrap()
10331 .await
10332 .unwrap();
10333 _ = follower.update(cx, |follower, _, cx| {
10334 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10335 });
10336 assert!(*is_still_following.borrow());
10337
10338 // Extend the pending selection so that it surrounds another selection
10339 _ = leader.update(cx, |leader, window, cx| {
10340 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10341 });
10342 follower
10343 .update(cx, |follower, window, cx| {
10344 follower.apply_update_proto(
10345 &project,
10346 pending_update.borrow_mut().take().unwrap(),
10347 window,
10348 cx,
10349 )
10350 })
10351 .unwrap()
10352 .await
10353 .unwrap();
10354 _ = follower.update(cx, |follower, _, cx| {
10355 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10356 });
10357
10358 // Scrolling locally breaks the follow
10359 _ = follower.update(cx, |follower, window, cx| {
10360 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10361 follower.set_scroll_anchor(
10362 ScrollAnchor {
10363 anchor: top_anchor,
10364 offset: gpui::Point::new(0.0, 0.5),
10365 },
10366 window,
10367 cx,
10368 );
10369 });
10370 assert!(!(*is_still_following.borrow()));
10371}
10372
10373#[gpui::test]
10374async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
10375 init_test(cx, |_| {});
10376
10377 let fs = FakeFs::new(cx.executor());
10378 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10379 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10380 let pane = workspace
10381 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10382 .unwrap();
10383
10384 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10385
10386 let leader = pane.update_in(cx, |_, window, cx| {
10387 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10388 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10389 });
10390
10391 // Start following the editor when it has no excerpts.
10392 let mut state_message =
10393 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10394 let workspace_entity = workspace.root(cx).unwrap();
10395 let follower_1 = cx
10396 .update_window(*workspace.deref(), |_, window, cx| {
10397 Editor::from_state_proto(
10398 workspace_entity,
10399 ViewId {
10400 creator: Default::default(),
10401 id: 0,
10402 },
10403 &mut state_message,
10404 window,
10405 cx,
10406 )
10407 })
10408 .unwrap()
10409 .unwrap()
10410 .await
10411 .unwrap();
10412
10413 let update_message = Rc::new(RefCell::new(None));
10414 follower_1.update_in(cx, {
10415 let update = update_message.clone();
10416 |_, window, cx| {
10417 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10418 leader.read(cx).add_event_to_update_proto(
10419 event,
10420 &mut update.borrow_mut(),
10421 window,
10422 cx,
10423 );
10424 })
10425 .detach();
10426 }
10427 });
10428
10429 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10430 (
10431 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10432 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10433 )
10434 });
10435
10436 // Insert some excerpts.
10437 leader.update(cx, |leader, cx| {
10438 leader.buffer.update(cx, |multibuffer, cx| {
10439 let excerpt_ids = multibuffer.push_excerpts(
10440 buffer_1.clone(),
10441 [
10442 ExcerptRange {
10443 context: 1..6,
10444 primary: None,
10445 },
10446 ExcerptRange {
10447 context: 12..15,
10448 primary: None,
10449 },
10450 ExcerptRange {
10451 context: 0..3,
10452 primary: None,
10453 },
10454 ],
10455 cx,
10456 );
10457 multibuffer.insert_excerpts_after(
10458 excerpt_ids[0],
10459 buffer_2.clone(),
10460 [
10461 ExcerptRange {
10462 context: 8..12,
10463 primary: None,
10464 },
10465 ExcerptRange {
10466 context: 0..6,
10467 primary: None,
10468 },
10469 ],
10470 cx,
10471 );
10472 });
10473 });
10474
10475 // Apply the update of adding the excerpts.
10476 follower_1
10477 .update_in(cx, |follower, window, cx| {
10478 follower.apply_update_proto(
10479 &project,
10480 update_message.borrow().clone().unwrap(),
10481 window,
10482 cx,
10483 )
10484 })
10485 .await
10486 .unwrap();
10487 assert_eq!(
10488 follower_1.update(cx, |editor, cx| editor.text(cx)),
10489 leader.update(cx, |editor, cx| editor.text(cx))
10490 );
10491 update_message.borrow_mut().take();
10492
10493 // Start following separately after it already has excerpts.
10494 let mut state_message =
10495 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10496 let workspace_entity = workspace.root(cx).unwrap();
10497 let follower_2 = cx
10498 .update_window(*workspace.deref(), |_, window, cx| {
10499 Editor::from_state_proto(
10500 workspace_entity,
10501 ViewId {
10502 creator: Default::default(),
10503 id: 0,
10504 },
10505 &mut state_message,
10506 window,
10507 cx,
10508 )
10509 })
10510 .unwrap()
10511 .unwrap()
10512 .await
10513 .unwrap();
10514 assert_eq!(
10515 follower_2.update(cx, |editor, cx| editor.text(cx)),
10516 leader.update(cx, |editor, cx| editor.text(cx))
10517 );
10518
10519 // Remove some excerpts.
10520 leader.update(cx, |leader, cx| {
10521 leader.buffer.update(cx, |multibuffer, cx| {
10522 let excerpt_ids = multibuffer.excerpt_ids();
10523 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10524 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10525 });
10526 });
10527
10528 // Apply the update of removing the excerpts.
10529 follower_1
10530 .update_in(cx, |follower, window, cx| {
10531 follower.apply_update_proto(
10532 &project,
10533 update_message.borrow().clone().unwrap(),
10534 window,
10535 cx,
10536 )
10537 })
10538 .await
10539 .unwrap();
10540 follower_2
10541 .update_in(cx, |follower, window, cx| {
10542 follower.apply_update_proto(
10543 &project,
10544 update_message.borrow().clone().unwrap(),
10545 window,
10546 cx,
10547 )
10548 })
10549 .await
10550 .unwrap();
10551 update_message.borrow_mut().take();
10552 assert_eq!(
10553 follower_1.update(cx, |editor, cx| editor.text(cx)),
10554 leader.update(cx, |editor, cx| editor.text(cx))
10555 );
10556}
10557
10558#[gpui::test]
10559async fn go_to_prev_overlapping_diagnostic(
10560 executor: BackgroundExecutor,
10561 cx: &mut gpui::TestAppContext,
10562) {
10563 init_test(cx, |_| {});
10564
10565 let mut cx = EditorTestContext::new(cx).await;
10566 let lsp_store =
10567 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10568
10569 cx.set_state(indoc! {"
10570 ˇfn func(abc def: i32) -> u32 {
10571 }
10572 "});
10573
10574 cx.update(|_, cx| {
10575 lsp_store.update(cx, |lsp_store, cx| {
10576 lsp_store
10577 .update_diagnostics(
10578 LanguageServerId(0),
10579 lsp::PublishDiagnosticsParams {
10580 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10581 version: None,
10582 diagnostics: vec![
10583 lsp::Diagnostic {
10584 range: lsp::Range::new(
10585 lsp::Position::new(0, 11),
10586 lsp::Position::new(0, 12),
10587 ),
10588 severity: Some(lsp::DiagnosticSeverity::ERROR),
10589 ..Default::default()
10590 },
10591 lsp::Diagnostic {
10592 range: lsp::Range::new(
10593 lsp::Position::new(0, 12),
10594 lsp::Position::new(0, 15),
10595 ),
10596 severity: Some(lsp::DiagnosticSeverity::ERROR),
10597 ..Default::default()
10598 },
10599 lsp::Diagnostic {
10600 range: lsp::Range::new(
10601 lsp::Position::new(0, 25),
10602 lsp::Position::new(0, 28),
10603 ),
10604 severity: Some(lsp::DiagnosticSeverity::ERROR),
10605 ..Default::default()
10606 },
10607 ],
10608 },
10609 &[],
10610 cx,
10611 )
10612 .unwrap()
10613 });
10614 });
10615
10616 executor.run_until_parked();
10617
10618 cx.update_editor(|editor, window, cx| {
10619 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10620 });
10621
10622 cx.assert_editor_state(indoc! {"
10623 fn func(abc def: i32) -> ˇu32 {
10624 }
10625 "});
10626
10627 cx.update_editor(|editor, window, cx| {
10628 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10629 });
10630
10631 cx.assert_editor_state(indoc! {"
10632 fn func(abc ˇdef: i32) -> u32 {
10633 }
10634 "});
10635
10636 cx.update_editor(|editor, window, cx| {
10637 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10638 });
10639
10640 cx.assert_editor_state(indoc! {"
10641 fn func(abcˇ def: i32) -> u32 {
10642 }
10643 "});
10644
10645 cx.update_editor(|editor, window, cx| {
10646 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10647 });
10648
10649 cx.assert_editor_state(indoc! {"
10650 fn func(abc def: i32) -> ˇu32 {
10651 }
10652 "});
10653}
10654
10655#[gpui::test]
10656async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10657 init_test(cx, |_| {});
10658
10659 let mut cx = EditorTestContext::new(cx).await;
10660
10661 cx.set_state(indoc! {"
10662 fn func(abˇc def: i32) -> u32 {
10663 }
10664 "});
10665 let lsp_store =
10666 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10667
10668 cx.update(|_, cx| {
10669 lsp_store.update(cx, |lsp_store, cx| {
10670 lsp_store.update_diagnostics(
10671 LanguageServerId(0),
10672 lsp::PublishDiagnosticsParams {
10673 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10674 version: None,
10675 diagnostics: vec![lsp::Diagnostic {
10676 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10677 severity: Some(lsp::DiagnosticSeverity::ERROR),
10678 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10679 ..Default::default()
10680 }],
10681 },
10682 &[],
10683 cx,
10684 )
10685 })
10686 }).unwrap();
10687 cx.run_until_parked();
10688 cx.update_editor(|editor, window, cx| {
10689 hover_popover::hover(editor, &Default::default(), window, cx)
10690 });
10691 cx.run_until_parked();
10692 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10693}
10694
10695#[gpui::test]
10696async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10697 init_test(cx, |_| {});
10698
10699 let mut cx = EditorTestContext::new(cx).await;
10700
10701 let diff_base = r#"
10702 use some::mod;
10703
10704 const A: u32 = 42;
10705
10706 fn main() {
10707 println!("hello");
10708
10709 println!("world");
10710 }
10711 "#
10712 .unindent();
10713
10714 // Edits are modified, removed, modified, added
10715 cx.set_state(
10716 &r#"
10717 use some::modified;
10718
10719 ˇ
10720 fn main() {
10721 println!("hello there");
10722
10723 println!("around the");
10724 println!("world");
10725 }
10726 "#
10727 .unindent(),
10728 );
10729
10730 cx.set_diff_base(&diff_base);
10731 executor.run_until_parked();
10732
10733 cx.update_editor(|editor, window, cx| {
10734 //Wrap around the bottom of the buffer
10735 for _ in 0..3 {
10736 editor.go_to_next_hunk(&GoToHunk, window, cx);
10737 }
10738 });
10739
10740 cx.assert_editor_state(
10741 &r#"
10742 ˇuse some::modified;
10743
10744
10745 fn main() {
10746 println!("hello there");
10747
10748 println!("around the");
10749 println!("world");
10750 }
10751 "#
10752 .unindent(),
10753 );
10754
10755 cx.update_editor(|editor, window, cx| {
10756 //Wrap around the top of the buffer
10757 for _ in 0..2 {
10758 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10759 }
10760 });
10761
10762 cx.assert_editor_state(
10763 &r#"
10764 use some::modified;
10765
10766
10767 fn main() {
10768 ˇ println!("hello there");
10769
10770 println!("around the");
10771 println!("world");
10772 }
10773 "#
10774 .unindent(),
10775 );
10776
10777 cx.update_editor(|editor, window, cx| {
10778 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10779 });
10780
10781 cx.assert_editor_state(
10782 &r#"
10783 use some::modified;
10784
10785 ˇ
10786 fn main() {
10787 println!("hello there");
10788
10789 println!("around the");
10790 println!("world");
10791 }
10792 "#
10793 .unindent(),
10794 );
10795
10796 cx.update_editor(|editor, window, cx| {
10797 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10798 });
10799
10800 cx.assert_editor_state(
10801 &r#"
10802 ˇuse some::modified;
10803
10804
10805 fn main() {
10806 println!("hello there");
10807
10808 println!("around the");
10809 println!("world");
10810 }
10811 "#
10812 .unindent(),
10813 );
10814
10815 cx.update_editor(|editor, window, cx| {
10816 for _ in 0..2 {
10817 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10818 }
10819 });
10820
10821 cx.assert_editor_state(
10822 &r#"
10823 use some::modified;
10824
10825
10826 fn main() {
10827 ˇ println!("hello there");
10828
10829 println!("around the");
10830 println!("world");
10831 }
10832 "#
10833 .unindent(),
10834 );
10835
10836 cx.update_editor(|editor, window, cx| {
10837 editor.fold(&Fold, window, cx);
10838 });
10839
10840 cx.update_editor(|editor, window, cx| {
10841 editor.go_to_next_hunk(&GoToHunk, window, cx);
10842 });
10843
10844 cx.assert_editor_state(
10845 &r#"
10846 ˇuse some::modified;
10847
10848
10849 fn main() {
10850 println!("hello there");
10851
10852 println!("around the");
10853 println!("world");
10854 }
10855 "#
10856 .unindent(),
10857 );
10858}
10859
10860#[test]
10861fn test_split_words() {
10862 fn split(text: &str) -> Vec<&str> {
10863 split_words(text).collect()
10864 }
10865
10866 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10867 assert_eq!(split("hello_world"), &["hello_", "world"]);
10868 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10869 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10870 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10871 assert_eq!(split("helloworld"), &["helloworld"]);
10872
10873 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10874}
10875
10876#[gpui::test]
10877async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10878 init_test(cx, |_| {});
10879
10880 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10881 let mut assert = |before, after| {
10882 let _state_context = cx.set_state(before);
10883 cx.run_until_parked();
10884 cx.update_editor(|editor, window, cx| {
10885 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
10886 });
10887 cx.assert_editor_state(after);
10888 };
10889
10890 // Outside bracket jumps to outside of matching bracket
10891 assert("console.logˇ(var);", "console.log(var)ˇ;");
10892 assert("console.log(var)ˇ;", "console.logˇ(var);");
10893
10894 // Inside bracket jumps to inside of matching bracket
10895 assert("console.log(ˇvar);", "console.log(varˇ);");
10896 assert("console.log(varˇ);", "console.log(ˇvar);");
10897
10898 // When outside a bracket and inside, favor jumping to the inside bracket
10899 assert(
10900 "console.log('foo', [1, 2, 3]ˇ);",
10901 "console.log(ˇ'foo', [1, 2, 3]);",
10902 );
10903 assert(
10904 "console.log(ˇ'foo', [1, 2, 3]);",
10905 "console.log('foo', [1, 2, 3]ˇ);",
10906 );
10907
10908 // Bias forward if two options are equally likely
10909 assert(
10910 "let result = curried_fun()ˇ();",
10911 "let result = curried_fun()()ˇ;",
10912 );
10913
10914 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10915 assert(
10916 indoc! {"
10917 function test() {
10918 console.log('test')ˇ
10919 }"},
10920 indoc! {"
10921 function test() {
10922 console.logˇ('test')
10923 }"},
10924 );
10925}
10926
10927#[gpui::test]
10928async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10929 init_test(cx, |_| {});
10930
10931 let fs = FakeFs::new(cx.executor());
10932 fs.insert_tree(
10933 path!("/a"),
10934 json!({
10935 "main.rs": "fn main() { let a = 5; }",
10936 "other.rs": "// Test file",
10937 }),
10938 )
10939 .await;
10940 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10941
10942 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10943 language_registry.add(Arc::new(Language::new(
10944 LanguageConfig {
10945 name: "Rust".into(),
10946 matcher: LanguageMatcher {
10947 path_suffixes: vec!["rs".to_string()],
10948 ..Default::default()
10949 },
10950 brackets: BracketPairConfig {
10951 pairs: vec![BracketPair {
10952 start: "{".to_string(),
10953 end: "}".to_string(),
10954 close: true,
10955 surround: true,
10956 newline: true,
10957 }],
10958 disabled_scopes_by_bracket_ix: Vec::new(),
10959 },
10960 ..Default::default()
10961 },
10962 Some(tree_sitter_rust::LANGUAGE.into()),
10963 )));
10964 let mut fake_servers = language_registry.register_fake_lsp(
10965 "Rust",
10966 FakeLspAdapter {
10967 capabilities: lsp::ServerCapabilities {
10968 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10969 first_trigger_character: "{".to_string(),
10970 more_trigger_character: None,
10971 }),
10972 ..Default::default()
10973 },
10974 ..Default::default()
10975 },
10976 );
10977
10978 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10979
10980 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10981
10982 let worktree_id = workspace
10983 .update(cx, |workspace, _, cx| {
10984 workspace.project().update(cx, |project, cx| {
10985 project.worktrees(cx).next().unwrap().read(cx).id()
10986 })
10987 })
10988 .unwrap();
10989
10990 let buffer = project
10991 .update(cx, |project, cx| {
10992 project.open_local_buffer(path!("/a/main.rs"), cx)
10993 })
10994 .await
10995 .unwrap();
10996 let editor_handle = workspace
10997 .update(cx, |workspace, window, cx| {
10998 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
10999 })
11000 .unwrap()
11001 .await
11002 .unwrap()
11003 .downcast::<Editor>()
11004 .unwrap();
11005
11006 cx.executor().start_waiting();
11007 let fake_server = fake_servers.next().await.unwrap();
11008
11009 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11010 assert_eq!(
11011 params.text_document_position.text_document.uri,
11012 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11013 );
11014 assert_eq!(
11015 params.text_document_position.position,
11016 lsp::Position::new(0, 21),
11017 );
11018
11019 Ok(Some(vec![lsp::TextEdit {
11020 new_text: "]".to_string(),
11021 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11022 }]))
11023 });
11024
11025 editor_handle.update_in(cx, |editor, window, cx| {
11026 window.focus(&editor.focus_handle(cx));
11027 editor.change_selections(None, window, cx, |s| {
11028 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11029 });
11030 editor.handle_input("{", window, cx);
11031 });
11032
11033 cx.executor().run_until_parked();
11034
11035 buffer.update(cx, |buffer, _| {
11036 assert_eq!(
11037 buffer.text(),
11038 "fn main() { let a = {5}; }",
11039 "No extra braces from on type formatting should appear in the buffer"
11040 )
11041 });
11042}
11043
11044#[gpui::test]
11045async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
11046 init_test(cx, |_| {});
11047
11048 let fs = FakeFs::new(cx.executor());
11049 fs.insert_tree(
11050 path!("/a"),
11051 json!({
11052 "main.rs": "fn main() { let a = 5; }",
11053 "other.rs": "// Test file",
11054 }),
11055 )
11056 .await;
11057
11058 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11059
11060 let server_restarts = Arc::new(AtomicUsize::new(0));
11061 let closure_restarts = Arc::clone(&server_restarts);
11062 let language_server_name = "test language server";
11063 let language_name: LanguageName = "Rust".into();
11064
11065 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11066 language_registry.add(Arc::new(Language::new(
11067 LanguageConfig {
11068 name: language_name.clone(),
11069 matcher: LanguageMatcher {
11070 path_suffixes: vec!["rs".to_string()],
11071 ..Default::default()
11072 },
11073 ..Default::default()
11074 },
11075 Some(tree_sitter_rust::LANGUAGE.into()),
11076 )));
11077 let mut fake_servers = language_registry.register_fake_lsp(
11078 "Rust",
11079 FakeLspAdapter {
11080 name: language_server_name,
11081 initialization_options: Some(json!({
11082 "testOptionValue": true
11083 })),
11084 initializer: Some(Box::new(move |fake_server| {
11085 let task_restarts = Arc::clone(&closure_restarts);
11086 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11087 task_restarts.fetch_add(1, atomic::Ordering::Release);
11088 futures::future::ready(Ok(()))
11089 });
11090 })),
11091 ..Default::default()
11092 },
11093 );
11094
11095 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11096 let _buffer = project
11097 .update(cx, |project, cx| {
11098 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11099 })
11100 .await
11101 .unwrap();
11102 let _fake_server = fake_servers.next().await.unwrap();
11103 update_test_language_settings(cx, |language_settings| {
11104 language_settings.languages.insert(
11105 language_name.clone(),
11106 LanguageSettingsContent {
11107 tab_size: NonZeroU32::new(8),
11108 ..Default::default()
11109 },
11110 );
11111 });
11112 cx.executor().run_until_parked();
11113 assert_eq!(
11114 server_restarts.load(atomic::Ordering::Acquire),
11115 0,
11116 "Should not restart LSP server on an unrelated change"
11117 );
11118
11119 update_test_project_settings(cx, |project_settings| {
11120 project_settings.lsp.insert(
11121 "Some other server name".into(),
11122 LspSettings {
11123 binary: None,
11124 settings: None,
11125 initialization_options: Some(json!({
11126 "some other init value": false
11127 })),
11128 },
11129 );
11130 });
11131 cx.executor().run_until_parked();
11132 assert_eq!(
11133 server_restarts.load(atomic::Ordering::Acquire),
11134 0,
11135 "Should not restart LSP server on an unrelated LSP settings change"
11136 );
11137
11138 update_test_project_settings(cx, |project_settings| {
11139 project_settings.lsp.insert(
11140 language_server_name.into(),
11141 LspSettings {
11142 binary: None,
11143 settings: None,
11144 initialization_options: Some(json!({
11145 "anotherInitValue": false
11146 })),
11147 },
11148 );
11149 });
11150 cx.executor().run_until_parked();
11151 assert_eq!(
11152 server_restarts.load(atomic::Ordering::Acquire),
11153 1,
11154 "Should restart LSP server on a related LSP settings change"
11155 );
11156
11157 update_test_project_settings(cx, |project_settings| {
11158 project_settings.lsp.insert(
11159 language_server_name.into(),
11160 LspSettings {
11161 binary: None,
11162 settings: None,
11163 initialization_options: Some(json!({
11164 "anotherInitValue": false
11165 })),
11166 },
11167 );
11168 });
11169 cx.executor().run_until_parked();
11170 assert_eq!(
11171 server_restarts.load(atomic::Ordering::Acquire),
11172 1,
11173 "Should not restart LSP server on a related LSP settings change that is the same"
11174 );
11175
11176 update_test_project_settings(cx, |project_settings| {
11177 project_settings.lsp.insert(
11178 language_server_name.into(),
11179 LspSettings {
11180 binary: None,
11181 settings: None,
11182 initialization_options: None,
11183 },
11184 );
11185 });
11186 cx.executor().run_until_parked();
11187 assert_eq!(
11188 server_restarts.load(atomic::Ordering::Acquire),
11189 2,
11190 "Should restart LSP server on another related LSP settings change"
11191 );
11192}
11193
11194#[gpui::test]
11195async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
11196 init_test(cx, |_| {});
11197
11198 let mut cx = EditorLspTestContext::new_rust(
11199 lsp::ServerCapabilities {
11200 completion_provider: Some(lsp::CompletionOptions {
11201 trigger_characters: Some(vec![".".to_string()]),
11202 resolve_provider: Some(true),
11203 ..Default::default()
11204 }),
11205 ..Default::default()
11206 },
11207 cx,
11208 )
11209 .await;
11210
11211 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11212 cx.simulate_keystroke(".");
11213 let completion_item = lsp::CompletionItem {
11214 label: "some".into(),
11215 kind: Some(lsp::CompletionItemKind::SNIPPET),
11216 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11217 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11218 kind: lsp::MarkupKind::Markdown,
11219 value: "```rust\nSome(2)\n```".to_string(),
11220 })),
11221 deprecated: Some(false),
11222 sort_text: Some("fffffff2".to_string()),
11223 filter_text: Some("some".to_string()),
11224 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11225 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11226 range: lsp::Range {
11227 start: lsp::Position {
11228 line: 0,
11229 character: 22,
11230 },
11231 end: lsp::Position {
11232 line: 0,
11233 character: 22,
11234 },
11235 },
11236 new_text: "Some(2)".to_string(),
11237 })),
11238 additional_text_edits: Some(vec![lsp::TextEdit {
11239 range: lsp::Range {
11240 start: lsp::Position {
11241 line: 0,
11242 character: 20,
11243 },
11244 end: lsp::Position {
11245 line: 0,
11246 character: 22,
11247 },
11248 },
11249 new_text: "".to_string(),
11250 }]),
11251 ..Default::default()
11252 };
11253
11254 let closure_completion_item = completion_item.clone();
11255 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11256 let task_completion_item = closure_completion_item.clone();
11257 async move {
11258 Ok(Some(lsp::CompletionResponse::Array(vec![
11259 task_completion_item,
11260 ])))
11261 }
11262 });
11263
11264 request.next().await;
11265
11266 cx.condition(|editor, _| editor.context_menu_visible())
11267 .await;
11268 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11269 editor
11270 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11271 .unwrap()
11272 });
11273 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11274
11275 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11276 let task_completion_item = completion_item.clone();
11277 async move { Ok(task_completion_item) }
11278 })
11279 .next()
11280 .await
11281 .unwrap();
11282 apply_additional_edits.await.unwrap();
11283 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11284}
11285
11286#[gpui::test]
11287async fn test_completions_resolve_updates_labels_if_filter_text_matches(
11288 cx: &mut gpui::TestAppContext,
11289) {
11290 init_test(cx, |_| {});
11291
11292 let mut cx = EditorLspTestContext::new_rust(
11293 lsp::ServerCapabilities {
11294 completion_provider: Some(lsp::CompletionOptions {
11295 trigger_characters: Some(vec![".".to_string()]),
11296 resolve_provider: Some(true),
11297 ..Default::default()
11298 }),
11299 ..Default::default()
11300 },
11301 cx,
11302 )
11303 .await;
11304
11305 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11306 cx.simulate_keystroke(".");
11307
11308 let item1 = lsp::CompletionItem {
11309 label: "method id()".to_string(),
11310 filter_text: Some("id".to_string()),
11311 detail: None,
11312 documentation: None,
11313 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11314 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11315 new_text: ".id".to_string(),
11316 })),
11317 ..lsp::CompletionItem::default()
11318 };
11319
11320 let item2 = lsp::CompletionItem {
11321 label: "other".to_string(),
11322 filter_text: Some("other".to_string()),
11323 detail: None,
11324 documentation: None,
11325 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11326 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11327 new_text: ".other".to_string(),
11328 })),
11329 ..lsp::CompletionItem::default()
11330 };
11331
11332 let item1 = item1.clone();
11333 cx.handle_request::<lsp::request::Completion, _, _>({
11334 let item1 = item1.clone();
11335 move |_, _, _| {
11336 let item1 = item1.clone();
11337 let item2 = item2.clone();
11338 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11339 }
11340 })
11341 .next()
11342 .await;
11343
11344 cx.condition(|editor, _| editor.context_menu_visible())
11345 .await;
11346 cx.update_editor(|editor, _, _| {
11347 let context_menu = editor.context_menu.borrow_mut();
11348 let context_menu = context_menu
11349 .as_ref()
11350 .expect("Should have the context menu deployed");
11351 match context_menu {
11352 CodeContextMenu::Completions(completions_menu) => {
11353 let completions = completions_menu.completions.borrow_mut();
11354 assert_eq!(
11355 completions
11356 .iter()
11357 .map(|completion| &completion.label.text)
11358 .collect::<Vec<_>>(),
11359 vec!["method id()", "other"]
11360 )
11361 }
11362 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11363 }
11364 });
11365
11366 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11367 let item1 = item1.clone();
11368 move |_, item_to_resolve, _| {
11369 let item1 = item1.clone();
11370 async move {
11371 if item1 == item_to_resolve {
11372 Ok(lsp::CompletionItem {
11373 label: "method id()".to_string(),
11374 filter_text: Some("id".to_string()),
11375 detail: Some("Now resolved!".to_string()),
11376 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11377 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11378 range: lsp::Range::new(
11379 lsp::Position::new(0, 22),
11380 lsp::Position::new(0, 22),
11381 ),
11382 new_text: ".id".to_string(),
11383 })),
11384 ..lsp::CompletionItem::default()
11385 })
11386 } else {
11387 Ok(item_to_resolve)
11388 }
11389 }
11390 }
11391 })
11392 .next()
11393 .await
11394 .unwrap();
11395 cx.run_until_parked();
11396
11397 cx.update_editor(|editor, window, cx| {
11398 editor.context_menu_next(&Default::default(), window, cx);
11399 });
11400
11401 cx.update_editor(|editor, _, _| {
11402 let context_menu = editor.context_menu.borrow_mut();
11403 let context_menu = context_menu
11404 .as_ref()
11405 .expect("Should have the context menu deployed");
11406 match context_menu {
11407 CodeContextMenu::Completions(completions_menu) => {
11408 let completions = completions_menu.completions.borrow_mut();
11409 assert_eq!(
11410 completions
11411 .iter()
11412 .map(|completion| &completion.label.text)
11413 .collect::<Vec<_>>(),
11414 vec!["method id() Now resolved!", "other"],
11415 "Should update first completion label, but not second as the filter text did not match."
11416 );
11417 }
11418 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11419 }
11420 });
11421}
11422
11423#[gpui::test]
11424async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
11425 init_test(cx, |_| {});
11426
11427 let mut cx = EditorLspTestContext::new_rust(
11428 lsp::ServerCapabilities {
11429 completion_provider: Some(lsp::CompletionOptions {
11430 trigger_characters: Some(vec![".".to_string()]),
11431 resolve_provider: Some(true),
11432 ..Default::default()
11433 }),
11434 ..Default::default()
11435 },
11436 cx,
11437 )
11438 .await;
11439
11440 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11441 cx.simulate_keystroke(".");
11442
11443 let unresolved_item_1 = lsp::CompletionItem {
11444 label: "id".to_string(),
11445 filter_text: Some("id".to_string()),
11446 detail: None,
11447 documentation: None,
11448 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11449 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11450 new_text: ".id".to_string(),
11451 })),
11452 ..lsp::CompletionItem::default()
11453 };
11454 let resolved_item_1 = lsp::CompletionItem {
11455 additional_text_edits: Some(vec![lsp::TextEdit {
11456 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11457 new_text: "!!".to_string(),
11458 }]),
11459 ..unresolved_item_1.clone()
11460 };
11461 let unresolved_item_2 = lsp::CompletionItem {
11462 label: "other".to_string(),
11463 filter_text: Some("other".to_string()),
11464 detail: None,
11465 documentation: None,
11466 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11467 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11468 new_text: ".other".to_string(),
11469 })),
11470 ..lsp::CompletionItem::default()
11471 };
11472 let resolved_item_2 = lsp::CompletionItem {
11473 additional_text_edits: Some(vec![lsp::TextEdit {
11474 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11475 new_text: "??".to_string(),
11476 }]),
11477 ..unresolved_item_2.clone()
11478 };
11479
11480 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11481 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11482 cx.lsp
11483 .server
11484 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11485 let unresolved_item_1 = unresolved_item_1.clone();
11486 let resolved_item_1 = resolved_item_1.clone();
11487 let unresolved_item_2 = unresolved_item_2.clone();
11488 let resolved_item_2 = resolved_item_2.clone();
11489 let resolve_requests_1 = resolve_requests_1.clone();
11490 let resolve_requests_2 = resolve_requests_2.clone();
11491 move |unresolved_request, _| {
11492 let unresolved_item_1 = unresolved_item_1.clone();
11493 let resolved_item_1 = resolved_item_1.clone();
11494 let unresolved_item_2 = unresolved_item_2.clone();
11495 let resolved_item_2 = resolved_item_2.clone();
11496 let resolve_requests_1 = resolve_requests_1.clone();
11497 let resolve_requests_2 = resolve_requests_2.clone();
11498 async move {
11499 if unresolved_request == unresolved_item_1 {
11500 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11501 Ok(resolved_item_1.clone())
11502 } else if unresolved_request == unresolved_item_2 {
11503 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11504 Ok(resolved_item_2.clone())
11505 } else {
11506 panic!("Unexpected completion item {unresolved_request:?}")
11507 }
11508 }
11509 }
11510 })
11511 .detach();
11512
11513 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11514 let unresolved_item_1 = unresolved_item_1.clone();
11515 let unresolved_item_2 = unresolved_item_2.clone();
11516 async move {
11517 Ok(Some(lsp::CompletionResponse::Array(vec![
11518 unresolved_item_1,
11519 unresolved_item_2,
11520 ])))
11521 }
11522 })
11523 .next()
11524 .await;
11525
11526 cx.condition(|editor, _| editor.context_menu_visible())
11527 .await;
11528 cx.update_editor(|editor, _, _| {
11529 let context_menu = editor.context_menu.borrow_mut();
11530 let context_menu = context_menu
11531 .as_ref()
11532 .expect("Should have the context menu deployed");
11533 match context_menu {
11534 CodeContextMenu::Completions(completions_menu) => {
11535 let completions = completions_menu.completions.borrow_mut();
11536 assert_eq!(
11537 completions
11538 .iter()
11539 .map(|completion| &completion.label.text)
11540 .collect::<Vec<_>>(),
11541 vec!["id", "other"]
11542 )
11543 }
11544 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11545 }
11546 });
11547 cx.run_until_parked();
11548
11549 cx.update_editor(|editor, window, cx| {
11550 editor.context_menu_next(&ContextMenuNext, window, cx);
11551 });
11552 cx.run_until_parked();
11553 cx.update_editor(|editor, window, cx| {
11554 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11555 });
11556 cx.run_until_parked();
11557 cx.update_editor(|editor, window, cx| {
11558 editor.context_menu_next(&ContextMenuNext, window, cx);
11559 });
11560 cx.run_until_parked();
11561 cx.update_editor(|editor, window, cx| {
11562 editor
11563 .compose_completion(&ComposeCompletion::default(), window, cx)
11564 .expect("No task returned")
11565 })
11566 .await
11567 .expect("Completion failed");
11568 cx.run_until_parked();
11569
11570 cx.update_editor(|editor, _, cx| {
11571 assert_eq!(
11572 resolve_requests_1.load(atomic::Ordering::Acquire),
11573 1,
11574 "Should always resolve once despite multiple selections"
11575 );
11576 assert_eq!(
11577 resolve_requests_2.load(atomic::Ordering::Acquire),
11578 1,
11579 "Should always resolve once after multiple selections and applying the completion"
11580 );
11581 assert_eq!(
11582 editor.text(cx),
11583 "fn main() { let a = ??.other; }",
11584 "Should use resolved data when applying the completion"
11585 );
11586 });
11587}
11588
11589#[gpui::test]
11590async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
11591 init_test(cx, |_| {});
11592
11593 let item_0 = lsp::CompletionItem {
11594 label: "abs".into(),
11595 insert_text: Some("abs".into()),
11596 data: Some(json!({ "very": "special"})),
11597 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11598 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11599 lsp::InsertReplaceEdit {
11600 new_text: "abs".to_string(),
11601 insert: lsp::Range::default(),
11602 replace: lsp::Range::default(),
11603 },
11604 )),
11605 ..lsp::CompletionItem::default()
11606 };
11607 let items = iter::once(item_0.clone())
11608 .chain((11..51).map(|i| lsp::CompletionItem {
11609 label: format!("item_{}", i),
11610 insert_text: Some(format!("item_{}", i)),
11611 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11612 ..lsp::CompletionItem::default()
11613 }))
11614 .collect::<Vec<_>>();
11615
11616 let default_commit_characters = vec!["?".to_string()];
11617 let default_data = json!({ "default": "data"});
11618 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11619 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11620 let default_edit_range = lsp::Range {
11621 start: lsp::Position {
11622 line: 0,
11623 character: 5,
11624 },
11625 end: lsp::Position {
11626 line: 0,
11627 character: 5,
11628 },
11629 };
11630
11631 let item_0_out = lsp::CompletionItem {
11632 commit_characters: Some(default_commit_characters.clone()),
11633 insert_text_format: Some(default_insert_text_format),
11634 ..item_0
11635 };
11636 let items_out = iter::once(item_0_out)
11637 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11638 commit_characters: Some(default_commit_characters.clone()),
11639 data: Some(default_data.clone()),
11640 insert_text_mode: Some(default_insert_text_mode),
11641 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11642 range: default_edit_range,
11643 new_text: item.label.clone(),
11644 })),
11645 ..item.clone()
11646 }))
11647 .collect::<Vec<lsp::CompletionItem>>();
11648
11649 let mut cx = EditorLspTestContext::new_rust(
11650 lsp::ServerCapabilities {
11651 completion_provider: Some(lsp::CompletionOptions {
11652 trigger_characters: Some(vec![".".to_string()]),
11653 resolve_provider: Some(true),
11654 ..Default::default()
11655 }),
11656 ..Default::default()
11657 },
11658 cx,
11659 )
11660 .await;
11661
11662 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11663 cx.simulate_keystroke(".");
11664
11665 let completion_data = default_data.clone();
11666 let completion_characters = default_commit_characters.clone();
11667 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11668 let default_data = completion_data.clone();
11669 let default_commit_characters = completion_characters.clone();
11670 let items = items.clone();
11671 async move {
11672 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11673 items,
11674 item_defaults: Some(lsp::CompletionListItemDefaults {
11675 data: Some(default_data.clone()),
11676 commit_characters: Some(default_commit_characters.clone()),
11677 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11678 default_edit_range,
11679 )),
11680 insert_text_format: Some(default_insert_text_format),
11681 insert_text_mode: Some(default_insert_text_mode),
11682 }),
11683 ..lsp::CompletionList::default()
11684 })))
11685 }
11686 })
11687 .next()
11688 .await;
11689
11690 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11691 cx.lsp
11692 .server
11693 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11694 let closure_resolved_items = resolved_items.clone();
11695 move |item_to_resolve, _| {
11696 let closure_resolved_items = closure_resolved_items.clone();
11697 async move {
11698 closure_resolved_items.lock().push(item_to_resolve.clone());
11699 Ok(item_to_resolve)
11700 }
11701 }
11702 })
11703 .detach();
11704
11705 cx.condition(|editor, _| editor.context_menu_visible())
11706 .await;
11707 cx.run_until_parked();
11708 cx.update_editor(|editor, _, _| {
11709 let menu = editor.context_menu.borrow_mut();
11710 match menu.as_ref().expect("should have the completions menu") {
11711 CodeContextMenu::Completions(completions_menu) => {
11712 assert_eq!(
11713 completions_menu
11714 .entries
11715 .borrow()
11716 .iter()
11717 .map(|mat| mat.string.clone())
11718 .collect::<Vec<String>>(),
11719 items_out
11720 .iter()
11721 .map(|completion| completion.label.clone())
11722 .collect::<Vec<String>>()
11723 );
11724 }
11725 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11726 }
11727 });
11728 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11729 // with 4 from the end.
11730 assert_eq!(
11731 *resolved_items.lock(),
11732 [
11733 &items_out[0..16],
11734 &items_out[items_out.len() - 4..items_out.len()]
11735 ]
11736 .concat()
11737 .iter()
11738 .cloned()
11739 .collect::<Vec<lsp::CompletionItem>>()
11740 );
11741 resolved_items.lock().clear();
11742
11743 cx.update_editor(|editor, window, cx| {
11744 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11745 });
11746 cx.run_until_parked();
11747 // Completions that have already been resolved are skipped.
11748 assert_eq!(
11749 *resolved_items.lock(),
11750 items_out[items_out.len() - 16..items_out.len() - 4]
11751 .iter()
11752 .cloned()
11753 .collect::<Vec<lsp::CompletionItem>>()
11754 );
11755 resolved_items.lock().clear();
11756}
11757
11758#[gpui::test]
11759async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11760 init_test(cx, |_| {});
11761
11762 let mut cx = EditorLspTestContext::new(
11763 Language::new(
11764 LanguageConfig {
11765 matcher: LanguageMatcher {
11766 path_suffixes: vec!["jsx".into()],
11767 ..Default::default()
11768 },
11769 overrides: [(
11770 "element".into(),
11771 LanguageConfigOverride {
11772 word_characters: Override::Set(['-'].into_iter().collect()),
11773 ..Default::default()
11774 },
11775 )]
11776 .into_iter()
11777 .collect(),
11778 ..Default::default()
11779 },
11780 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11781 )
11782 .with_override_query("(jsx_self_closing_element) @element")
11783 .unwrap(),
11784 lsp::ServerCapabilities {
11785 completion_provider: Some(lsp::CompletionOptions {
11786 trigger_characters: Some(vec![":".to_string()]),
11787 ..Default::default()
11788 }),
11789 ..Default::default()
11790 },
11791 cx,
11792 )
11793 .await;
11794
11795 cx.lsp
11796 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11797 Ok(Some(lsp::CompletionResponse::Array(vec![
11798 lsp::CompletionItem {
11799 label: "bg-blue".into(),
11800 ..Default::default()
11801 },
11802 lsp::CompletionItem {
11803 label: "bg-red".into(),
11804 ..Default::default()
11805 },
11806 lsp::CompletionItem {
11807 label: "bg-yellow".into(),
11808 ..Default::default()
11809 },
11810 ])))
11811 });
11812
11813 cx.set_state(r#"<p class="bgˇ" />"#);
11814
11815 // Trigger completion when typing a dash, because the dash is an extra
11816 // word character in the 'element' scope, which contains the cursor.
11817 cx.simulate_keystroke("-");
11818 cx.executor().run_until_parked();
11819 cx.update_editor(|editor, _, _| {
11820 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11821 {
11822 assert_eq!(
11823 completion_menu_entries(&menu),
11824 &["bg-red", "bg-blue", "bg-yellow"]
11825 );
11826 } else {
11827 panic!("expected completion menu to be open");
11828 }
11829 });
11830
11831 cx.simulate_keystroke("l");
11832 cx.executor().run_until_parked();
11833 cx.update_editor(|editor, _, _| {
11834 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11835 {
11836 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
11837 } else {
11838 panic!("expected completion menu to be open");
11839 }
11840 });
11841
11842 // When filtering completions, consider the character after the '-' to
11843 // be the start of a subword.
11844 cx.set_state(r#"<p class="yelˇ" />"#);
11845 cx.simulate_keystroke("l");
11846 cx.executor().run_until_parked();
11847 cx.update_editor(|editor, _, _| {
11848 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11849 {
11850 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
11851 } else {
11852 panic!("expected completion menu to be open");
11853 }
11854 });
11855}
11856
11857fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
11858 let entries = menu.entries.borrow();
11859 entries.iter().map(|mat| mat.string.clone()).collect()
11860}
11861
11862#[gpui::test]
11863async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11864 init_test(cx, |settings| {
11865 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11866 FormatterList(vec![Formatter::Prettier].into()),
11867 ))
11868 });
11869
11870 let fs = FakeFs::new(cx.executor());
11871 fs.insert_file(path!("/file.ts"), Default::default()).await;
11872
11873 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
11874 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11875
11876 language_registry.add(Arc::new(Language::new(
11877 LanguageConfig {
11878 name: "TypeScript".into(),
11879 matcher: LanguageMatcher {
11880 path_suffixes: vec!["ts".to_string()],
11881 ..Default::default()
11882 },
11883 ..Default::default()
11884 },
11885 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11886 )));
11887 update_test_language_settings(cx, |settings| {
11888 settings.defaults.prettier = Some(PrettierSettings {
11889 allowed: true,
11890 ..PrettierSettings::default()
11891 });
11892 });
11893
11894 let test_plugin = "test_plugin";
11895 let _ = language_registry.register_fake_lsp(
11896 "TypeScript",
11897 FakeLspAdapter {
11898 prettier_plugins: vec![test_plugin],
11899 ..Default::default()
11900 },
11901 );
11902
11903 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11904 let buffer = project
11905 .update(cx, |project, cx| {
11906 project.open_local_buffer(path!("/file.ts"), cx)
11907 })
11908 .await
11909 .unwrap();
11910
11911 let buffer_text = "one\ntwo\nthree\n";
11912 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11913 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11914 editor.update_in(cx, |editor, window, cx| {
11915 editor.set_text(buffer_text, window, cx)
11916 });
11917
11918 editor
11919 .update_in(cx, |editor, window, cx| {
11920 editor.perform_format(
11921 project.clone(),
11922 FormatTrigger::Manual,
11923 FormatTarget::Buffers,
11924 window,
11925 cx,
11926 )
11927 })
11928 .unwrap()
11929 .await;
11930 assert_eq!(
11931 editor.update(cx, |editor, cx| editor.text(cx)),
11932 buffer_text.to_string() + prettier_format_suffix,
11933 "Test prettier formatting was not applied to the original buffer text",
11934 );
11935
11936 update_test_language_settings(cx, |settings| {
11937 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11938 });
11939 let format = editor.update_in(cx, |editor, window, cx| {
11940 editor.perform_format(
11941 project.clone(),
11942 FormatTrigger::Manual,
11943 FormatTarget::Buffers,
11944 window,
11945 cx,
11946 )
11947 });
11948 format.await.unwrap();
11949 assert_eq!(
11950 editor.update(cx, |editor, cx| editor.text(cx)),
11951 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11952 "Autoformatting (via test prettier) was not applied to the original buffer text",
11953 );
11954}
11955
11956#[gpui::test]
11957async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11958 init_test(cx, |_| {});
11959 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11960 let base_text = indoc! {r#"
11961 struct Row;
11962 struct Row1;
11963 struct Row2;
11964
11965 struct Row4;
11966 struct Row5;
11967 struct Row6;
11968
11969 struct Row8;
11970 struct Row9;
11971 struct Row10;"#};
11972
11973 // When addition hunks are not adjacent to carets, no hunk revert is performed
11974 assert_hunk_revert(
11975 indoc! {r#"struct Row;
11976 struct Row1;
11977 struct Row1.1;
11978 struct Row1.2;
11979 struct Row2;ˇ
11980
11981 struct Row4;
11982 struct Row5;
11983 struct Row6;
11984
11985 struct Row8;
11986 ˇstruct Row9;
11987 struct Row9.1;
11988 struct Row9.2;
11989 struct Row9.3;
11990 struct Row10;"#},
11991 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11992 indoc! {r#"struct Row;
11993 struct Row1;
11994 struct Row1.1;
11995 struct Row1.2;
11996 struct Row2;ˇ
11997
11998 struct Row4;
11999 struct Row5;
12000 struct Row6;
12001
12002 struct Row8;
12003 ˇstruct Row9;
12004 struct Row9.1;
12005 struct Row9.2;
12006 struct Row9.3;
12007 struct Row10;"#},
12008 base_text,
12009 &mut cx,
12010 );
12011 // Same for selections
12012 assert_hunk_revert(
12013 indoc! {r#"struct Row;
12014 struct Row1;
12015 struct Row2;
12016 struct Row2.1;
12017 struct Row2.2;
12018 «ˇ
12019 struct Row4;
12020 struct» Row5;
12021 «struct Row6;
12022 ˇ»
12023 struct Row9.1;
12024 struct Row9.2;
12025 struct Row9.3;
12026 struct Row8;
12027 struct Row9;
12028 struct Row10;"#},
12029 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
12030 indoc! {r#"struct Row;
12031 struct Row1;
12032 struct Row2;
12033 struct Row2.1;
12034 struct Row2.2;
12035 «ˇ
12036 struct Row4;
12037 struct» Row5;
12038 «struct Row6;
12039 ˇ»
12040 struct Row9.1;
12041 struct Row9.2;
12042 struct Row9.3;
12043 struct Row8;
12044 struct Row9;
12045 struct Row10;"#},
12046 base_text,
12047 &mut cx,
12048 );
12049
12050 // When carets and selections intersect the addition hunks, those are reverted.
12051 // Adjacent carets got merged.
12052 assert_hunk_revert(
12053 indoc! {r#"struct Row;
12054 ˇ// something on the top
12055 struct Row1;
12056 struct Row2;
12057 struct Roˇw3.1;
12058 struct Row2.2;
12059 struct Row2.3;ˇ
12060
12061 struct Row4;
12062 struct ˇRow5.1;
12063 struct Row5.2;
12064 struct «Rowˇ»5.3;
12065 struct Row5;
12066 struct Row6;
12067 ˇ
12068 struct Row9.1;
12069 struct «Rowˇ»9.2;
12070 struct «ˇRow»9.3;
12071 struct Row8;
12072 struct Row9;
12073 «ˇ// something on bottom»
12074 struct Row10;"#},
12075 vec![
12076 DiffHunkStatus::Added,
12077 DiffHunkStatus::Added,
12078 DiffHunkStatus::Added,
12079 DiffHunkStatus::Added,
12080 DiffHunkStatus::Added,
12081 ],
12082 indoc! {r#"struct Row;
12083 ˇstruct Row1;
12084 struct Row2;
12085 ˇ
12086 struct Row4;
12087 ˇstruct Row5;
12088 struct Row6;
12089 ˇ
12090 ˇstruct Row8;
12091 struct Row9;
12092 ˇstruct Row10;"#},
12093 base_text,
12094 &mut cx,
12095 );
12096}
12097
12098#[gpui::test]
12099async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
12100 init_test(cx, |_| {});
12101 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12102 let base_text = indoc! {r#"
12103 struct Row;
12104 struct Row1;
12105 struct Row2;
12106
12107 struct Row4;
12108 struct Row5;
12109 struct Row6;
12110
12111 struct Row8;
12112 struct Row9;
12113 struct Row10;"#};
12114
12115 // Modification hunks behave the same as the addition ones.
12116 assert_hunk_revert(
12117 indoc! {r#"struct Row;
12118 struct Row1;
12119 struct Row33;
12120 ˇ
12121 struct Row4;
12122 struct Row5;
12123 struct Row6;
12124 ˇ
12125 struct Row99;
12126 struct Row9;
12127 struct Row10;"#},
12128 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
12129 indoc! {r#"struct Row;
12130 struct Row1;
12131 struct Row33;
12132 ˇ
12133 struct Row4;
12134 struct Row5;
12135 struct Row6;
12136 ˇ
12137 struct Row99;
12138 struct Row9;
12139 struct Row10;"#},
12140 base_text,
12141 &mut cx,
12142 );
12143 assert_hunk_revert(
12144 indoc! {r#"struct Row;
12145 struct Row1;
12146 struct Row33;
12147 «ˇ
12148 struct Row4;
12149 struct» Row5;
12150 «struct Row6;
12151 ˇ»
12152 struct Row99;
12153 struct Row9;
12154 struct Row10;"#},
12155 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
12156 indoc! {r#"struct Row;
12157 struct Row1;
12158 struct Row33;
12159 «ˇ
12160 struct Row4;
12161 struct» Row5;
12162 «struct Row6;
12163 ˇ»
12164 struct Row99;
12165 struct Row9;
12166 struct Row10;"#},
12167 base_text,
12168 &mut cx,
12169 );
12170
12171 assert_hunk_revert(
12172 indoc! {r#"ˇstruct Row1.1;
12173 struct Row1;
12174 «ˇstr»uct Row22;
12175
12176 struct ˇRow44;
12177 struct Row5;
12178 struct «Rˇ»ow66;ˇ
12179
12180 «struˇ»ct Row88;
12181 struct Row9;
12182 struct Row1011;ˇ"#},
12183 vec![
12184 DiffHunkStatus::Modified,
12185 DiffHunkStatus::Modified,
12186 DiffHunkStatus::Modified,
12187 DiffHunkStatus::Modified,
12188 DiffHunkStatus::Modified,
12189 DiffHunkStatus::Modified,
12190 ],
12191 indoc! {r#"struct Row;
12192 ˇstruct Row1;
12193 struct Row2;
12194 ˇ
12195 struct Row4;
12196 ˇstruct Row5;
12197 struct Row6;
12198 ˇ
12199 struct Row8;
12200 ˇstruct Row9;
12201 struct Row10;ˇ"#},
12202 base_text,
12203 &mut cx,
12204 );
12205}
12206
12207#[gpui::test]
12208async fn test_deleting_over_diff_hunk(cx: &mut gpui::TestAppContext) {
12209 init_test(cx, |_| {});
12210 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12211 let base_text = indoc! {r#"
12212 one
12213
12214 two
12215 three
12216 "#};
12217
12218 cx.set_diff_base(base_text);
12219 cx.set_state("\nˇ\n");
12220 cx.executor().run_until_parked();
12221 cx.update_editor(|editor, _window, cx| {
12222 editor.expand_selected_diff_hunks(cx);
12223 });
12224 cx.executor().run_until_parked();
12225 cx.update_editor(|editor, window, cx| {
12226 editor.backspace(&Default::default(), window, cx);
12227 });
12228 cx.run_until_parked();
12229 cx.assert_state_with_diff(
12230 indoc! {r#"
12231
12232 - two
12233 - threeˇ
12234 +
12235 "#}
12236 .to_string(),
12237 );
12238}
12239
12240#[gpui::test]
12241async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
12242 init_test(cx, |_| {});
12243 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12244 let base_text = indoc! {r#"struct Row;
12245struct Row1;
12246struct Row2;
12247
12248struct Row4;
12249struct Row5;
12250struct Row6;
12251
12252struct Row8;
12253struct Row9;
12254struct Row10;"#};
12255
12256 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12257 assert_hunk_revert(
12258 indoc! {r#"struct Row;
12259 struct Row2;
12260
12261 ˇstruct Row4;
12262 struct Row5;
12263 struct Row6;
12264 ˇ
12265 struct Row8;
12266 struct Row10;"#},
12267 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12268 indoc! {r#"struct Row;
12269 struct Row2;
12270
12271 ˇstruct Row4;
12272 struct Row5;
12273 struct Row6;
12274 ˇ
12275 struct Row8;
12276 struct Row10;"#},
12277 base_text,
12278 &mut cx,
12279 );
12280 assert_hunk_revert(
12281 indoc! {r#"struct Row;
12282 struct Row2;
12283
12284 «ˇstruct Row4;
12285 struct» Row5;
12286 «struct Row6;
12287 ˇ»
12288 struct Row8;
12289 struct Row10;"#},
12290 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12291 indoc! {r#"struct Row;
12292 struct Row2;
12293
12294 «ˇstruct Row4;
12295 struct» Row5;
12296 «struct Row6;
12297 ˇ»
12298 struct Row8;
12299 struct Row10;"#},
12300 base_text,
12301 &mut cx,
12302 );
12303
12304 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12305 assert_hunk_revert(
12306 indoc! {r#"struct Row;
12307 ˇstruct Row2;
12308
12309 struct Row4;
12310 struct Row5;
12311 struct Row6;
12312
12313 struct Row8;ˇ
12314 struct Row10;"#},
12315 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12316 indoc! {r#"struct Row;
12317 struct Row1;
12318 ˇstruct Row2;
12319
12320 struct Row4;
12321 struct Row5;
12322 struct Row6;
12323
12324 struct Row8;ˇ
12325 struct Row9;
12326 struct Row10;"#},
12327 base_text,
12328 &mut cx,
12329 );
12330 assert_hunk_revert(
12331 indoc! {r#"struct Row;
12332 struct Row2«ˇ;
12333 struct Row4;
12334 struct» Row5;
12335 «struct Row6;
12336
12337 struct Row8;ˇ»
12338 struct Row10;"#},
12339 vec![
12340 DiffHunkStatus::Removed,
12341 DiffHunkStatus::Removed,
12342 DiffHunkStatus::Removed,
12343 ],
12344 indoc! {r#"struct Row;
12345 struct Row1;
12346 struct Row2«ˇ;
12347
12348 struct Row4;
12349 struct» Row5;
12350 «struct Row6;
12351
12352 struct Row8;ˇ»
12353 struct Row9;
12354 struct Row10;"#},
12355 base_text,
12356 &mut cx,
12357 );
12358}
12359
12360#[gpui::test]
12361async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
12362 init_test(cx, |_| {});
12363
12364 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12365 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12366 let base_text_3 =
12367 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12368
12369 let text_1 = edit_first_char_of_every_line(base_text_1);
12370 let text_2 = edit_first_char_of_every_line(base_text_2);
12371 let text_3 = edit_first_char_of_every_line(base_text_3);
12372
12373 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12374 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12375 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12376
12377 let multibuffer = cx.new(|cx| {
12378 let mut multibuffer = MultiBuffer::new(ReadWrite);
12379 multibuffer.push_excerpts(
12380 buffer_1.clone(),
12381 [
12382 ExcerptRange {
12383 context: Point::new(0, 0)..Point::new(3, 0),
12384 primary: None,
12385 },
12386 ExcerptRange {
12387 context: Point::new(5, 0)..Point::new(7, 0),
12388 primary: None,
12389 },
12390 ExcerptRange {
12391 context: Point::new(9, 0)..Point::new(10, 4),
12392 primary: None,
12393 },
12394 ],
12395 cx,
12396 );
12397 multibuffer.push_excerpts(
12398 buffer_2.clone(),
12399 [
12400 ExcerptRange {
12401 context: Point::new(0, 0)..Point::new(3, 0),
12402 primary: None,
12403 },
12404 ExcerptRange {
12405 context: Point::new(5, 0)..Point::new(7, 0),
12406 primary: None,
12407 },
12408 ExcerptRange {
12409 context: Point::new(9, 0)..Point::new(10, 4),
12410 primary: None,
12411 },
12412 ],
12413 cx,
12414 );
12415 multibuffer.push_excerpts(
12416 buffer_3.clone(),
12417 [
12418 ExcerptRange {
12419 context: Point::new(0, 0)..Point::new(3, 0),
12420 primary: None,
12421 },
12422 ExcerptRange {
12423 context: Point::new(5, 0)..Point::new(7, 0),
12424 primary: None,
12425 },
12426 ExcerptRange {
12427 context: Point::new(9, 0)..Point::new(10, 4),
12428 primary: None,
12429 },
12430 ],
12431 cx,
12432 );
12433 multibuffer
12434 });
12435
12436 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12437 editor.update_in(cx, |editor, _window, cx| {
12438 for (buffer, diff_base) in [
12439 (buffer_1.clone(), base_text_1),
12440 (buffer_2.clone(), base_text_2),
12441 (buffer_3.clone(), base_text_3),
12442 ] {
12443 let change_set =
12444 cx.new(|cx| BufferChangeSet::new_with_base_text(&diff_base, &buffer, cx));
12445 editor
12446 .buffer
12447 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
12448 }
12449 });
12450 cx.executor().run_until_parked();
12451
12452 editor.update_in(cx, |editor, window, cx| {
12453 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}");
12454 editor.select_all(&SelectAll, window, cx);
12455 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
12456 });
12457 cx.executor().run_until_parked();
12458
12459 // When all ranges are selected, all buffer hunks are reverted.
12460 editor.update(cx, |editor, cx| {
12461 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");
12462 });
12463 buffer_1.update(cx, |buffer, _| {
12464 assert_eq!(buffer.text(), base_text_1);
12465 });
12466 buffer_2.update(cx, |buffer, _| {
12467 assert_eq!(buffer.text(), base_text_2);
12468 });
12469 buffer_3.update(cx, |buffer, _| {
12470 assert_eq!(buffer.text(), base_text_3);
12471 });
12472
12473 editor.update_in(cx, |editor, window, cx| {
12474 editor.undo(&Default::default(), window, cx);
12475 });
12476
12477 editor.update_in(cx, |editor, window, cx| {
12478 editor.change_selections(None, window, cx, |s| {
12479 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12480 });
12481 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
12482 });
12483
12484 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12485 // but not affect buffer_2 and its related excerpts.
12486 editor.update(cx, |editor, cx| {
12487 assert_eq!(
12488 editor.text(cx),
12489 "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}"
12490 );
12491 });
12492 buffer_1.update(cx, |buffer, _| {
12493 assert_eq!(buffer.text(), base_text_1);
12494 });
12495 buffer_2.update(cx, |buffer, _| {
12496 assert_eq!(
12497 buffer.text(),
12498 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12499 );
12500 });
12501 buffer_3.update(cx, |buffer, _| {
12502 assert_eq!(
12503 buffer.text(),
12504 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12505 );
12506 });
12507
12508 fn edit_first_char_of_every_line(text: &str) -> String {
12509 text.split('\n')
12510 .map(|line| format!("X{}", &line[1..]))
12511 .collect::<Vec<_>>()
12512 .join("\n")
12513 }
12514}
12515
12516#[gpui::test]
12517async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
12518 init_test(cx, |_| {});
12519
12520 let cols = 4;
12521 let rows = 10;
12522 let sample_text_1 = sample_text(rows, cols, 'a');
12523 assert_eq!(
12524 sample_text_1,
12525 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12526 );
12527 let sample_text_2 = sample_text(rows, cols, 'l');
12528 assert_eq!(
12529 sample_text_2,
12530 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12531 );
12532 let sample_text_3 = sample_text(rows, cols, 'v');
12533 assert_eq!(
12534 sample_text_3,
12535 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12536 );
12537
12538 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12539 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12540 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12541
12542 let multi_buffer = cx.new(|cx| {
12543 let mut multibuffer = MultiBuffer::new(ReadWrite);
12544 multibuffer.push_excerpts(
12545 buffer_1.clone(),
12546 [
12547 ExcerptRange {
12548 context: Point::new(0, 0)..Point::new(3, 0),
12549 primary: None,
12550 },
12551 ExcerptRange {
12552 context: Point::new(5, 0)..Point::new(7, 0),
12553 primary: None,
12554 },
12555 ExcerptRange {
12556 context: Point::new(9, 0)..Point::new(10, 4),
12557 primary: None,
12558 },
12559 ],
12560 cx,
12561 );
12562 multibuffer.push_excerpts(
12563 buffer_2.clone(),
12564 [
12565 ExcerptRange {
12566 context: Point::new(0, 0)..Point::new(3, 0),
12567 primary: None,
12568 },
12569 ExcerptRange {
12570 context: Point::new(5, 0)..Point::new(7, 0),
12571 primary: None,
12572 },
12573 ExcerptRange {
12574 context: Point::new(9, 0)..Point::new(10, 4),
12575 primary: None,
12576 },
12577 ],
12578 cx,
12579 );
12580 multibuffer.push_excerpts(
12581 buffer_3.clone(),
12582 [
12583 ExcerptRange {
12584 context: Point::new(0, 0)..Point::new(3, 0),
12585 primary: None,
12586 },
12587 ExcerptRange {
12588 context: Point::new(5, 0)..Point::new(7, 0),
12589 primary: None,
12590 },
12591 ExcerptRange {
12592 context: Point::new(9, 0)..Point::new(10, 4),
12593 primary: None,
12594 },
12595 ],
12596 cx,
12597 );
12598 multibuffer
12599 });
12600
12601 let fs = FakeFs::new(cx.executor());
12602 fs.insert_tree(
12603 "/a",
12604 json!({
12605 "main.rs": sample_text_1,
12606 "other.rs": sample_text_2,
12607 "lib.rs": sample_text_3,
12608 }),
12609 )
12610 .await;
12611 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12612 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12613 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12614 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12615 Editor::new(
12616 EditorMode::Full,
12617 multi_buffer,
12618 Some(project.clone()),
12619 true,
12620 window,
12621 cx,
12622 )
12623 });
12624 let multibuffer_item_id = workspace
12625 .update(cx, |workspace, window, cx| {
12626 assert!(
12627 workspace.active_item(cx).is_none(),
12628 "active item should be None before the first item is added"
12629 );
12630 workspace.add_item_to_active_pane(
12631 Box::new(multi_buffer_editor.clone()),
12632 None,
12633 true,
12634 window,
12635 cx,
12636 );
12637 let active_item = workspace
12638 .active_item(cx)
12639 .expect("should have an active item after adding the multi buffer");
12640 assert!(
12641 !active_item.is_singleton(cx),
12642 "A multi buffer was expected to active after adding"
12643 );
12644 active_item.item_id()
12645 })
12646 .unwrap();
12647 cx.executor().run_until_parked();
12648
12649 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12650 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12651 s.select_ranges(Some(1..2))
12652 });
12653 editor.open_excerpts(&OpenExcerpts, window, cx);
12654 });
12655 cx.executor().run_until_parked();
12656 let first_item_id = workspace
12657 .update(cx, |workspace, window, cx| {
12658 let active_item = workspace
12659 .active_item(cx)
12660 .expect("should have an active item after navigating into the 1st buffer");
12661 let first_item_id = active_item.item_id();
12662 assert_ne!(
12663 first_item_id, multibuffer_item_id,
12664 "Should navigate into the 1st buffer and activate it"
12665 );
12666 assert!(
12667 active_item.is_singleton(cx),
12668 "New active item should be a singleton buffer"
12669 );
12670 assert_eq!(
12671 active_item
12672 .act_as::<Editor>(cx)
12673 .expect("should have navigated into an editor for the 1st buffer")
12674 .read(cx)
12675 .text(cx),
12676 sample_text_1
12677 );
12678
12679 workspace
12680 .go_back(workspace.active_pane().downgrade(), window, cx)
12681 .detach_and_log_err(cx);
12682
12683 first_item_id
12684 })
12685 .unwrap();
12686 cx.executor().run_until_parked();
12687 workspace
12688 .update(cx, |workspace, _, cx| {
12689 let active_item = workspace
12690 .active_item(cx)
12691 .expect("should have an active item after navigating back");
12692 assert_eq!(
12693 active_item.item_id(),
12694 multibuffer_item_id,
12695 "Should navigate back to the multi buffer"
12696 );
12697 assert!(!active_item.is_singleton(cx));
12698 })
12699 .unwrap();
12700
12701 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12702 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12703 s.select_ranges(Some(39..40))
12704 });
12705 editor.open_excerpts(&OpenExcerpts, window, cx);
12706 });
12707 cx.executor().run_until_parked();
12708 let second_item_id = workspace
12709 .update(cx, |workspace, window, cx| {
12710 let active_item = workspace
12711 .active_item(cx)
12712 .expect("should have an active item after navigating into the 2nd buffer");
12713 let second_item_id = active_item.item_id();
12714 assert_ne!(
12715 second_item_id, multibuffer_item_id,
12716 "Should navigate away from the multibuffer"
12717 );
12718 assert_ne!(
12719 second_item_id, first_item_id,
12720 "Should navigate into the 2nd buffer and activate it"
12721 );
12722 assert!(
12723 active_item.is_singleton(cx),
12724 "New active item should be a singleton buffer"
12725 );
12726 assert_eq!(
12727 active_item
12728 .act_as::<Editor>(cx)
12729 .expect("should have navigated into an editor")
12730 .read(cx)
12731 .text(cx),
12732 sample_text_2
12733 );
12734
12735 workspace
12736 .go_back(workspace.active_pane().downgrade(), window, cx)
12737 .detach_and_log_err(cx);
12738
12739 second_item_id
12740 })
12741 .unwrap();
12742 cx.executor().run_until_parked();
12743 workspace
12744 .update(cx, |workspace, _, cx| {
12745 let active_item = workspace
12746 .active_item(cx)
12747 .expect("should have an active item after navigating back from the 2nd buffer");
12748 assert_eq!(
12749 active_item.item_id(),
12750 multibuffer_item_id,
12751 "Should navigate back from the 2nd buffer to the multi buffer"
12752 );
12753 assert!(!active_item.is_singleton(cx));
12754 })
12755 .unwrap();
12756
12757 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12758 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12759 s.select_ranges(Some(70..70))
12760 });
12761 editor.open_excerpts(&OpenExcerpts, window, cx);
12762 });
12763 cx.executor().run_until_parked();
12764 workspace
12765 .update(cx, |workspace, window, cx| {
12766 let active_item = workspace
12767 .active_item(cx)
12768 .expect("should have an active item after navigating into the 3rd buffer");
12769 let third_item_id = active_item.item_id();
12770 assert_ne!(
12771 third_item_id, multibuffer_item_id,
12772 "Should navigate into the 3rd buffer and activate it"
12773 );
12774 assert_ne!(third_item_id, first_item_id);
12775 assert_ne!(third_item_id, second_item_id);
12776 assert!(
12777 active_item.is_singleton(cx),
12778 "New active item should be a singleton buffer"
12779 );
12780 assert_eq!(
12781 active_item
12782 .act_as::<Editor>(cx)
12783 .expect("should have navigated into an editor")
12784 .read(cx)
12785 .text(cx),
12786 sample_text_3
12787 );
12788
12789 workspace
12790 .go_back(workspace.active_pane().downgrade(), window, cx)
12791 .detach_and_log_err(cx);
12792 })
12793 .unwrap();
12794 cx.executor().run_until_parked();
12795 workspace
12796 .update(cx, |workspace, _, cx| {
12797 let active_item = workspace
12798 .active_item(cx)
12799 .expect("should have an active item after navigating back from the 3rd buffer");
12800 assert_eq!(
12801 active_item.item_id(),
12802 multibuffer_item_id,
12803 "Should navigate back from the 3rd buffer to the multi buffer"
12804 );
12805 assert!(!active_item.is_singleton(cx));
12806 })
12807 .unwrap();
12808}
12809
12810#[gpui::test]
12811async fn test_toggle_selected_diff_hunks(
12812 executor: BackgroundExecutor,
12813 cx: &mut gpui::TestAppContext,
12814) {
12815 init_test(cx, |_| {});
12816
12817 let mut cx = EditorTestContext::new(cx).await;
12818
12819 let diff_base = r#"
12820 use some::mod;
12821
12822 const A: u32 = 42;
12823
12824 fn main() {
12825 println!("hello");
12826
12827 println!("world");
12828 }
12829 "#
12830 .unindent();
12831
12832 cx.set_state(
12833 &r#"
12834 use some::modified;
12835
12836 ˇ
12837 fn main() {
12838 println!("hello there");
12839
12840 println!("around the");
12841 println!("world");
12842 }
12843 "#
12844 .unindent(),
12845 );
12846
12847 cx.set_diff_base(&diff_base);
12848 executor.run_until_parked();
12849
12850 cx.update_editor(|editor, window, cx| {
12851 editor.go_to_next_hunk(&GoToHunk, window, cx);
12852 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
12853 });
12854 executor.run_until_parked();
12855 cx.assert_state_with_diff(
12856 r#"
12857 use some::modified;
12858
12859
12860 fn main() {
12861 - println!("hello");
12862 + ˇ println!("hello there");
12863
12864 println!("around the");
12865 println!("world");
12866 }
12867 "#
12868 .unindent(),
12869 );
12870
12871 cx.update_editor(|editor, window, cx| {
12872 for _ in 0..2 {
12873 editor.go_to_next_hunk(&GoToHunk, window, cx);
12874 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
12875 }
12876 });
12877 executor.run_until_parked();
12878 cx.assert_state_with_diff(
12879 r#"
12880 - use some::mod;
12881 + ˇuse some::modified;
12882
12883
12884 fn main() {
12885 - println!("hello");
12886 + println!("hello there");
12887
12888 + println!("around the");
12889 println!("world");
12890 }
12891 "#
12892 .unindent(),
12893 );
12894
12895 cx.update_editor(|editor, window, cx| {
12896 editor.go_to_next_hunk(&GoToHunk, window, cx);
12897 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
12898 });
12899 executor.run_until_parked();
12900 cx.assert_state_with_diff(
12901 r#"
12902 - use some::mod;
12903 + use some::modified;
12904
12905 - const A: u32 = 42;
12906 ˇ
12907 fn main() {
12908 - println!("hello");
12909 + println!("hello there");
12910
12911 + println!("around the");
12912 println!("world");
12913 }
12914 "#
12915 .unindent(),
12916 );
12917
12918 cx.update_editor(|editor, window, cx| {
12919 editor.cancel(&Cancel, window, cx);
12920 });
12921
12922 cx.assert_state_with_diff(
12923 r#"
12924 use some::modified;
12925
12926 ˇ
12927 fn main() {
12928 println!("hello there");
12929
12930 println!("around the");
12931 println!("world");
12932 }
12933 "#
12934 .unindent(),
12935 );
12936}
12937
12938#[gpui::test]
12939async fn test_diff_base_change_with_expanded_diff_hunks(
12940 executor: BackgroundExecutor,
12941 cx: &mut gpui::TestAppContext,
12942) {
12943 init_test(cx, |_| {});
12944
12945 let mut cx = EditorTestContext::new(cx).await;
12946
12947 let diff_base = r#"
12948 use some::mod1;
12949 use some::mod2;
12950
12951 const A: u32 = 42;
12952 const B: u32 = 42;
12953 const C: u32 = 42;
12954
12955 fn main() {
12956 println!("hello");
12957
12958 println!("world");
12959 }
12960 "#
12961 .unindent();
12962
12963 cx.set_state(
12964 &r#"
12965 use some::mod2;
12966
12967 const A: u32 = 42;
12968 const C: u32 = 42;
12969
12970 fn main(ˇ) {
12971 //println!("hello");
12972
12973 println!("world");
12974 //
12975 //
12976 }
12977 "#
12978 .unindent(),
12979 );
12980
12981 cx.set_diff_base(&diff_base);
12982 executor.run_until_parked();
12983
12984 cx.update_editor(|editor, window, cx| {
12985 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
12986 });
12987 executor.run_until_parked();
12988 cx.assert_state_with_diff(
12989 r#"
12990 - use some::mod1;
12991 use some::mod2;
12992
12993 const A: u32 = 42;
12994 - const B: u32 = 42;
12995 const C: u32 = 42;
12996
12997 fn main(ˇ) {
12998 - println!("hello");
12999 + //println!("hello");
13000
13001 println!("world");
13002 + //
13003 + //
13004 }
13005 "#
13006 .unindent(),
13007 );
13008
13009 cx.set_diff_base("new diff base!");
13010 executor.run_until_parked();
13011 cx.assert_state_with_diff(
13012 r#"
13013 use some::mod2;
13014
13015 const A: u32 = 42;
13016 const C: u32 = 42;
13017
13018 fn main(ˇ) {
13019 //println!("hello");
13020
13021 println!("world");
13022 //
13023 //
13024 }
13025 "#
13026 .unindent(),
13027 );
13028
13029 cx.update_editor(|editor, window, cx| {
13030 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13031 });
13032 executor.run_until_parked();
13033 cx.assert_state_with_diff(
13034 r#"
13035 - new diff base!
13036 + use some::mod2;
13037 +
13038 + const A: u32 = 42;
13039 + const C: u32 = 42;
13040 +
13041 + fn main(ˇ) {
13042 + //println!("hello");
13043 +
13044 + println!("world");
13045 + //
13046 + //
13047 + }
13048 "#
13049 .unindent(),
13050 );
13051}
13052
13053#[gpui::test]
13054async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
13055 init_test(cx, |_| {});
13056
13057 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13058 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13059 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13060 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13061 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13062 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13063
13064 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13065 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13066 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13067
13068 let multi_buffer = cx.new(|cx| {
13069 let mut multibuffer = MultiBuffer::new(ReadWrite);
13070 multibuffer.push_excerpts(
13071 buffer_1.clone(),
13072 [
13073 ExcerptRange {
13074 context: Point::new(0, 0)..Point::new(3, 0),
13075 primary: None,
13076 },
13077 ExcerptRange {
13078 context: Point::new(5, 0)..Point::new(7, 0),
13079 primary: None,
13080 },
13081 ExcerptRange {
13082 context: Point::new(9, 0)..Point::new(10, 3),
13083 primary: None,
13084 },
13085 ],
13086 cx,
13087 );
13088 multibuffer.push_excerpts(
13089 buffer_2.clone(),
13090 [
13091 ExcerptRange {
13092 context: Point::new(0, 0)..Point::new(3, 0),
13093 primary: None,
13094 },
13095 ExcerptRange {
13096 context: Point::new(5, 0)..Point::new(7, 0),
13097 primary: None,
13098 },
13099 ExcerptRange {
13100 context: Point::new(9, 0)..Point::new(10, 3),
13101 primary: None,
13102 },
13103 ],
13104 cx,
13105 );
13106 multibuffer.push_excerpts(
13107 buffer_3.clone(),
13108 [
13109 ExcerptRange {
13110 context: Point::new(0, 0)..Point::new(3, 0),
13111 primary: None,
13112 },
13113 ExcerptRange {
13114 context: Point::new(5, 0)..Point::new(7, 0),
13115 primary: None,
13116 },
13117 ExcerptRange {
13118 context: Point::new(9, 0)..Point::new(10, 3),
13119 primary: None,
13120 },
13121 ],
13122 cx,
13123 );
13124 multibuffer
13125 });
13126
13127 let editor = cx.add_window(|window, cx| {
13128 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13129 });
13130 editor
13131 .update(cx, |editor, _window, cx| {
13132 for (buffer, diff_base) in [
13133 (buffer_1.clone(), file_1_old),
13134 (buffer_2.clone(), file_2_old),
13135 (buffer_3.clone(), file_3_old),
13136 ] {
13137 let change_set =
13138 cx.new(|cx| BufferChangeSet::new_with_base_text(&diff_base, &buffer, cx));
13139 editor
13140 .buffer
13141 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
13142 }
13143 })
13144 .unwrap();
13145
13146 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13147 cx.run_until_parked();
13148
13149 cx.assert_editor_state(
13150 &"
13151 ˇaaa
13152 ccc
13153 ddd
13154
13155 ggg
13156 hhh
13157
13158
13159 lll
13160 mmm
13161 NNN
13162
13163 qqq
13164 rrr
13165
13166 uuu
13167 111
13168 222
13169 333
13170
13171 666
13172 777
13173
13174 000
13175 !!!"
13176 .unindent(),
13177 );
13178
13179 cx.update_editor(|editor, window, cx| {
13180 editor.select_all(&SelectAll, window, cx);
13181 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13182 });
13183 cx.executor().run_until_parked();
13184
13185 cx.assert_state_with_diff(
13186 "
13187 «aaa
13188 - bbb
13189 ccc
13190 ddd
13191
13192 ggg
13193 hhh
13194
13195
13196 lll
13197 mmm
13198 - nnn
13199 + NNN
13200
13201 qqq
13202 rrr
13203
13204 uuu
13205 111
13206 222
13207 333
13208
13209 + 666
13210 777
13211
13212 000
13213 !!!ˇ»"
13214 .unindent(),
13215 );
13216}
13217
13218#[gpui::test]
13219async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
13220 init_test(cx, |_| {});
13221
13222 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13223 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13224
13225 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13226 let multi_buffer = cx.new(|cx| {
13227 let mut multibuffer = MultiBuffer::new(ReadWrite);
13228 multibuffer.push_excerpts(
13229 buffer.clone(),
13230 [
13231 ExcerptRange {
13232 context: Point::new(0, 0)..Point::new(2, 0),
13233 primary: None,
13234 },
13235 ExcerptRange {
13236 context: Point::new(4, 0)..Point::new(7, 0),
13237 primary: None,
13238 },
13239 ExcerptRange {
13240 context: Point::new(9, 0)..Point::new(10, 0),
13241 primary: None,
13242 },
13243 ],
13244 cx,
13245 );
13246 multibuffer
13247 });
13248
13249 let editor = cx.add_window(|window, cx| {
13250 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13251 });
13252 editor
13253 .update(cx, |editor, _window, cx| {
13254 let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base, &buffer, cx));
13255 editor
13256 .buffer
13257 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx))
13258 })
13259 .unwrap();
13260
13261 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13262 cx.run_until_parked();
13263
13264 cx.update_editor(|editor, window, cx| {
13265 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13266 });
13267 cx.executor().run_until_parked();
13268
13269 // When the start of a hunk coincides with the start of its excerpt,
13270 // the hunk is expanded. When the start of a a hunk is earlier than
13271 // the start of its excerpt, the hunk is not expanded.
13272 cx.assert_state_with_diff(
13273 "
13274 ˇaaa
13275 - bbb
13276 + BBB
13277
13278 - ddd
13279 - eee
13280 + DDD
13281 + EEE
13282 fff
13283
13284 iii
13285 "
13286 .unindent(),
13287 );
13288}
13289
13290#[gpui::test]
13291async fn test_edits_around_expanded_insertion_hunks(
13292 executor: BackgroundExecutor,
13293 cx: &mut gpui::TestAppContext,
13294) {
13295 init_test(cx, |_| {});
13296
13297 let mut cx = EditorTestContext::new(cx).await;
13298
13299 let diff_base = r#"
13300 use some::mod1;
13301 use some::mod2;
13302
13303 const A: u32 = 42;
13304
13305 fn main() {
13306 println!("hello");
13307
13308 println!("world");
13309 }
13310 "#
13311 .unindent();
13312 executor.run_until_parked();
13313 cx.set_state(
13314 &r#"
13315 use some::mod1;
13316 use some::mod2;
13317
13318 const A: u32 = 42;
13319 const B: u32 = 42;
13320 const C: u32 = 42;
13321 ˇ
13322
13323 fn main() {
13324 println!("hello");
13325
13326 println!("world");
13327 }
13328 "#
13329 .unindent(),
13330 );
13331
13332 cx.set_diff_base(&diff_base);
13333 executor.run_until_parked();
13334
13335 cx.update_editor(|editor, window, cx| {
13336 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13337 });
13338 executor.run_until_parked();
13339
13340 cx.assert_state_with_diff(
13341 r#"
13342 use some::mod1;
13343 use some::mod2;
13344
13345 const A: u32 = 42;
13346 + const B: u32 = 42;
13347 + const C: u32 = 42;
13348 + ˇ
13349
13350 fn main() {
13351 println!("hello");
13352
13353 println!("world");
13354 }
13355 "#
13356 .unindent(),
13357 );
13358
13359 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13360 executor.run_until_parked();
13361
13362 cx.assert_state_with_diff(
13363 r#"
13364 use some::mod1;
13365 use some::mod2;
13366
13367 const A: u32 = 42;
13368 + const B: u32 = 42;
13369 + const C: u32 = 42;
13370 + const D: u32 = 42;
13371 + ˇ
13372
13373 fn main() {
13374 println!("hello");
13375
13376 println!("world");
13377 }
13378 "#
13379 .unindent(),
13380 );
13381
13382 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13383 executor.run_until_parked();
13384
13385 cx.assert_state_with_diff(
13386 r#"
13387 use some::mod1;
13388 use some::mod2;
13389
13390 const A: u32 = 42;
13391 + const B: u32 = 42;
13392 + const C: u32 = 42;
13393 + const D: u32 = 42;
13394 + const E: u32 = 42;
13395 + ˇ
13396
13397 fn main() {
13398 println!("hello");
13399
13400 println!("world");
13401 }
13402 "#
13403 .unindent(),
13404 );
13405
13406 cx.update_editor(|editor, window, cx| {
13407 editor.delete_line(&DeleteLine, window, cx);
13408 });
13409 executor.run_until_parked();
13410
13411 cx.assert_state_with_diff(
13412 r#"
13413 use some::mod1;
13414 use some::mod2;
13415
13416 const A: u32 = 42;
13417 + const B: u32 = 42;
13418 + const C: u32 = 42;
13419 + const D: u32 = 42;
13420 + const E: u32 = 42;
13421 ˇ
13422 fn main() {
13423 println!("hello");
13424
13425 println!("world");
13426 }
13427 "#
13428 .unindent(),
13429 );
13430
13431 cx.update_editor(|editor, window, cx| {
13432 editor.move_up(&MoveUp, window, cx);
13433 editor.delete_line(&DeleteLine, window, cx);
13434 editor.move_up(&MoveUp, window, cx);
13435 editor.delete_line(&DeleteLine, window, cx);
13436 editor.move_up(&MoveUp, window, cx);
13437 editor.delete_line(&DeleteLine, window, cx);
13438 });
13439 executor.run_until_parked();
13440 cx.assert_state_with_diff(
13441 r#"
13442 use some::mod1;
13443 use some::mod2;
13444
13445 const A: u32 = 42;
13446 + const B: u32 = 42;
13447 ˇ
13448 fn main() {
13449 println!("hello");
13450
13451 println!("world");
13452 }
13453 "#
13454 .unindent(),
13455 );
13456
13457 cx.update_editor(|editor, window, cx| {
13458 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13459 editor.delete_line(&DeleteLine, window, cx);
13460 });
13461 executor.run_until_parked();
13462 cx.assert_state_with_diff(
13463 r#"
13464 ˇ
13465 fn main() {
13466 println!("hello");
13467
13468 println!("world");
13469 }
13470 "#
13471 .unindent(),
13472 );
13473}
13474
13475#[gpui::test]
13476async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13477 init_test(cx, |_| {});
13478
13479 let mut cx = EditorTestContext::new(cx).await;
13480 cx.set_diff_base(indoc! { "
13481 one
13482 two
13483 three
13484 four
13485 five
13486 "
13487 });
13488 cx.set_state(indoc! { "
13489 one
13490 ˇthree
13491 five
13492 "});
13493 cx.run_until_parked();
13494 cx.update_editor(|editor, window, cx| {
13495 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13496 });
13497 cx.assert_state_with_diff(
13498 indoc! { "
13499 one
13500 - two
13501 ˇthree
13502 - four
13503 five
13504 "}
13505 .to_string(),
13506 );
13507 cx.update_editor(|editor, window, cx| {
13508 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13509 });
13510
13511 cx.assert_state_with_diff(
13512 indoc! { "
13513 one
13514 ˇthree
13515 five
13516 "}
13517 .to_string(),
13518 );
13519
13520 cx.set_state(indoc! { "
13521 one
13522 ˇTWO
13523 three
13524 four
13525 five
13526 "});
13527 cx.run_until_parked();
13528 cx.update_editor(|editor, window, cx| {
13529 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13530 });
13531
13532 cx.assert_state_with_diff(
13533 indoc! { "
13534 one
13535 - two
13536 + ˇTWO
13537 three
13538 four
13539 five
13540 "}
13541 .to_string(),
13542 );
13543 cx.update_editor(|editor, window, cx| {
13544 editor.move_up(&Default::default(), window, cx);
13545 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13546 });
13547 cx.assert_state_with_diff(
13548 indoc! { "
13549 one
13550 ˇTWO
13551 three
13552 four
13553 five
13554 "}
13555 .to_string(),
13556 );
13557}
13558
13559#[gpui::test]
13560async fn test_edits_around_expanded_deletion_hunks(
13561 executor: BackgroundExecutor,
13562 cx: &mut gpui::TestAppContext,
13563) {
13564 init_test(cx, |_| {});
13565
13566 let mut cx = EditorTestContext::new(cx).await;
13567
13568 let diff_base = r#"
13569 use some::mod1;
13570 use some::mod2;
13571
13572 const A: u32 = 42;
13573 const B: u32 = 42;
13574 const C: u32 = 42;
13575
13576
13577 fn main() {
13578 println!("hello");
13579
13580 println!("world");
13581 }
13582 "#
13583 .unindent();
13584 executor.run_until_parked();
13585 cx.set_state(
13586 &r#"
13587 use some::mod1;
13588 use some::mod2;
13589
13590 ˇconst B: u32 = 42;
13591 const C: u32 = 42;
13592
13593
13594 fn main() {
13595 println!("hello");
13596
13597 println!("world");
13598 }
13599 "#
13600 .unindent(),
13601 );
13602
13603 cx.set_diff_base(&diff_base);
13604 executor.run_until_parked();
13605
13606 cx.update_editor(|editor, window, cx| {
13607 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13608 });
13609 executor.run_until_parked();
13610
13611 cx.assert_state_with_diff(
13612 r#"
13613 use some::mod1;
13614 use some::mod2;
13615
13616 - const A: u32 = 42;
13617 ˇconst B: u32 = 42;
13618 const C: u32 = 42;
13619
13620
13621 fn main() {
13622 println!("hello");
13623
13624 println!("world");
13625 }
13626 "#
13627 .unindent(),
13628 );
13629
13630 cx.update_editor(|editor, window, cx| {
13631 editor.delete_line(&DeleteLine, window, cx);
13632 });
13633 executor.run_until_parked();
13634 cx.assert_state_with_diff(
13635 r#"
13636 use some::mod1;
13637 use some::mod2;
13638
13639 - const A: u32 = 42;
13640 - const B: u32 = 42;
13641 ˇconst C: u32 = 42;
13642
13643
13644 fn main() {
13645 println!("hello");
13646
13647 println!("world");
13648 }
13649 "#
13650 .unindent(),
13651 );
13652
13653 cx.update_editor(|editor, window, cx| {
13654 editor.delete_line(&DeleteLine, window, cx);
13655 });
13656 executor.run_until_parked();
13657 cx.assert_state_with_diff(
13658 r#"
13659 use some::mod1;
13660 use some::mod2;
13661
13662 - const A: u32 = 42;
13663 - const B: u32 = 42;
13664 - const C: u32 = 42;
13665 ˇ
13666
13667 fn main() {
13668 println!("hello");
13669
13670 println!("world");
13671 }
13672 "#
13673 .unindent(),
13674 );
13675
13676 cx.update_editor(|editor, window, cx| {
13677 editor.handle_input("replacement", window, cx);
13678 });
13679 executor.run_until_parked();
13680 cx.assert_state_with_diff(
13681 r#"
13682 use some::mod1;
13683 use some::mod2;
13684
13685 - const A: u32 = 42;
13686 - const B: u32 = 42;
13687 - const C: u32 = 42;
13688 -
13689 + replacementˇ
13690
13691 fn main() {
13692 println!("hello");
13693
13694 println!("world");
13695 }
13696 "#
13697 .unindent(),
13698 );
13699}
13700
13701#[gpui::test]
13702async fn test_backspace_after_deletion_hunk(
13703 executor: BackgroundExecutor,
13704 cx: &mut gpui::TestAppContext,
13705) {
13706 init_test(cx, |_| {});
13707
13708 let mut cx = EditorTestContext::new(cx).await;
13709
13710 let base_text = r#"
13711 one
13712 two
13713 three
13714 four
13715 five
13716 "#
13717 .unindent();
13718 executor.run_until_parked();
13719 cx.set_state(
13720 &r#"
13721 one
13722 two
13723 fˇour
13724 five
13725 "#
13726 .unindent(),
13727 );
13728
13729 cx.set_diff_base(&base_text);
13730 executor.run_until_parked();
13731
13732 cx.update_editor(|editor, window, cx| {
13733 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13734 });
13735 executor.run_until_parked();
13736
13737 cx.assert_state_with_diff(
13738 r#"
13739 one
13740 two
13741 - three
13742 fˇour
13743 five
13744 "#
13745 .unindent(),
13746 );
13747
13748 cx.update_editor(|editor, window, cx| {
13749 editor.backspace(&Backspace, window, cx);
13750 editor.backspace(&Backspace, window, cx);
13751 });
13752 executor.run_until_parked();
13753 cx.assert_state_with_diff(
13754 r#"
13755 one
13756 two
13757 - threeˇ
13758 - four
13759 + our
13760 five
13761 "#
13762 .unindent(),
13763 );
13764}
13765
13766#[gpui::test]
13767async fn test_edit_after_expanded_modification_hunk(
13768 executor: BackgroundExecutor,
13769 cx: &mut gpui::TestAppContext,
13770) {
13771 init_test(cx, |_| {});
13772
13773 let mut cx = EditorTestContext::new(cx).await;
13774
13775 let diff_base = r#"
13776 use some::mod1;
13777 use some::mod2;
13778
13779 const A: u32 = 42;
13780 const B: u32 = 42;
13781 const C: u32 = 42;
13782 const D: u32 = 42;
13783
13784
13785 fn main() {
13786 println!("hello");
13787
13788 println!("world");
13789 }"#
13790 .unindent();
13791
13792 cx.set_state(
13793 &r#"
13794 use some::mod1;
13795 use some::mod2;
13796
13797 const A: u32 = 42;
13798 const B: u32 = 42;
13799 const C: u32 = 43ˇ
13800 const D: u32 = 42;
13801
13802
13803 fn main() {
13804 println!("hello");
13805
13806 println!("world");
13807 }"#
13808 .unindent(),
13809 );
13810
13811 cx.set_diff_base(&diff_base);
13812 executor.run_until_parked();
13813 cx.update_editor(|editor, window, cx| {
13814 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13815 });
13816 executor.run_until_parked();
13817
13818 cx.assert_state_with_diff(
13819 r#"
13820 use some::mod1;
13821 use some::mod2;
13822
13823 const A: u32 = 42;
13824 const B: u32 = 42;
13825 - const C: u32 = 42;
13826 + const C: u32 = 43ˇ
13827 const D: u32 = 42;
13828
13829
13830 fn main() {
13831 println!("hello");
13832
13833 println!("world");
13834 }"#
13835 .unindent(),
13836 );
13837
13838 cx.update_editor(|editor, window, cx| {
13839 editor.handle_input("\nnew_line\n", window, cx);
13840 });
13841 executor.run_until_parked();
13842
13843 cx.assert_state_with_diff(
13844 r#"
13845 use some::mod1;
13846 use some::mod2;
13847
13848 const A: u32 = 42;
13849 const B: u32 = 42;
13850 - const C: u32 = 42;
13851 + const C: u32 = 43
13852 + new_line
13853 + ˇ
13854 const D: u32 = 42;
13855
13856
13857 fn main() {
13858 println!("hello");
13859
13860 println!("world");
13861 }"#
13862 .unindent(),
13863 );
13864}
13865
13866async fn setup_indent_guides_editor(
13867 text: &str,
13868 cx: &mut gpui::TestAppContext,
13869) -> (BufferId, EditorTestContext) {
13870 init_test(cx, |_| {});
13871
13872 let mut cx = EditorTestContext::new(cx).await;
13873
13874 let buffer_id = cx.update_editor(|editor, window, cx| {
13875 editor.set_text(text, window, cx);
13876 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13877
13878 buffer_ids[0]
13879 });
13880
13881 (buffer_id, cx)
13882}
13883
13884fn assert_indent_guides(
13885 range: Range<u32>,
13886 expected: Vec<IndentGuide>,
13887 active_indices: Option<Vec<usize>>,
13888 cx: &mut EditorTestContext,
13889) {
13890 let indent_guides = cx.update_editor(|editor, window, cx| {
13891 let snapshot = editor.snapshot(window, cx).display_snapshot;
13892 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13893 editor,
13894 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13895 true,
13896 &snapshot,
13897 cx,
13898 );
13899
13900 indent_guides.sort_by(|a, b| {
13901 a.depth.cmp(&b.depth).then(
13902 a.start_row
13903 .cmp(&b.start_row)
13904 .then(a.end_row.cmp(&b.end_row)),
13905 )
13906 });
13907 indent_guides
13908 });
13909
13910 if let Some(expected) = active_indices {
13911 let active_indices = cx.update_editor(|editor, window, cx| {
13912 let snapshot = editor.snapshot(window, cx).display_snapshot;
13913 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
13914 });
13915
13916 assert_eq!(
13917 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13918 expected,
13919 "Active indent guide indices do not match"
13920 );
13921 }
13922
13923 assert_eq!(indent_guides, expected, "Indent guides do not match");
13924}
13925
13926fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13927 IndentGuide {
13928 buffer_id,
13929 start_row: MultiBufferRow(start_row),
13930 end_row: MultiBufferRow(end_row),
13931 depth,
13932 tab_size: 4,
13933 settings: IndentGuideSettings {
13934 enabled: true,
13935 line_width: 1,
13936 active_line_width: 1,
13937 ..Default::default()
13938 },
13939 }
13940}
13941
13942#[gpui::test]
13943async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13944 let (buffer_id, mut cx) = setup_indent_guides_editor(
13945 &"
13946 fn main() {
13947 let a = 1;
13948 }"
13949 .unindent(),
13950 cx,
13951 )
13952 .await;
13953
13954 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13955}
13956
13957#[gpui::test]
13958async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13959 let (buffer_id, mut cx) = setup_indent_guides_editor(
13960 &"
13961 fn main() {
13962 let a = 1;
13963 let b = 2;
13964 }"
13965 .unindent(),
13966 cx,
13967 )
13968 .await;
13969
13970 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13971}
13972
13973#[gpui::test]
13974async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13975 let (buffer_id, mut cx) = setup_indent_guides_editor(
13976 &"
13977 fn main() {
13978 let a = 1;
13979 if a == 3 {
13980 let b = 2;
13981 } else {
13982 let c = 3;
13983 }
13984 }"
13985 .unindent(),
13986 cx,
13987 )
13988 .await;
13989
13990 assert_indent_guides(
13991 0..8,
13992 vec![
13993 indent_guide(buffer_id, 1, 6, 0),
13994 indent_guide(buffer_id, 3, 3, 1),
13995 indent_guide(buffer_id, 5, 5, 1),
13996 ],
13997 None,
13998 &mut cx,
13999 );
14000}
14001
14002#[gpui::test]
14003async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
14004 let (buffer_id, mut cx) = setup_indent_guides_editor(
14005 &"
14006 fn main() {
14007 let a = 1;
14008 let b = 2;
14009 let c = 3;
14010 }"
14011 .unindent(),
14012 cx,
14013 )
14014 .await;
14015
14016 assert_indent_guides(
14017 0..5,
14018 vec![
14019 indent_guide(buffer_id, 1, 3, 0),
14020 indent_guide(buffer_id, 2, 2, 1),
14021 ],
14022 None,
14023 &mut cx,
14024 );
14025}
14026
14027#[gpui::test]
14028async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
14029 let (buffer_id, mut cx) = setup_indent_guides_editor(
14030 &"
14031 fn main() {
14032 let a = 1;
14033
14034 let c = 3;
14035 }"
14036 .unindent(),
14037 cx,
14038 )
14039 .await;
14040
14041 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14042}
14043
14044#[gpui::test]
14045async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
14046 let (buffer_id, mut cx) = setup_indent_guides_editor(
14047 &"
14048 fn main() {
14049 let a = 1;
14050
14051 let c = 3;
14052
14053 if a == 3 {
14054 let b = 2;
14055 } else {
14056 let c = 3;
14057 }
14058 }"
14059 .unindent(),
14060 cx,
14061 )
14062 .await;
14063
14064 assert_indent_guides(
14065 0..11,
14066 vec![
14067 indent_guide(buffer_id, 1, 9, 0),
14068 indent_guide(buffer_id, 6, 6, 1),
14069 indent_guide(buffer_id, 8, 8, 1),
14070 ],
14071 None,
14072 &mut cx,
14073 );
14074}
14075
14076#[gpui::test]
14077async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
14078 let (buffer_id, mut cx) = setup_indent_guides_editor(
14079 &"
14080 fn main() {
14081 let a = 1;
14082
14083 let c = 3;
14084
14085 if a == 3 {
14086 let b = 2;
14087 } else {
14088 let c = 3;
14089 }
14090 }"
14091 .unindent(),
14092 cx,
14093 )
14094 .await;
14095
14096 assert_indent_guides(
14097 1..11,
14098 vec![
14099 indent_guide(buffer_id, 1, 9, 0),
14100 indent_guide(buffer_id, 6, 6, 1),
14101 indent_guide(buffer_id, 8, 8, 1),
14102 ],
14103 None,
14104 &mut cx,
14105 );
14106}
14107
14108#[gpui::test]
14109async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
14110 let (buffer_id, mut cx) = setup_indent_guides_editor(
14111 &"
14112 fn main() {
14113 let a = 1;
14114
14115 let c = 3;
14116
14117 if a == 3 {
14118 let b = 2;
14119 } else {
14120 let c = 3;
14121 }
14122 }"
14123 .unindent(),
14124 cx,
14125 )
14126 .await;
14127
14128 assert_indent_guides(
14129 1..10,
14130 vec![
14131 indent_guide(buffer_id, 1, 9, 0),
14132 indent_guide(buffer_id, 6, 6, 1),
14133 indent_guide(buffer_id, 8, 8, 1),
14134 ],
14135 None,
14136 &mut cx,
14137 );
14138}
14139
14140#[gpui::test]
14141async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
14142 let (buffer_id, mut cx) = setup_indent_guides_editor(
14143 &"
14144 block1
14145 block2
14146 block3
14147 block4
14148 block2
14149 block1
14150 block1"
14151 .unindent(),
14152 cx,
14153 )
14154 .await;
14155
14156 assert_indent_guides(
14157 1..10,
14158 vec![
14159 indent_guide(buffer_id, 1, 4, 0),
14160 indent_guide(buffer_id, 2, 3, 1),
14161 indent_guide(buffer_id, 3, 3, 2),
14162 ],
14163 None,
14164 &mut cx,
14165 );
14166}
14167
14168#[gpui::test]
14169async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
14170 let (buffer_id, mut cx) = setup_indent_guides_editor(
14171 &"
14172 block1
14173 block2
14174 block3
14175
14176 block1
14177 block1"
14178 .unindent(),
14179 cx,
14180 )
14181 .await;
14182
14183 assert_indent_guides(
14184 0..6,
14185 vec![
14186 indent_guide(buffer_id, 1, 2, 0),
14187 indent_guide(buffer_id, 2, 2, 1),
14188 ],
14189 None,
14190 &mut cx,
14191 );
14192}
14193
14194#[gpui::test]
14195async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
14196 let (buffer_id, mut cx) = setup_indent_guides_editor(
14197 &"
14198 block1
14199
14200
14201
14202 block2
14203 "
14204 .unindent(),
14205 cx,
14206 )
14207 .await;
14208
14209 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14210}
14211
14212#[gpui::test]
14213async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
14214 let (buffer_id, mut cx) = setup_indent_guides_editor(
14215 &"
14216 def a:
14217 \tb = 3
14218 \tif True:
14219 \t\tc = 4
14220 \t\td = 5
14221 \tprint(b)
14222 "
14223 .unindent(),
14224 cx,
14225 )
14226 .await;
14227
14228 assert_indent_guides(
14229 0..6,
14230 vec![
14231 indent_guide(buffer_id, 1, 6, 0),
14232 indent_guide(buffer_id, 3, 4, 1),
14233 ],
14234 None,
14235 &mut cx,
14236 );
14237}
14238
14239#[gpui::test]
14240async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
14241 let (buffer_id, mut cx) = setup_indent_guides_editor(
14242 &"
14243 fn main() {
14244 let a = 1;
14245 }"
14246 .unindent(),
14247 cx,
14248 )
14249 .await;
14250
14251 cx.update_editor(|editor, window, cx| {
14252 editor.change_selections(None, window, cx, |s| {
14253 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14254 });
14255 });
14256
14257 assert_indent_guides(
14258 0..3,
14259 vec![indent_guide(buffer_id, 1, 1, 0)],
14260 Some(vec![0]),
14261 &mut cx,
14262 );
14263}
14264
14265#[gpui::test]
14266async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
14267 let (buffer_id, mut cx) = setup_indent_guides_editor(
14268 &"
14269 fn main() {
14270 if 1 == 2 {
14271 let a = 1;
14272 }
14273 }"
14274 .unindent(),
14275 cx,
14276 )
14277 .await;
14278
14279 cx.update_editor(|editor, window, cx| {
14280 editor.change_selections(None, window, cx, |s| {
14281 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14282 });
14283 });
14284
14285 assert_indent_guides(
14286 0..4,
14287 vec![
14288 indent_guide(buffer_id, 1, 3, 0),
14289 indent_guide(buffer_id, 2, 2, 1),
14290 ],
14291 Some(vec![1]),
14292 &mut cx,
14293 );
14294
14295 cx.update_editor(|editor, window, cx| {
14296 editor.change_selections(None, window, cx, |s| {
14297 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14298 });
14299 });
14300
14301 assert_indent_guides(
14302 0..4,
14303 vec![
14304 indent_guide(buffer_id, 1, 3, 0),
14305 indent_guide(buffer_id, 2, 2, 1),
14306 ],
14307 Some(vec![1]),
14308 &mut cx,
14309 );
14310
14311 cx.update_editor(|editor, window, cx| {
14312 editor.change_selections(None, window, cx, |s| {
14313 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14314 });
14315 });
14316
14317 assert_indent_guides(
14318 0..4,
14319 vec![
14320 indent_guide(buffer_id, 1, 3, 0),
14321 indent_guide(buffer_id, 2, 2, 1),
14322 ],
14323 Some(vec![0]),
14324 &mut cx,
14325 );
14326}
14327
14328#[gpui::test]
14329async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
14330 let (buffer_id, mut cx) = setup_indent_guides_editor(
14331 &"
14332 fn main() {
14333 let a = 1;
14334
14335 let b = 2;
14336 }"
14337 .unindent(),
14338 cx,
14339 )
14340 .await;
14341
14342 cx.update_editor(|editor, window, cx| {
14343 editor.change_selections(None, window, cx, |s| {
14344 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14345 });
14346 });
14347
14348 assert_indent_guides(
14349 0..5,
14350 vec![indent_guide(buffer_id, 1, 3, 0)],
14351 Some(vec![0]),
14352 &mut cx,
14353 );
14354}
14355
14356#[gpui::test]
14357async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
14358 let (buffer_id, mut cx) = setup_indent_guides_editor(
14359 &"
14360 def m:
14361 a = 1
14362 pass"
14363 .unindent(),
14364 cx,
14365 )
14366 .await;
14367
14368 cx.update_editor(|editor, window, cx| {
14369 editor.change_selections(None, window, cx, |s| {
14370 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14371 });
14372 });
14373
14374 assert_indent_guides(
14375 0..3,
14376 vec![indent_guide(buffer_id, 1, 2, 0)],
14377 Some(vec![0]),
14378 &mut cx,
14379 );
14380}
14381
14382#[gpui::test]
14383async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContext) {
14384 init_test(cx, |_| {});
14385 let mut cx = EditorTestContext::new(cx).await;
14386 let text = indoc! {
14387 "
14388 impl A {
14389 fn b() {
14390 0;
14391 3;
14392 5;
14393 6;
14394 7;
14395 }
14396 }
14397 "
14398 };
14399 let base_text = indoc! {
14400 "
14401 impl A {
14402 fn b() {
14403 0;
14404 1;
14405 2;
14406 3;
14407 4;
14408 }
14409 fn c() {
14410 5;
14411 6;
14412 7;
14413 }
14414 }
14415 "
14416 };
14417
14418 cx.update_editor(|editor, window, cx| {
14419 editor.set_text(text, window, cx);
14420
14421 editor.buffer().update(cx, |multibuffer, cx| {
14422 let buffer = multibuffer.as_singleton().unwrap();
14423 let change_set =
14424 cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx));
14425
14426 multibuffer.set_all_diff_hunks_expanded(cx);
14427 multibuffer.add_change_set(change_set, cx);
14428
14429 buffer.read(cx).remote_id()
14430 })
14431 });
14432 cx.run_until_parked();
14433
14434 cx.assert_state_with_diff(
14435 indoc! { "
14436 impl A {
14437 fn b() {
14438 0;
14439 - 1;
14440 - 2;
14441 3;
14442 - 4;
14443 - }
14444 - fn c() {
14445 5;
14446 6;
14447 7;
14448 }
14449 }
14450 ˇ"
14451 }
14452 .to_string(),
14453 );
14454
14455 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14456 editor
14457 .snapshot(window, cx)
14458 .buffer_snapshot
14459 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14460 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14461 .collect::<Vec<_>>()
14462 });
14463 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14464 assert_eq!(
14465 actual_guides,
14466 vec![
14467 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14468 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14469 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14470 ]
14471 );
14472}
14473
14474#[gpui::test]
14475fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
14476 init_test(cx, |_| {});
14477
14478 let editor = cx.add_window(|window, cx| {
14479 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
14480 build_editor(buffer, window, cx)
14481 });
14482
14483 let render_args = Arc::new(Mutex::new(None));
14484 let snapshot = editor
14485 .update(cx, |editor, window, cx| {
14486 let snapshot = editor.buffer().read(cx).snapshot(cx);
14487 let range =
14488 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
14489
14490 struct RenderArgs {
14491 row: MultiBufferRow,
14492 folded: bool,
14493 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
14494 }
14495
14496 let crease = Crease::inline(
14497 range,
14498 FoldPlaceholder::test(),
14499 {
14500 let toggle_callback = render_args.clone();
14501 move |row, folded, callback, _window, _cx| {
14502 *toggle_callback.lock() = Some(RenderArgs {
14503 row,
14504 folded,
14505 callback,
14506 });
14507 div()
14508 }
14509 },
14510 |_row, _folded, _window, _cx| div(),
14511 );
14512
14513 editor.insert_creases(Some(crease), cx);
14514 let snapshot = editor.snapshot(window, cx);
14515 let _div = snapshot.render_crease_toggle(
14516 MultiBufferRow(1),
14517 false,
14518 cx.entity().clone(),
14519 window,
14520 cx,
14521 );
14522 snapshot
14523 })
14524 .unwrap();
14525
14526 let render_args = render_args.lock().take().unwrap();
14527 assert_eq!(render_args.row, MultiBufferRow(1));
14528 assert!(!render_args.folded);
14529 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14530
14531 cx.update_window(*editor, |_, window, cx| {
14532 (render_args.callback)(true, window, cx)
14533 })
14534 .unwrap();
14535 let snapshot = editor
14536 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14537 .unwrap();
14538 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
14539
14540 cx.update_window(*editor, |_, window, cx| {
14541 (render_args.callback)(false, window, cx)
14542 })
14543 .unwrap();
14544 let snapshot = editor
14545 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14546 .unwrap();
14547 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14548}
14549
14550#[gpui::test]
14551async fn test_input_text(cx: &mut gpui::TestAppContext) {
14552 init_test(cx, |_| {});
14553 let mut cx = EditorTestContext::new(cx).await;
14554
14555 cx.set_state(
14556 &r#"ˇone
14557 two
14558
14559 three
14560 fourˇ
14561 five
14562
14563 siˇx"#
14564 .unindent(),
14565 );
14566
14567 cx.dispatch_action(HandleInput(String::new()));
14568 cx.assert_editor_state(
14569 &r#"ˇone
14570 two
14571
14572 three
14573 fourˇ
14574 five
14575
14576 siˇx"#
14577 .unindent(),
14578 );
14579
14580 cx.dispatch_action(HandleInput("AAAA".to_string()));
14581 cx.assert_editor_state(
14582 &r#"AAAAˇone
14583 two
14584
14585 three
14586 fourAAAAˇ
14587 five
14588
14589 siAAAAˇx"#
14590 .unindent(),
14591 );
14592}
14593
14594#[gpui::test]
14595async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
14596 init_test(cx, |_| {});
14597
14598 let mut cx = EditorTestContext::new(cx).await;
14599 cx.set_state(
14600 r#"let foo = 1;
14601let foo = 2;
14602let foo = 3;
14603let fooˇ = 4;
14604let foo = 5;
14605let foo = 6;
14606let foo = 7;
14607let foo = 8;
14608let foo = 9;
14609let foo = 10;
14610let foo = 11;
14611let foo = 12;
14612let foo = 13;
14613let foo = 14;
14614let foo = 15;"#,
14615 );
14616
14617 cx.update_editor(|e, window, cx| {
14618 assert_eq!(
14619 e.next_scroll_position,
14620 NextScrollCursorCenterTopBottom::Center,
14621 "Default next scroll direction is center",
14622 );
14623
14624 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14625 assert_eq!(
14626 e.next_scroll_position,
14627 NextScrollCursorCenterTopBottom::Top,
14628 "After center, next scroll direction should be top",
14629 );
14630
14631 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14632 assert_eq!(
14633 e.next_scroll_position,
14634 NextScrollCursorCenterTopBottom::Bottom,
14635 "After top, next scroll direction should be bottom",
14636 );
14637
14638 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14639 assert_eq!(
14640 e.next_scroll_position,
14641 NextScrollCursorCenterTopBottom::Center,
14642 "After bottom, scrolling should start over",
14643 );
14644
14645 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14646 assert_eq!(
14647 e.next_scroll_position,
14648 NextScrollCursorCenterTopBottom::Top,
14649 "Scrolling continues if retriggered fast enough"
14650 );
14651 });
14652
14653 cx.executor()
14654 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
14655 cx.executor().run_until_parked();
14656 cx.update_editor(|e, _, _| {
14657 assert_eq!(
14658 e.next_scroll_position,
14659 NextScrollCursorCenterTopBottom::Center,
14660 "If scrolling is not triggered fast enough, it should reset"
14661 );
14662 });
14663}
14664
14665#[gpui::test]
14666async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
14667 init_test(cx, |_| {});
14668 let mut cx = EditorLspTestContext::new_rust(
14669 lsp::ServerCapabilities {
14670 definition_provider: Some(lsp::OneOf::Left(true)),
14671 references_provider: Some(lsp::OneOf::Left(true)),
14672 ..lsp::ServerCapabilities::default()
14673 },
14674 cx,
14675 )
14676 .await;
14677
14678 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
14679 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
14680 move |params, _| async move {
14681 if empty_go_to_definition {
14682 Ok(None)
14683 } else {
14684 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
14685 uri: params.text_document_position_params.text_document.uri,
14686 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
14687 })))
14688 }
14689 },
14690 );
14691 let references =
14692 cx.lsp
14693 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
14694 Ok(Some(vec![lsp::Location {
14695 uri: params.text_document_position.text_document.uri,
14696 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
14697 }]))
14698 });
14699 (go_to_definition, references)
14700 };
14701
14702 cx.set_state(
14703 &r#"fn one() {
14704 let mut a = ˇtwo();
14705 }
14706
14707 fn two() {}"#
14708 .unindent(),
14709 );
14710 set_up_lsp_handlers(false, &mut cx);
14711 let navigated = cx
14712 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
14713 .await
14714 .expect("Failed to navigate to definition");
14715 assert_eq!(
14716 navigated,
14717 Navigated::Yes,
14718 "Should have navigated to definition from the GetDefinition response"
14719 );
14720 cx.assert_editor_state(
14721 &r#"fn one() {
14722 let mut a = two();
14723 }
14724
14725 fn «twoˇ»() {}"#
14726 .unindent(),
14727 );
14728
14729 let editors = cx.update_workspace(|workspace, _, cx| {
14730 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14731 });
14732 cx.update_editor(|_, _, test_editor_cx| {
14733 assert_eq!(
14734 editors.len(),
14735 1,
14736 "Initially, only one, test, editor should be open in the workspace"
14737 );
14738 assert_eq!(
14739 test_editor_cx.entity(),
14740 editors.last().expect("Asserted len is 1").clone()
14741 );
14742 });
14743
14744 set_up_lsp_handlers(true, &mut cx);
14745 let navigated = cx
14746 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
14747 .await
14748 .expect("Failed to navigate to lookup references");
14749 assert_eq!(
14750 navigated,
14751 Navigated::Yes,
14752 "Should have navigated to references as a fallback after empty GoToDefinition response"
14753 );
14754 // We should not change the selections in the existing file,
14755 // if opening another milti buffer with the references
14756 cx.assert_editor_state(
14757 &r#"fn one() {
14758 let mut a = two();
14759 }
14760
14761 fn «twoˇ»() {}"#
14762 .unindent(),
14763 );
14764 let editors = cx.update_workspace(|workspace, _, cx| {
14765 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14766 });
14767 cx.update_editor(|_, _, test_editor_cx| {
14768 assert_eq!(
14769 editors.len(),
14770 2,
14771 "After falling back to references search, we open a new editor with the results"
14772 );
14773 let references_fallback_text = editors
14774 .into_iter()
14775 .find(|new_editor| *new_editor != test_editor_cx.entity())
14776 .expect("Should have one non-test editor now")
14777 .read(test_editor_cx)
14778 .text(test_editor_cx);
14779 assert_eq!(
14780 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14781 "Should use the range from the references response and not the GoToDefinition one"
14782 );
14783 });
14784}
14785
14786#[gpui::test]
14787async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14788 init_test(cx, |_| {});
14789
14790 let language = Arc::new(Language::new(
14791 LanguageConfig::default(),
14792 Some(tree_sitter_rust::LANGUAGE.into()),
14793 ));
14794
14795 let text = r#"
14796 #[cfg(test)]
14797 mod tests() {
14798 #[test]
14799 fn runnable_1() {
14800 let a = 1;
14801 }
14802
14803 #[test]
14804 fn runnable_2() {
14805 let a = 1;
14806 let b = 2;
14807 }
14808 }
14809 "#
14810 .unindent();
14811
14812 let fs = FakeFs::new(cx.executor());
14813 fs.insert_file("/file.rs", Default::default()).await;
14814
14815 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14816 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14817 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14818 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14819 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14820
14821 let editor = cx.new_window_entity(|window, cx| {
14822 Editor::new(
14823 EditorMode::Full,
14824 multi_buffer,
14825 Some(project.clone()),
14826 true,
14827 window,
14828 cx,
14829 )
14830 });
14831
14832 editor.update_in(cx, |editor, window, cx| {
14833 editor.tasks.insert(
14834 (buffer.read(cx).remote_id(), 3),
14835 RunnableTasks {
14836 templates: vec![],
14837 offset: MultiBufferOffset(43),
14838 column: 0,
14839 extra_variables: HashMap::default(),
14840 context_range: BufferOffset(43)..BufferOffset(85),
14841 },
14842 );
14843 editor.tasks.insert(
14844 (buffer.read(cx).remote_id(), 8),
14845 RunnableTasks {
14846 templates: vec![],
14847 offset: MultiBufferOffset(86),
14848 column: 0,
14849 extra_variables: HashMap::default(),
14850 context_range: BufferOffset(86)..BufferOffset(191),
14851 },
14852 );
14853
14854 // Test finding task when cursor is inside function body
14855 editor.change_selections(None, window, cx, |s| {
14856 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14857 });
14858 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14859 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14860
14861 // Test finding task when cursor is on function name
14862 editor.change_selections(None, window, cx, |s| {
14863 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14864 });
14865 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14866 assert_eq!(row, 8, "Should find task when cursor is on function name");
14867 });
14868}
14869
14870#[gpui::test]
14871async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14872 init_test(cx, |_| {});
14873
14874 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14875 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14876 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14877
14878 let fs = FakeFs::new(cx.executor());
14879 fs.insert_tree(
14880 "/a",
14881 json!({
14882 "first.rs": sample_text_1,
14883 "second.rs": sample_text_2,
14884 "third.rs": sample_text_3,
14885 }),
14886 )
14887 .await;
14888 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14889 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14890 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14891 let worktree = project.update(cx, |project, cx| {
14892 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14893 assert_eq!(worktrees.len(), 1);
14894 worktrees.pop().unwrap()
14895 });
14896 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14897
14898 let buffer_1 = project
14899 .update(cx, |project, cx| {
14900 project.open_buffer((worktree_id, "first.rs"), cx)
14901 })
14902 .await
14903 .unwrap();
14904 let buffer_2 = project
14905 .update(cx, |project, cx| {
14906 project.open_buffer((worktree_id, "second.rs"), cx)
14907 })
14908 .await
14909 .unwrap();
14910 let buffer_3 = project
14911 .update(cx, |project, cx| {
14912 project.open_buffer((worktree_id, "third.rs"), cx)
14913 })
14914 .await
14915 .unwrap();
14916
14917 let multi_buffer = cx.new(|cx| {
14918 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14919 multi_buffer.push_excerpts(
14920 buffer_1.clone(),
14921 [
14922 ExcerptRange {
14923 context: Point::new(0, 0)..Point::new(3, 0),
14924 primary: None,
14925 },
14926 ExcerptRange {
14927 context: Point::new(5, 0)..Point::new(7, 0),
14928 primary: None,
14929 },
14930 ExcerptRange {
14931 context: Point::new(9, 0)..Point::new(10, 4),
14932 primary: None,
14933 },
14934 ],
14935 cx,
14936 );
14937 multi_buffer.push_excerpts(
14938 buffer_2.clone(),
14939 [
14940 ExcerptRange {
14941 context: Point::new(0, 0)..Point::new(3, 0),
14942 primary: None,
14943 },
14944 ExcerptRange {
14945 context: Point::new(5, 0)..Point::new(7, 0),
14946 primary: None,
14947 },
14948 ExcerptRange {
14949 context: Point::new(9, 0)..Point::new(10, 4),
14950 primary: None,
14951 },
14952 ],
14953 cx,
14954 );
14955 multi_buffer.push_excerpts(
14956 buffer_3.clone(),
14957 [
14958 ExcerptRange {
14959 context: Point::new(0, 0)..Point::new(3, 0),
14960 primary: None,
14961 },
14962 ExcerptRange {
14963 context: Point::new(5, 0)..Point::new(7, 0),
14964 primary: None,
14965 },
14966 ExcerptRange {
14967 context: Point::new(9, 0)..Point::new(10, 4),
14968 primary: None,
14969 },
14970 ],
14971 cx,
14972 );
14973 multi_buffer
14974 });
14975 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14976 Editor::new(
14977 EditorMode::Full,
14978 multi_buffer,
14979 Some(project.clone()),
14980 true,
14981 window,
14982 cx,
14983 )
14984 });
14985
14986 let full_text = "\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";
14987 assert_eq!(
14988 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14989 full_text,
14990 );
14991
14992 multi_buffer_editor.update(cx, |editor, cx| {
14993 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14994 });
14995 assert_eq!(
14996 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14997 "\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",
14998 "After folding the first buffer, its text should not be displayed"
14999 );
15000
15001 multi_buffer_editor.update(cx, |editor, cx| {
15002 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15003 });
15004 assert_eq!(
15005 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15006 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15007 "After folding the second buffer, its text should not be displayed"
15008 );
15009
15010 multi_buffer_editor.update(cx, |editor, cx| {
15011 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15012 });
15013 assert_eq!(
15014 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15015 "\n\n\n\n\n",
15016 "After folding the third buffer, its text should not be displayed"
15017 );
15018
15019 // Emulate selection inside the fold logic, that should work
15020 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15021 editor
15022 .snapshot(window, cx)
15023 .next_line_boundary(Point::new(0, 4));
15024 });
15025
15026 multi_buffer_editor.update(cx, |editor, cx| {
15027 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15028 });
15029 assert_eq!(
15030 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15031 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15032 "After unfolding the second buffer, its text should be displayed"
15033 );
15034
15035 multi_buffer_editor.update(cx, |editor, cx| {
15036 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15037 });
15038 assert_eq!(
15039 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15040 "\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",
15041 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15042 );
15043
15044 multi_buffer_editor.update(cx, |editor, cx| {
15045 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15046 });
15047 assert_eq!(
15048 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15049 full_text,
15050 "After unfolding the all buffers, all original text should be displayed"
15051 );
15052}
15053
15054#[gpui::test]
15055async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
15056 init_test(cx, |_| {});
15057
15058 let sample_text_1 = "1111\n2222\n3333".to_string();
15059 let sample_text_2 = "4444\n5555\n6666".to_string();
15060 let sample_text_3 = "7777\n8888\n9999".to_string();
15061
15062 let fs = FakeFs::new(cx.executor());
15063 fs.insert_tree(
15064 "/a",
15065 json!({
15066 "first.rs": sample_text_1,
15067 "second.rs": sample_text_2,
15068 "third.rs": sample_text_3,
15069 }),
15070 )
15071 .await;
15072 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15073 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15074 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15075 let worktree = project.update(cx, |project, cx| {
15076 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15077 assert_eq!(worktrees.len(), 1);
15078 worktrees.pop().unwrap()
15079 });
15080 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15081
15082 let buffer_1 = project
15083 .update(cx, |project, cx| {
15084 project.open_buffer((worktree_id, "first.rs"), cx)
15085 })
15086 .await
15087 .unwrap();
15088 let buffer_2 = project
15089 .update(cx, |project, cx| {
15090 project.open_buffer((worktree_id, "second.rs"), cx)
15091 })
15092 .await
15093 .unwrap();
15094 let buffer_3 = project
15095 .update(cx, |project, cx| {
15096 project.open_buffer((worktree_id, "third.rs"), cx)
15097 })
15098 .await
15099 .unwrap();
15100
15101 let multi_buffer = cx.new(|cx| {
15102 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15103 multi_buffer.push_excerpts(
15104 buffer_1.clone(),
15105 [ExcerptRange {
15106 context: Point::new(0, 0)..Point::new(3, 0),
15107 primary: None,
15108 }],
15109 cx,
15110 );
15111 multi_buffer.push_excerpts(
15112 buffer_2.clone(),
15113 [ExcerptRange {
15114 context: Point::new(0, 0)..Point::new(3, 0),
15115 primary: None,
15116 }],
15117 cx,
15118 );
15119 multi_buffer.push_excerpts(
15120 buffer_3.clone(),
15121 [ExcerptRange {
15122 context: Point::new(0, 0)..Point::new(3, 0),
15123 primary: None,
15124 }],
15125 cx,
15126 );
15127 multi_buffer
15128 });
15129
15130 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15131 Editor::new(
15132 EditorMode::Full,
15133 multi_buffer,
15134 Some(project.clone()),
15135 true,
15136 window,
15137 cx,
15138 )
15139 });
15140
15141 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
15142 assert_eq!(
15143 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15144 full_text,
15145 );
15146
15147 multi_buffer_editor.update(cx, |editor, cx| {
15148 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15149 });
15150 assert_eq!(
15151 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15152 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
15153 "After folding the first buffer, its text should not be displayed"
15154 );
15155
15156 multi_buffer_editor.update(cx, |editor, cx| {
15157 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15158 });
15159
15160 assert_eq!(
15161 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15162 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
15163 "After folding the second buffer, its text should not be displayed"
15164 );
15165
15166 multi_buffer_editor.update(cx, |editor, cx| {
15167 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15168 });
15169 assert_eq!(
15170 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15171 "\n\n\n\n\n",
15172 "After folding the third buffer, its text should not be displayed"
15173 );
15174
15175 multi_buffer_editor.update(cx, |editor, cx| {
15176 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15177 });
15178 assert_eq!(
15179 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15180 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
15181 "After unfolding the second buffer, its text should be displayed"
15182 );
15183
15184 multi_buffer_editor.update(cx, |editor, cx| {
15185 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15186 });
15187 assert_eq!(
15188 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15189 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
15190 "After unfolding the first buffer, its text should be displayed"
15191 );
15192
15193 multi_buffer_editor.update(cx, |editor, cx| {
15194 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15195 });
15196 assert_eq!(
15197 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15198 full_text,
15199 "After unfolding all buffers, all original text should be displayed"
15200 );
15201}
15202
15203#[gpui::test]
15204async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
15205 init_test(cx, |_| {});
15206
15207 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15208
15209 let fs = FakeFs::new(cx.executor());
15210 fs.insert_tree(
15211 "/a",
15212 json!({
15213 "main.rs": sample_text,
15214 }),
15215 )
15216 .await;
15217 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15218 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15219 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15220 let worktree = project.update(cx, |project, cx| {
15221 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15222 assert_eq!(worktrees.len(), 1);
15223 worktrees.pop().unwrap()
15224 });
15225 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15226
15227 let buffer_1 = project
15228 .update(cx, |project, cx| {
15229 project.open_buffer((worktree_id, "main.rs"), cx)
15230 })
15231 .await
15232 .unwrap();
15233
15234 let multi_buffer = cx.new(|cx| {
15235 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15236 multi_buffer.push_excerpts(
15237 buffer_1.clone(),
15238 [ExcerptRange {
15239 context: Point::new(0, 0)
15240 ..Point::new(
15241 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
15242 0,
15243 ),
15244 primary: None,
15245 }],
15246 cx,
15247 );
15248 multi_buffer
15249 });
15250 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15251 Editor::new(
15252 EditorMode::Full,
15253 multi_buffer,
15254 Some(project.clone()),
15255 true,
15256 window,
15257 cx,
15258 )
15259 });
15260
15261 let selection_range = Point::new(1, 0)..Point::new(2, 0);
15262 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15263 enum TestHighlight {}
15264 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
15265 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
15266 editor.highlight_text::<TestHighlight>(
15267 vec![highlight_range.clone()],
15268 HighlightStyle::color(Hsla::green()),
15269 cx,
15270 );
15271 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
15272 });
15273
15274 let full_text = format!("\n\n\n{sample_text}\n");
15275 assert_eq!(
15276 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15277 full_text,
15278 );
15279}
15280
15281#[gpui::test]
15282async fn test_inline_completion_text(cx: &mut TestAppContext) {
15283 init_test(cx, |_| {});
15284
15285 // Simple insertion
15286 assert_highlighted_edits(
15287 "Hello, world!",
15288 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
15289 true,
15290 cx,
15291 |highlighted_edits, cx| {
15292 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
15293 assert_eq!(highlighted_edits.highlights.len(), 1);
15294 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
15295 assert_eq!(
15296 highlighted_edits.highlights[0].1.background_color,
15297 Some(cx.theme().status().created_background)
15298 );
15299 },
15300 )
15301 .await;
15302
15303 // Replacement
15304 assert_highlighted_edits(
15305 "This is a test.",
15306 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
15307 false,
15308 cx,
15309 |highlighted_edits, cx| {
15310 assert_eq!(highlighted_edits.text, "That is a test.");
15311 assert_eq!(highlighted_edits.highlights.len(), 1);
15312 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
15313 assert_eq!(
15314 highlighted_edits.highlights[0].1.background_color,
15315 Some(cx.theme().status().created_background)
15316 );
15317 },
15318 )
15319 .await;
15320
15321 // Multiple edits
15322 assert_highlighted_edits(
15323 "Hello, world!",
15324 vec![
15325 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
15326 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
15327 ],
15328 false,
15329 cx,
15330 |highlighted_edits, cx| {
15331 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
15332 assert_eq!(highlighted_edits.highlights.len(), 2);
15333 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
15334 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
15335 assert_eq!(
15336 highlighted_edits.highlights[0].1.background_color,
15337 Some(cx.theme().status().created_background)
15338 );
15339 assert_eq!(
15340 highlighted_edits.highlights[1].1.background_color,
15341 Some(cx.theme().status().created_background)
15342 );
15343 },
15344 )
15345 .await;
15346
15347 // Multiple lines with edits
15348 assert_highlighted_edits(
15349 "First line\nSecond line\nThird line\nFourth line",
15350 vec![
15351 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
15352 (
15353 Point::new(2, 0)..Point::new(2, 10),
15354 "New third line".to_string(),
15355 ),
15356 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
15357 ],
15358 false,
15359 cx,
15360 |highlighted_edits, cx| {
15361 assert_eq!(
15362 highlighted_edits.text,
15363 "Second modified\nNew third line\nFourth updated line"
15364 );
15365 assert_eq!(highlighted_edits.highlights.len(), 3);
15366 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
15367 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
15368 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
15369 for highlight in &highlighted_edits.highlights {
15370 assert_eq!(
15371 highlight.1.background_color,
15372 Some(cx.theme().status().created_background)
15373 );
15374 }
15375 },
15376 )
15377 .await;
15378}
15379
15380#[gpui::test]
15381async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
15382 init_test(cx, |_| {});
15383
15384 // Deletion
15385 assert_highlighted_edits(
15386 "Hello, world!",
15387 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
15388 true,
15389 cx,
15390 |highlighted_edits, cx| {
15391 assert_eq!(highlighted_edits.text, "Hello, world!");
15392 assert_eq!(highlighted_edits.highlights.len(), 1);
15393 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
15394 assert_eq!(
15395 highlighted_edits.highlights[0].1.background_color,
15396 Some(cx.theme().status().deleted_background)
15397 );
15398 },
15399 )
15400 .await;
15401
15402 // Insertion
15403 assert_highlighted_edits(
15404 "Hello, world!",
15405 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
15406 true,
15407 cx,
15408 |highlighted_edits, cx| {
15409 assert_eq!(highlighted_edits.highlights.len(), 1);
15410 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
15411 assert_eq!(
15412 highlighted_edits.highlights[0].1.background_color,
15413 Some(cx.theme().status().created_background)
15414 );
15415 },
15416 )
15417 .await;
15418}
15419
15420async fn assert_highlighted_edits(
15421 text: &str,
15422 edits: Vec<(Range<Point>, String)>,
15423 include_deletions: bool,
15424 cx: &mut TestAppContext,
15425 assertion_fn: impl Fn(HighlightedText, &App),
15426) {
15427 let window = cx.add_window(|window, cx| {
15428 let buffer = MultiBuffer::build_simple(text, cx);
15429 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
15430 });
15431 let cx = &mut VisualTestContext::from_window(*window, cx);
15432
15433 let (buffer, snapshot) = window
15434 .update(cx, |editor, _window, cx| {
15435 (
15436 editor.buffer().clone(),
15437 editor.buffer().read(cx).snapshot(cx),
15438 )
15439 })
15440 .unwrap();
15441
15442 let edits = edits
15443 .into_iter()
15444 .map(|(range, edit)| {
15445 (
15446 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
15447 edit,
15448 )
15449 })
15450 .collect::<Vec<_>>();
15451
15452 let text_anchor_edits = edits
15453 .clone()
15454 .into_iter()
15455 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
15456 .collect::<Vec<_>>();
15457
15458 let edit_preview = window
15459 .update(cx, |_, _window, cx| {
15460 buffer
15461 .read(cx)
15462 .as_singleton()
15463 .unwrap()
15464 .read(cx)
15465 .preview_edits(text_anchor_edits.into(), cx)
15466 })
15467 .unwrap()
15468 .await;
15469
15470 cx.update(|_window, cx| {
15471 let highlighted_edits = inline_completion_edit_text(
15472 &snapshot.as_singleton().unwrap().2,
15473 &edits,
15474 &edit_preview,
15475 include_deletions,
15476 cx,
15477 );
15478 assertion_fn(highlighted_edits, cx)
15479 });
15480}
15481
15482#[gpui::test]
15483async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
15484 init_test(cx, |_| {});
15485 let capabilities = lsp::ServerCapabilities {
15486 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
15487 prepare_provider: Some(true),
15488 work_done_progress_options: Default::default(),
15489 })),
15490 ..Default::default()
15491 };
15492 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15493
15494 cx.set_state(indoc! {"
15495 struct Fˇoo {}
15496 "});
15497
15498 cx.update_editor(|editor, _, cx| {
15499 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15500 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15501 editor.highlight_background::<DocumentHighlightRead>(
15502 &[highlight_range],
15503 |c| c.editor_document_highlight_read_background,
15504 cx,
15505 );
15506 });
15507
15508 let mut prepare_rename_handler =
15509 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
15510 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
15511 start: lsp::Position {
15512 line: 0,
15513 character: 7,
15514 },
15515 end: lsp::Position {
15516 line: 0,
15517 character: 10,
15518 },
15519 })))
15520 });
15521 let prepare_rename_task = cx
15522 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15523 .expect("Prepare rename was not started");
15524 prepare_rename_handler.next().await.unwrap();
15525 prepare_rename_task.await.expect("Prepare rename failed");
15526
15527 let mut rename_handler =
15528 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15529 let edit = lsp::TextEdit {
15530 range: lsp::Range {
15531 start: lsp::Position {
15532 line: 0,
15533 character: 7,
15534 },
15535 end: lsp::Position {
15536 line: 0,
15537 character: 10,
15538 },
15539 },
15540 new_text: "FooRenamed".to_string(),
15541 };
15542 Ok(Some(lsp::WorkspaceEdit::new(
15543 // Specify the same edit twice
15544 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
15545 )))
15546 });
15547 let rename_task = cx
15548 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15549 .expect("Confirm rename was not started");
15550 rename_handler.next().await.unwrap();
15551 rename_task.await.expect("Confirm rename failed");
15552 cx.run_until_parked();
15553
15554 // Despite two edits, only one is actually applied as those are identical
15555 cx.assert_editor_state(indoc! {"
15556 struct FooRenamedˇ {}
15557 "});
15558}
15559
15560#[gpui::test]
15561async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
15562 init_test(cx, |_| {});
15563 // These capabilities indicate that the server does not support prepare rename.
15564 let capabilities = lsp::ServerCapabilities {
15565 rename_provider: Some(lsp::OneOf::Left(true)),
15566 ..Default::default()
15567 };
15568 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15569
15570 cx.set_state(indoc! {"
15571 struct Fˇoo {}
15572 "});
15573
15574 cx.update_editor(|editor, _window, cx| {
15575 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15576 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15577 editor.highlight_background::<DocumentHighlightRead>(
15578 &[highlight_range],
15579 |c| c.editor_document_highlight_read_background,
15580 cx,
15581 );
15582 });
15583
15584 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15585 .expect("Prepare rename was not started")
15586 .await
15587 .expect("Prepare rename failed");
15588
15589 let mut rename_handler =
15590 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15591 let edit = lsp::TextEdit {
15592 range: lsp::Range {
15593 start: lsp::Position {
15594 line: 0,
15595 character: 7,
15596 },
15597 end: lsp::Position {
15598 line: 0,
15599 character: 10,
15600 },
15601 },
15602 new_text: "FooRenamed".to_string(),
15603 };
15604 Ok(Some(lsp::WorkspaceEdit::new(
15605 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
15606 )))
15607 });
15608 let rename_task = cx
15609 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15610 .expect("Confirm rename was not started");
15611 rename_handler.next().await.unwrap();
15612 rename_task.await.expect("Confirm rename failed");
15613 cx.run_until_parked();
15614
15615 // Correct range is renamed, as `surrounding_word` is used to find it.
15616 cx.assert_editor_state(indoc! {"
15617 struct FooRenamedˇ {}
15618 "});
15619}
15620
15621fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
15622 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
15623 point..point
15624}
15625
15626fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
15627 let (text, ranges) = marked_text_ranges(marked_text, true);
15628 assert_eq!(editor.text(cx), text);
15629 assert_eq!(
15630 editor.selections.ranges(cx),
15631 ranges,
15632 "Assert selections are {}",
15633 marked_text
15634 );
15635}
15636
15637pub fn handle_signature_help_request(
15638 cx: &mut EditorLspTestContext,
15639 mocked_response: lsp::SignatureHelp,
15640) -> impl Future<Output = ()> {
15641 let mut request =
15642 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
15643 let mocked_response = mocked_response.clone();
15644 async move { Ok(Some(mocked_response)) }
15645 });
15646
15647 async move {
15648 request.next().await;
15649 }
15650}
15651
15652/// Handle completion request passing a marked string specifying where the completion
15653/// should be triggered from using '|' character, what range should be replaced, and what completions
15654/// should be returned using '<' and '>' to delimit the range
15655pub fn handle_completion_request(
15656 cx: &mut EditorLspTestContext,
15657 marked_string: &str,
15658 completions: Vec<&'static str>,
15659 counter: Arc<AtomicUsize>,
15660) -> impl Future<Output = ()> {
15661 let complete_from_marker: TextRangeMarker = '|'.into();
15662 let replace_range_marker: TextRangeMarker = ('<', '>').into();
15663 let (_, mut marked_ranges) = marked_text_ranges_by(
15664 marked_string,
15665 vec![complete_from_marker.clone(), replace_range_marker.clone()],
15666 );
15667
15668 let complete_from_position =
15669 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
15670 let replace_range =
15671 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
15672
15673 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
15674 let completions = completions.clone();
15675 counter.fetch_add(1, atomic::Ordering::Release);
15676 async move {
15677 assert_eq!(params.text_document_position.text_document.uri, url.clone());
15678 assert_eq!(
15679 params.text_document_position.position,
15680 complete_from_position
15681 );
15682 Ok(Some(lsp::CompletionResponse::Array(
15683 completions
15684 .iter()
15685 .map(|completion_text| lsp::CompletionItem {
15686 label: completion_text.to_string(),
15687 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15688 range: replace_range,
15689 new_text: completion_text.to_string(),
15690 })),
15691 ..Default::default()
15692 })
15693 .collect(),
15694 )))
15695 }
15696 });
15697
15698 async move {
15699 request.next().await;
15700 }
15701}
15702
15703fn handle_resolve_completion_request(
15704 cx: &mut EditorLspTestContext,
15705 edits: Option<Vec<(&'static str, &'static str)>>,
15706) -> impl Future<Output = ()> {
15707 let edits = edits.map(|edits| {
15708 edits
15709 .iter()
15710 .map(|(marked_string, new_text)| {
15711 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
15712 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
15713 lsp::TextEdit::new(replace_range, new_text.to_string())
15714 })
15715 .collect::<Vec<_>>()
15716 });
15717
15718 let mut request =
15719 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15720 let edits = edits.clone();
15721 async move {
15722 Ok(lsp::CompletionItem {
15723 additional_text_edits: edits,
15724 ..Default::default()
15725 })
15726 }
15727 });
15728
15729 async move {
15730 request.next().await;
15731 }
15732}
15733
15734pub(crate) fn update_test_language_settings(
15735 cx: &mut TestAppContext,
15736 f: impl Fn(&mut AllLanguageSettingsContent),
15737) {
15738 cx.update(|cx| {
15739 SettingsStore::update_global(cx, |store, cx| {
15740 store.update_user_settings::<AllLanguageSettings>(cx, f);
15741 });
15742 });
15743}
15744
15745pub(crate) fn update_test_project_settings(
15746 cx: &mut TestAppContext,
15747 f: impl Fn(&mut ProjectSettings),
15748) {
15749 cx.update(|cx| {
15750 SettingsStore::update_global(cx, |store, cx| {
15751 store.update_user_settings::<ProjectSettings>(cx, f);
15752 });
15753 });
15754}
15755
15756pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
15757 cx.update(|cx| {
15758 assets::Assets.load_test_fonts(cx);
15759 let store = SettingsStore::test(cx);
15760 cx.set_global(store);
15761 theme::init(theme::LoadThemes::JustBase, cx);
15762 release_channel::init(SemanticVersion::default(), cx);
15763 client::init_settings(cx);
15764 language::init(cx);
15765 Project::init_settings(cx);
15766 workspace::init_settings(cx);
15767 crate::init(cx);
15768 });
15769
15770 update_test_language_settings(cx, f);
15771}
15772
15773#[track_caller]
15774fn assert_hunk_revert(
15775 not_reverted_text_with_selections: &str,
15776 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
15777 expected_reverted_text_with_selections: &str,
15778 base_text: &str,
15779 cx: &mut EditorLspTestContext,
15780) {
15781 cx.set_state(not_reverted_text_with_selections);
15782 cx.set_diff_base(base_text);
15783 cx.executor().run_until_parked();
15784
15785 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
15786 let snapshot = editor.snapshot(window, cx);
15787 let reverted_hunk_statuses = snapshot
15788 .buffer_snapshot
15789 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
15790 .map(|hunk| hunk.status())
15791 .collect::<Vec<_>>();
15792
15793 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
15794 reverted_hunk_statuses
15795 });
15796 cx.executor().run_until_parked();
15797 cx.assert_editor_state(expected_reverted_text_with_selections);
15798 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
15799}