1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
20 LanguageSettingsContent, PrettierSettings,
21 },
22 BracketPairConfig,
23 Capability::ReadWrite,
24 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
25 Override, Point,
26};
27use language_settings::{Formatter, FormatterList, IndentGuideSettings};
28use multi_buffer::{IndentGuide, PathKey};
29use parking_lot::Mutex;
30use pretty_assertions::{assert_eq, assert_ne};
31use project::project_settings::{LspSettings, ProjectSettings};
32use project::FakeFs;
33use serde_json::{self, json};
34use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
35use std::{
36 iter,
37 sync::atomic::{self, AtomicUsize},
38};
39use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
40use text::ToPoint as _;
41use unindent::Unindent;
42use util::{
43 assert_set_eq, path,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45 uri,
46};
47use workspace::{
48 item::{FollowEvent, FollowableItem, Item, ItemHandle},
49 NavigationEntry, ViewId,
50};
51
52#[gpui::test]
53fn test_edit_events(cx: &mut TestAppContext) {
54 init_test(cx, |_| {});
55
56 let buffer = cx.new(|cx| {
57 let mut buffer = language::Buffer::local("123456", cx);
58 buffer.set_group_interval(Duration::from_secs(1));
59 buffer
60 });
61
62 let events = Rc::new(RefCell::new(Vec::new()));
63 let editor1 = cx.add_window({
64 let events = events.clone();
65 |window, cx| {
66 let entity = cx.entity().clone();
67 cx.subscribe_in(
68 &entity,
69 window,
70 move |_, _, event: &EditorEvent, _, _| match event {
71 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
72 EditorEvent::BufferEdited => {
73 events.borrow_mut().push(("editor1", "buffer edited"))
74 }
75 _ => {}
76 },
77 )
78 .detach();
79 Editor::for_buffer(buffer.clone(), None, window, cx)
80 }
81 });
82
83 let editor2 = cx.add_window({
84 let events = events.clone();
85 |window, cx| {
86 cx.subscribe_in(
87 &cx.entity().clone(),
88 window,
89 move |_, _, event: &EditorEvent, _, _| match event {
90 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
91 EditorEvent::BufferEdited => {
92 events.borrow_mut().push(("editor2", "buffer edited"))
93 }
94 _ => {}
95 },
96 )
97 .detach();
98 Editor::for_buffer(buffer.clone(), None, window, cx)
99 }
100 });
101
102 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
103
104 // Mutating editor 1 will emit an `Edited` event only for that editor.
105 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor1", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Mutating editor 2 will emit an `Edited` event only for that editor.
116 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor2", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Undoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Redoing on editor 1 will emit an `Edited` event only for that editor.
138 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor1", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Undoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // Redoing on editor 2 will emit an `Edited` event only for that editor.
160 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
161 assert_eq!(
162 mem::take(&mut *events.borrow_mut()),
163 [
164 ("editor2", "edited"),
165 ("editor1", "buffer edited"),
166 ("editor2", "buffer edited"),
167 ]
168 );
169
170 // No event is emitted when the mutation is a no-op.
171 _ = editor2.update(cx, |editor, window, cx| {
172 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
173
174 editor.backspace(&Backspace, window, cx);
175 });
176 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
177}
178
179#[gpui::test]
180fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
181 init_test(cx, |_| {});
182
183 let mut now = Instant::now();
184 let group_interval = Duration::from_millis(1);
185 let buffer = cx.new(|cx| {
186 let mut buf = language::Buffer::local("123456", cx);
187 buf.set_group_interval(group_interval);
188 buf
189 });
190 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
191 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
192
193 _ = editor.update(cx, |editor, window, cx| {
194 editor.start_transaction_at(now, window, cx);
195 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
196
197 editor.insert("cd", window, cx);
198 editor.end_transaction_at(now, cx);
199 assert_eq!(editor.text(cx), "12cd56");
200 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
201
202 editor.start_transaction_at(now, window, cx);
203 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
204 editor.insert("e", window, cx);
205 editor.end_transaction_at(now, cx);
206 assert_eq!(editor.text(cx), "12cde6");
207 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
208
209 now += group_interval + Duration::from_millis(1);
210 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
211
212 // Simulate an edit in another editor
213 buffer.update(cx, |buffer, cx| {
214 buffer.start_transaction_at(now, cx);
215 buffer.edit([(0..1, "a")], None, cx);
216 buffer.edit([(1..1, "b")], None, cx);
217 buffer.end_transaction_at(now, cx);
218 });
219
220 assert_eq!(editor.text(cx), "ab2cde6");
221 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
222
223 // Last transaction happened past the group interval in a different editor.
224 // Undo it individually and don't restore selections.
225 editor.undo(&Undo, window, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
228
229 // First two transactions happened within the group interval in this editor.
230 // Undo them together and restore selections.
231 editor.undo(&Undo, window, cx);
232 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
233 assert_eq!(editor.text(cx), "123456");
234 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
235
236 // Redo the first two transactions together.
237 editor.redo(&Redo, window, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
240
241 // Redo the last transaction on its own.
242 editor.redo(&Redo, window, cx);
243 assert_eq!(editor.text(cx), "ab2cde6");
244 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
245
246 // Test empty transactions.
247 editor.start_transaction_at(now, window, cx);
248 editor.end_transaction_at(now, cx);
249 editor.undo(&Undo, window, cx);
250 assert_eq!(editor.text(cx), "12cde6");
251 });
252}
253
254#[gpui::test]
255fn test_ime_composition(cx: &mut TestAppContext) {
256 init_test(cx, |_| {});
257
258 let buffer = cx.new(|cx| {
259 let mut buffer = language::Buffer::local("abcde", cx);
260 // Ensure automatic grouping doesn't occur.
261 buffer.set_group_interval(Duration::ZERO);
262 buffer
263 });
264
265 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
266 cx.add_window(|window, cx| {
267 let mut editor = build_editor(buffer.clone(), window, cx);
268
269 // Start a new IME composition.
270 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
272 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
273 assert_eq!(editor.text(cx), "äbcde");
274 assert_eq!(
275 editor.marked_text_ranges(cx),
276 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
277 );
278
279 // Finalize IME composition.
280 editor.replace_text_in_range(None, "ā", window, cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // IME composition edits are grouped and are undone/redone at once.
285 editor.undo(&Default::default(), window, cx);
286 assert_eq!(editor.text(cx), "abcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288 editor.redo(&Default::default(), window, cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition.
293 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
294 assert_eq!(
295 editor.marked_text_ranges(cx),
296 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
297 );
298
299 // Undoing during an IME composition cancels it.
300 editor.undo(&Default::default(), window, cx);
301 assert_eq!(editor.text(cx), "ābcde");
302 assert_eq!(editor.marked_text_ranges(cx), None);
303
304 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
305 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
306 assert_eq!(editor.text(cx), "ābcdè");
307 assert_eq!(
308 editor.marked_text_ranges(cx),
309 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
310 );
311
312 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
313 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
314 assert_eq!(editor.text(cx), "ābcdę");
315 assert_eq!(editor.marked_text_ranges(cx), None);
316
317 // Start a new IME composition with multiple cursors.
318 editor.change_selections(None, window, cx, |s| {
319 s.select_ranges([
320 OffsetUtf16(1)..OffsetUtf16(1),
321 OffsetUtf16(3)..OffsetUtf16(3),
322 OffsetUtf16(5)..OffsetUtf16(5),
323 ])
324 });
325 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
326 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(0)..OffsetUtf16(3),
331 OffsetUtf16(4)..OffsetUtf16(7),
332 OffsetUtf16(8)..OffsetUtf16(11)
333 ])
334 );
335
336 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
337 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
338 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
339 assert_eq!(
340 editor.marked_text_ranges(cx),
341 Some(vec![
342 OffsetUtf16(1)..OffsetUtf16(2),
343 OffsetUtf16(5)..OffsetUtf16(6),
344 OffsetUtf16(9)..OffsetUtf16(10)
345 ])
346 );
347
348 // Finalize IME composition with multiple cursors.
349 editor.replace_text_in_range(Some(9..10), "2", window, cx);
350 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
351 assert_eq!(editor.marked_text_ranges(cx), None);
352
353 editor
354 });
355}
356
357#[gpui::test]
358fn test_selection_with_mouse(cx: &mut TestAppContext) {
359 init_test(cx, |_| {});
360
361 let editor = cx.add_window(|window, cx| {
362 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
363 build_editor(buffer, window, cx)
364 });
365
366 _ = editor.update(cx, |editor, window, cx| {
367 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
368 });
369 assert_eq!(
370 editor
371 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
374 );
375
376 _ = editor.update(cx, |editor, window, cx| {
377 editor.update_selection(
378 DisplayPoint::new(DisplayRow(3), 3),
379 0,
380 gpui::Point::<f32>::default(),
381 window,
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
391 );
392
393 _ = editor.update(cx, |editor, window, cx| {
394 editor.update_selection(
395 DisplayPoint::new(DisplayRow(1), 1),
396 0,
397 gpui::Point::<f32>::default(),
398 window,
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |editor, window, cx| {
411 editor.end_selection(window, cx);
412 editor.update_selection(
413 DisplayPoint::new(DisplayRow(3), 3),
414 0,
415 gpui::Point::<f32>::default(),
416 window,
417 cx,
418 );
419 });
420
421 assert_eq!(
422 editor
423 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
424 .unwrap(),
425 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
426 );
427
428 _ = editor.update(cx, |editor, window, cx| {
429 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
430 editor.update_selection(
431 DisplayPoint::new(DisplayRow(0), 0),
432 0,
433 gpui::Point::<f32>::default(),
434 window,
435 cx,
436 );
437 });
438
439 assert_eq!(
440 editor
441 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
442 .unwrap(),
443 [
444 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
445 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
446 ]
447 );
448
449 _ = editor.update(cx, |editor, window, cx| {
450 editor.end_selection(window, cx);
451 });
452
453 assert_eq!(
454 editor
455 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
456 .unwrap(),
457 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
458 );
459}
460
461#[gpui::test]
462fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
463 init_test(cx, |_| {});
464
465 let editor = cx.add_window(|window, cx| {
466 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
467 build_editor(buffer, window, cx)
468 });
469
470 _ = editor.update(cx, |editor, window, cx| {
471 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
472 });
473
474 _ = editor.update(cx, |editor, window, cx| {
475 editor.end_selection(window, cx);
476 });
477
478 _ = editor.update(cx, |editor, window, cx| {
479 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
480 });
481
482 _ = editor.update(cx, |editor, window, cx| {
483 editor.end_selection(window, cx);
484 });
485
486 assert_eq!(
487 editor
488 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
489 .unwrap(),
490 [
491 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
492 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
493 ]
494 );
495
496 _ = editor.update(cx, |editor, window, cx| {
497 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
498 });
499
500 _ = editor.update(cx, |editor, window, cx| {
501 editor.end_selection(window, cx);
502 });
503
504 assert_eq!(
505 editor
506 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
507 .unwrap(),
508 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
509 );
510}
511
512#[gpui::test]
513fn test_canceling_pending_selection(cx: &mut TestAppContext) {
514 init_test(cx, |_| {});
515
516 let editor = cx.add_window(|window, cx| {
517 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
518 build_editor(buffer, window, cx)
519 });
520
521 _ = editor.update(cx, |editor, window, cx| {
522 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
523 assert_eq!(
524 editor.selections.display_ranges(cx),
525 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
526 );
527 });
528
529 _ = editor.update(cx, |editor, window, cx| {
530 editor.update_selection(
531 DisplayPoint::new(DisplayRow(3), 3),
532 0,
533 gpui::Point::<f32>::default(),
534 window,
535 cx,
536 );
537 assert_eq!(
538 editor.selections.display_ranges(cx),
539 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
540 );
541 });
542
543 _ = editor.update(cx, |editor, window, cx| {
544 editor.cancel(&Cancel, window, cx);
545 editor.update_selection(
546 DisplayPoint::new(DisplayRow(1), 1),
547 0,
548 gpui::Point::<f32>::default(),
549 window,
550 cx,
551 );
552 assert_eq!(
553 editor.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
555 );
556 });
557}
558
559#[gpui::test]
560fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
561 init_test(cx, |_| {});
562
563 let editor = cx.add_window(|window, cx| {
564 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
565 build_editor(buffer, window, cx)
566 });
567
568 _ = editor.update(cx, |editor, window, cx| {
569 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
570 assert_eq!(
571 editor.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
573 );
574
575 editor.move_down(&Default::default(), window, cx);
576 assert_eq!(
577 editor.selections.display_ranges(cx),
578 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
579 );
580
581 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
582 assert_eq!(
583 editor.selections.display_ranges(cx),
584 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
585 );
586
587 editor.move_up(&Default::default(), window, cx);
588 assert_eq!(
589 editor.selections.display_ranges(cx),
590 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
591 );
592 });
593}
594
595#[gpui::test]
596fn test_clone(cx: &mut TestAppContext) {
597 init_test(cx, |_| {});
598
599 let (text, selection_ranges) = marked_text_ranges(
600 indoc! {"
601 one
602 two
603 threeˇ
604 four
605 fiveˇ
606 "},
607 true,
608 );
609
610 let editor = cx.add_window(|window, cx| {
611 let buffer = MultiBuffer::build_simple(&text, cx);
612 build_editor(buffer, window, cx)
613 });
614
615 _ = editor.update(cx, |editor, window, cx| {
616 editor.change_selections(None, window, cx, |s| {
617 s.select_ranges(selection_ranges.clone())
618 });
619 editor.fold_creases(
620 vec![
621 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
622 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
623 ],
624 true,
625 window,
626 cx,
627 );
628 });
629
630 let cloned_editor = editor
631 .update(cx, |editor, _, cx| {
632 cx.open_window(Default::default(), |window, cx| {
633 cx.new(|cx| editor.clone(window, cx))
634 })
635 })
636 .unwrap()
637 .unwrap();
638
639 let snapshot = editor
640 .update(cx, |e, window, cx| e.snapshot(window, cx))
641 .unwrap();
642 let cloned_snapshot = cloned_editor
643 .update(cx, |e, window, cx| e.snapshot(window, cx))
644 .unwrap();
645
646 assert_eq!(
647 cloned_editor
648 .update(cx, |e, _, cx| e.display_text(cx))
649 .unwrap(),
650 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
651 );
652 assert_eq!(
653 cloned_snapshot
654 .folds_in_range(0..text.len())
655 .collect::<Vec<_>>(),
656 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
657 );
658 assert_set_eq!(
659 cloned_editor
660 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
661 .unwrap(),
662 editor
663 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
664 .unwrap()
665 );
666 assert_set_eq!(
667 cloned_editor
668 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
669 .unwrap(),
670 editor
671 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
672 .unwrap()
673 );
674}
675
676#[gpui::test]
677async fn test_navigation_history(cx: &mut TestAppContext) {
678 init_test(cx, |_| {});
679
680 use workspace::item::Item;
681
682 let fs = FakeFs::new(cx.executor());
683 let project = Project::test(fs, [], cx).await;
684 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
685 let pane = workspace
686 .update(cx, |workspace, _, _| workspace.active_pane().clone())
687 .unwrap();
688
689 _ = workspace.update(cx, |_v, window, cx| {
690 cx.new(|cx| {
691 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
692 let mut editor = build_editor(buffer.clone(), window, cx);
693 let handle = cx.entity();
694 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
695
696 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
697 editor.nav_history.as_mut().unwrap().pop_backward(cx)
698 }
699
700 // Move the cursor a small distance.
701 // Nothing is added to the navigation history.
702 editor.change_selections(None, window, cx, |s| {
703 s.select_display_ranges([
704 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
705 ])
706 });
707 editor.change_selections(None, window, cx, |s| {
708 s.select_display_ranges([
709 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
710 ])
711 });
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance.
715 // The history can jump back to the previous position.
716 editor.change_selections(None, window, cx, |s| {
717 s.select_display_ranges([
718 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
719 ])
720 });
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), window, cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Move the cursor a small distance via the mouse.
731 // Nothing is added to the navigation history.
732 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
733 editor.end_selection(window, cx);
734 assert_eq!(
735 editor.selections.display_ranges(cx),
736 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
737 );
738 assert!(pop_history(&mut editor, cx).is_none());
739
740 // Move the cursor a large distance via the mouse.
741 // The history can jump back to the previous position.
742 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
743 editor.end_selection(window, cx);
744 assert_eq!(
745 editor.selections.display_ranges(cx),
746 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
747 );
748 let nav_entry = pop_history(&mut editor, cx).unwrap();
749 editor.navigate(nav_entry.data.unwrap(), window, cx);
750 assert_eq!(nav_entry.item.id(), cx.entity_id());
751 assert_eq!(
752 editor.selections.display_ranges(cx),
753 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
754 );
755 assert!(pop_history(&mut editor, cx).is_none());
756
757 // Set scroll position to check later
758 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
759 let original_scroll_position = editor.scroll_manager.anchor();
760
761 // Jump to the end of the document and adjust scroll
762 editor.move_to_end(&MoveToEnd, window, cx);
763 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
764 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
765
766 let nav_entry = pop_history(&mut editor, cx).unwrap();
767 editor.navigate(nav_entry.data.unwrap(), window, cx);
768 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
769
770 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
771 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
772 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
773 let invalid_point = Point::new(9999, 0);
774 editor.navigate(
775 Box::new(NavigationData {
776 cursor_anchor: invalid_anchor,
777 cursor_position: invalid_point,
778 scroll_anchor: ScrollAnchor {
779 anchor: invalid_anchor,
780 offset: Default::default(),
781 },
782 scroll_top_row: invalid_point.row,
783 }),
784 window,
785 cx,
786 );
787 assert_eq!(
788 editor.selections.display_ranges(cx),
789 &[editor.max_point(cx)..editor.max_point(cx)]
790 );
791 assert_eq!(
792 editor.scroll_position(cx),
793 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
794 );
795
796 editor
797 })
798 });
799}
800
801#[gpui::test]
802fn test_cancel(cx: &mut TestAppContext) {
803 init_test(cx, |_| {});
804
805 let editor = cx.add_window(|window, cx| {
806 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
807 build_editor(buffer, window, cx)
808 });
809
810 _ = editor.update(cx, |editor, window, cx| {
811 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
812 editor.update_selection(
813 DisplayPoint::new(DisplayRow(1), 1),
814 0,
815 gpui::Point::<f32>::default(),
816 window,
817 cx,
818 );
819 editor.end_selection(window, cx);
820
821 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
822 editor.update_selection(
823 DisplayPoint::new(DisplayRow(0), 3),
824 0,
825 gpui::Point::<f32>::default(),
826 window,
827 cx,
828 );
829 editor.end_selection(window, cx);
830 assert_eq!(
831 editor.selections.display_ranges(cx),
832 [
833 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
834 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
835 ]
836 );
837 });
838
839 _ = editor.update(cx, |editor, window, cx| {
840 editor.cancel(&Cancel, window, cx);
841 assert_eq!(
842 editor.selections.display_ranges(cx),
843 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
844 );
845 });
846
847 _ = editor.update(cx, |editor, window, cx| {
848 editor.cancel(&Cancel, window, cx);
849 assert_eq!(
850 editor.selections.display_ranges(cx),
851 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
852 );
853 });
854}
855
856#[gpui::test]
857fn test_fold_action(cx: &mut TestAppContext) {
858 init_test(cx, |_| {});
859
860 let editor = cx.add_window(|window, cx| {
861 let buffer = MultiBuffer::build_simple(
862 &"
863 impl Foo {
864 // Hello!
865
866 fn a() {
867 1
868 }
869
870 fn b() {
871 2
872 }
873
874 fn c() {
875 3
876 }
877 }
878 "
879 .unindent(),
880 cx,
881 );
882 build_editor(buffer.clone(), window, cx)
883 });
884
885 _ = editor.update(cx, |editor, window, cx| {
886 editor.change_selections(None, window, cx, |s| {
887 s.select_display_ranges([
888 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
889 ]);
890 });
891 editor.fold(&Fold, window, cx);
892 assert_eq!(
893 editor.display_text(cx),
894 "
895 impl Foo {
896 // Hello!
897
898 fn a() {
899 1
900 }
901
902 fn b() {⋯
903 }
904
905 fn c() {⋯
906 }
907 }
908 "
909 .unindent(),
910 );
911
912 editor.fold(&Fold, window, cx);
913 assert_eq!(
914 editor.display_text(cx),
915 "
916 impl Foo {⋯
917 }
918 "
919 .unindent(),
920 );
921
922 editor.unfold_lines(&UnfoldLines, window, cx);
923 assert_eq!(
924 editor.display_text(cx),
925 "
926 impl Foo {
927 // Hello!
928
929 fn a() {
930 1
931 }
932
933 fn b() {⋯
934 }
935
936 fn c() {⋯
937 }
938 }
939 "
940 .unindent(),
941 );
942
943 editor.unfold_lines(&UnfoldLines, window, cx);
944 assert_eq!(
945 editor.display_text(cx),
946 editor.buffer.read(cx).read(cx).text()
947 );
948 });
949}
950
951#[gpui::test]
952fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
953 init_test(cx, |_| {});
954
955 let editor = cx.add_window(|window, cx| {
956 let buffer = MultiBuffer::build_simple(
957 &"
958 class Foo:
959 # Hello!
960
961 def a():
962 print(1)
963
964 def b():
965 print(2)
966
967 def c():
968 print(3)
969 "
970 .unindent(),
971 cx,
972 );
973 build_editor(buffer.clone(), window, cx)
974 });
975
976 _ = editor.update(cx, |editor, window, cx| {
977 editor.change_selections(None, window, cx, |s| {
978 s.select_display_ranges([
979 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
980 ]);
981 });
982 editor.fold(&Fold, window, cx);
983 assert_eq!(
984 editor.display_text(cx),
985 "
986 class Foo:
987 # Hello!
988
989 def a():
990 print(1)
991
992 def b():⋯
993
994 def c():⋯
995 "
996 .unindent(),
997 );
998
999 editor.fold(&Fold, window, cx);
1000 assert_eq!(
1001 editor.display_text(cx),
1002 "
1003 class Foo:⋯
1004 "
1005 .unindent(),
1006 );
1007
1008 editor.unfold_lines(&UnfoldLines, window, cx);
1009 assert_eq!(
1010 editor.display_text(cx),
1011 "
1012 class Foo:
1013 # Hello!
1014
1015 def a():
1016 print(1)
1017
1018 def b():⋯
1019
1020 def c():⋯
1021 "
1022 .unindent(),
1023 );
1024
1025 editor.unfold_lines(&UnfoldLines, window, cx);
1026 assert_eq!(
1027 editor.display_text(cx),
1028 editor.buffer.read(cx).read(cx).text()
1029 );
1030 });
1031}
1032
1033#[gpui::test]
1034fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1035 init_test(cx, |_| {});
1036
1037 let editor = cx.add_window(|window, cx| {
1038 let buffer = MultiBuffer::build_simple(
1039 &"
1040 class Foo:
1041 # Hello!
1042
1043 def a():
1044 print(1)
1045
1046 def b():
1047 print(2)
1048
1049
1050 def c():
1051 print(3)
1052
1053
1054 "
1055 .unindent(),
1056 cx,
1057 );
1058 build_editor(buffer.clone(), window, cx)
1059 });
1060
1061 _ = editor.update(cx, |editor, window, cx| {
1062 editor.change_selections(None, window, cx, |s| {
1063 s.select_display_ranges([
1064 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1065 ]);
1066 });
1067 editor.fold(&Fold, window, cx);
1068 assert_eq!(
1069 editor.display_text(cx),
1070 "
1071 class Foo:
1072 # Hello!
1073
1074 def a():
1075 print(1)
1076
1077 def b():⋯
1078
1079
1080 def c():⋯
1081
1082
1083 "
1084 .unindent(),
1085 );
1086
1087 editor.fold(&Fold, window, cx);
1088 assert_eq!(
1089 editor.display_text(cx),
1090 "
1091 class Foo:⋯
1092
1093
1094 "
1095 .unindent(),
1096 );
1097
1098 editor.unfold_lines(&UnfoldLines, window, cx);
1099 assert_eq!(
1100 editor.display_text(cx),
1101 "
1102 class Foo:
1103 # Hello!
1104
1105 def a():
1106 print(1)
1107
1108 def b():⋯
1109
1110
1111 def c():⋯
1112
1113
1114 "
1115 .unindent(),
1116 );
1117
1118 editor.unfold_lines(&UnfoldLines, window, cx);
1119 assert_eq!(
1120 editor.display_text(cx),
1121 editor.buffer.read(cx).read(cx).text()
1122 );
1123 });
1124}
1125
1126#[gpui::test]
1127fn test_fold_at_level(cx: &mut TestAppContext) {
1128 init_test(cx, |_| {});
1129
1130 let editor = cx.add_window(|window, cx| {
1131 let buffer = MultiBuffer::build_simple(
1132 &"
1133 class Foo:
1134 # Hello!
1135
1136 def a():
1137 print(1)
1138
1139 def b():
1140 print(2)
1141
1142
1143 class Bar:
1144 # World!
1145
1146 def a():
1147 print(1)
1148
1149 def b():
1150 print(2)
1151
1152
1153 "
1154 .unindent(),
1155 cx,
1156 );
1157 build_editor(buffer.clone(), window, cx)
1158 });
1159
1160 _ = editor.update(cx, |editor, window, cx| {
1161 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1162 assert_eq!(
1163 editor.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():⋯
1169
1170 def b():⋯
1171
1172
1173 class Bar:
1174 # World!
1175
1176 def a():⋯
1177
1178 def b():⋯
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1186 assert_eq!(
1187 editor.display_text(cx),
1188 "
1189 class Foo:⋯
1190
1191
1192 class Bar:⋯
1193
1194
1195 "
1196 .unindent(),
1197 );
1198
1199 editor.unfold_all(&UnfoldAll, window, cx);
1200 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1201 assert_eq!(
1202 editor.display_text(cx),
1203 "
1204 class Foo:
1205 # Hello!
1206
1207 def a():
1208 print(1)
1209
1210 def b():
1211 print(2)
1212
1213
1214 class Bar:
1215 # World!
1216
1217 def a():
1218 print(1)
1219
1220 def b():
1221 print(2)
1222
1223
1224 "
1225 .unindent(),
1226 );
1227
1228 assert_eq!(
1229 editor.display_text(cx),
1230 editor.buffer.read(cx).read(cx).text()
1231 );
1232 });
1233}
1234
1235#[gpui::test]
1236fn test_move_cursor(cx: &mut TestAppContext) {
1237 init_test(cx, |_| {});
1238
1239 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1240 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1241
1242 buffer.update(cx, |buffer, cx| {
1243 buffer.edit(
1244 vec![
1245 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1246 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1247 ],
1248 None,
1249 cx,
1250 );
1251 });
1252 _ = editor.update(cx, |editor, window, cx| {
1253 assert_eq!(
1254 editor.selections.display_ranges(cx),
1255 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1256 );
1257
1258 editor.move_down(&MoveDown, window, cx);
1259 assert_eq!(
1260 editor.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1262 );
1263
1264 editor.move_right(&MoveRight, window, cx);
1265 assert_eq!(
1266 editor.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1268 );
1269
1270 editor.move_left(&MoveLeft, window, cx);
1271 assert_eq!(
1272 editor.selections.display_ranges(cx),
1273 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1274 );
1275
1276 editor.move_up(&MoveUp, window, cx);
1277 assert_eq!(
1278 editor.selections.display_ranges(cx),
1279 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1280 );
1281
1282 editor.move_to_end(&MoveToEnd, window, cx);
1283 assert_eq!(
1284 editor.selections.display_ranges(cx),
1285 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1286 );
1287
1288 editor.move_to_beginning(&MoveToBeginning, window, cx);
1289 assert_eq!(
1290 editor.selections.display_ranges(cx),
1291 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1292 );
1293
1294 editor.change_selections(None, window, cx, |s| {
1295 s.select_display_ranges([
1296 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1297 ]);
1298 });
1299 editor.select_to_beginning(&SelectToBeginning, window, cx);
1300 assert_eq!(
1301 editor.selections.display_ranges(cx),
1302 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1303 );
1304
1305 editor.select_to_end(&SelectToEnd, window, cx);
1306 assert_eq!(
1307 editor.selections.display_ranges(cx),
1308 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1309 );
1310 });
1311}
1312
1313// TODO: Re-enable this test
1314#[cfg(target_os = "macos")]
1315#[gpui::test]
1316fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1317 init_test(cx, |_| {});
1318
1319 let editor = cx.add_window(|window, cx| {
1320 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1321 build_editor(buffer.clone(), window, cx)
1322 });
1323
1324 assert_eq!('🟥'.len_utf8(), 4);
1325 assert_eq!('α'.len_utf8(), 2);
1326
1327 _ = editor.update(cx, |editor, window, cx| {
1328 editor.fold_creases(
1329 vec![
1330 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1332 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1333 ],
1334 true,
1335 window,
1336 cx,
1337 );
1338 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1339
1340 editor.move_right(&MoveRight, window, cx);
1341 assert_eq!(
1342 editor.selections.display_ranges(cx),
1343 &[empty_range(0, "🟥".len())]
1344 );
1345 editor.move_right(&MoveRight, window, cx);
1346 assert_eq!(
1347 editor.selections.display_ranges(cx),
1348 &[empty_range(0, "🟥🟧".len())]
1349 );
1350 editor.move_right(&MoveRight, window, cx);
1351 assert_eq!(
1352 editor.selections.display_ranges(cx),
1353 &[empty_range(0, "🟥🟧⋯".len())]
1354 );
1355
1356 editor.move_down(&MoveDown, window, cx);
1357 assert_eq!(
1358 editor.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 editor.move_left(&MoveLeft, window, cx);
1362 assert_eq!(
1363 editor.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯".len())]
1365 );
1366 editor.move_left(&MoveLeft, window, cx);
1367 assert_eq!(
1368 editor.selections.display_ranges(cx),
1369 &[empty_range(1, "ab".len())]
1370 );
1371 editor.move_left(&MoveLeft, window, cx);
1372 assert_eq!(
1373 editor.selections.display_ranges(cx),
1374 &[empty_range(1, "a".len())]
1375 );
1376
1377 editor.move_down(&MoveDown, window, cx);
1378 assert_eq!(
1379 editor.selections.display_ranges(cx),
1380 &[empty_range(2, "α".len())]
1381 );
1382 editor.move_right(&MoveRight, window, cx);
1383 assert_eq!(
1384 editor.selections.display_ranges(cx),
1385 &[empty_range(2, "αβ".len())]
1386 );
1387 editor.move_right(&MoveRight, window, cx);
1388 assert_eq!(
1389 editor.selections.display_ranges(cx),
1390 &[empty_range(2, "αβ⋯".len())]
1391 );
1392 editor.move_right(&MoveRight, window, cx);
1393 assert_eq!(
1394 editor.selections.display_ranges(cx),
1395 &[empty_range(2, "αβ⋯ε".len())]
1396 );
1397
1398 editor.move_up(&MoveUp, window, cx);
1399 assert_eq!(
1400 editor.selections.display_ranges(cx),
1401 &[empty_range(1, "ab⋯e".len())]
1402 );
1403 editor.move_down(&MoveDown, window, cx);
1404 assert_eq!(
1405 editor.selections.display_ranges(cx),
1406 &[empty_range(2, "αβ⋯ε".len())]
1407 );
1408 editor.move_up(&MoveUp, window, cx);
1409 assert_eq!(
1410 editor.selections.display_ranges(cx),
1411 &[empty_range(1, "ab⋯e".len())]
1412 );
1413
1414 editor.move_up(&MoveUp, window, cx);
1415 assert_eq!(
1416 editor.selections.display_ranges(cx),
1417 &[empty_range(0, "🟥🟧".len())]
1418 );
1419 editor.move_left(&MoveLeft, window, cx);
1420 assert_eq!(
1421 editor.selections.display_ranges(cx),
1422 &[empty_range(0, "🟥".len())]
1423 );
1424 editor.move_left(&MoveLeft, window, cx);
1425 assert_eq!(
1426 editor.selections.display_ranges(cx),
1427 &[empty_range(0, "".len())]
1428 );
1429 });
1430}
1431
1432#[gpui::test]
1433fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1434 init_test(cx, |_| {});
1435
1436 let editor = cx.add_window(|window, cx| {
1437 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1438 build_editor(buffer.clone(), window, cx)
1439 });
1440 _ = editor.update(cx, |editor, window, cx| {
1441 editor.change_selections(None, window, cx, |s| {
1442 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1443 });
1444
1445 // moving above start of document should move selection to start of document,
1446 // but the next move down should still be at the original goal_x
1447 editor.move_up(&MoveUp, window, cx);
1448 assert_eq!(
1449 editor.selections.display_ranges(cx),
1450 &[empty_range(0, "".len())]
1451 );
1452
1453 editor.move_down(&MoveDown, window, cx);
1454 assert_eq!(
1455 editor.selections.display_ranges(cx),
1456 &[empty_range(1, "abcd".len())]
1457 );
1458
1459 editor.move_down(&MoveDown, window, cx);
1460 assert_eq!(
1461 editor.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464
1465 editor.move_down(&MoveDown, window, cx);
1466 assert_eq!(
1467 editor.selections.display_ranges(cx),
1468 &[empty_range(3, "abcd".len())]
1469 );
1470
1471 editor.move_down(&MoveDown, window, cx);
1472 assert_eq!(
1473 editor.selections.display_ranges(cx),
1474 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1475 );
1476
1477 // moving past end of document should not change goal_x
1478 editor.move_down(&MoveDown, window, cx);
1479 assert_eq!(
1480 editor.selections.display_ranges(cx),
1481 &[empty_range(5, "".len())]
1482 );
1483
1484 editor.move_down(&MoveDown, window, cx);
1485 assert_eq!(
1486 editor.selections.display_ranges(cx),
1487 &[empty_range(5, "".len())]
1488 );
1489
1490 editor.move_up(&MoveUp, window, cx);
1491 assert_eq!(
1492 editor.selections.display_ranges(cx),
1493 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1494 );
1495
1496 editor.move_up(&MoveUp, window, cx);
1497 assert_eq!(
1498 editor.selections.display_ranges(cx),
1499 &[empty_range(3, "abcd".len())]
1500 );
1501
1502 editor.move_up(&MoveUp, window, cx);
1503 assert_eq!(
1504 editor.selections.display_ranges(cx),
1505 &[empty_range(2, "αβγ".len())]
1506 );
1507 });
1508}
1509
1510#[gpui::test]
1511fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1512 init_test(cx, |_| {});
1513 let move_to_beg = MoveToBeginningOfLine {
1514 stop_at_soft_wraps: true,
1515 stop_at_indent: true,
1516 };
1517
1518 let delete_to_beg = DeleteToBeginningOfLine {
1519 stop_at_indent: false,
1520 };
1521
1522 let move_to_end = MoveToEndOfLine {
1523 stop_at_soft_wraps: true,
1524 };
1525
1526 let editor = cx.add_window(|window, cx| {
1527 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1528 build_editor(buffer, window, cx)
1529 });
1530 _ = editor.update(cx, |editor, window, cx| {
1531 editor.change_selections(None, window, cx, |s| {
1532 s.select_display_ranges([
1533 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1534 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1535 ]);
1536 });
1537 });
1538
1539 _ = editor.update(cx, |editor, window, cx| {
1540 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1541 assert_eq!(
1542 editor.selections.display_ranges(cx),
1543 &[
1544 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1545 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1546 ]
1547 );
1548 });
1549
1550 _ = editor.update(cx, |editor, window, cx| {
1551 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1552 assert_eq!(
1553 editor.selections.display_ranges(cx),
1554 &[
1555 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1556 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1557 ]
1558 );
1559 });
1560
1561 _ = editor.update(cx, |editor, window, cx| {
1562 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1563 assert_eq!(
1564 editor.selections.display_ranges(cx),
1565 &[
1566 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1567 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1568 ]
1569 );
1570 });
1571
1572 _ = editor.update(cx, |editor, window, cx| {
1573 editor.move_to_end_of_line(&move_to_end, window, cx);
1574 assert_eq!(
1575 editor.selections.display_ranges(cx),
1576 &[
1577 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1578 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1579 ]
1580 );
1581 });
1582
1583 // Moving to the end of line again is a no-op.
1584 _ = editor.update(cx, |editor, window, cx| {
1585 editor.move_to_end_of_line(&move_to_end, window, cx);
1586 assert_eq!(
1587 editor.selections.display_ranges(cx),
1588 &[
1589 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1590 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1591 ]
1592 );
1593 });
1594
1595 _ = editor.update(cx, |editor, window, cx| {
1596 editor.move_left(&MoveLeft, window, cx);
1597 editor.select_to_beginning_of_line(
1598 &SelectToBeginningOfLine {
1599 stop_at_soft_wraps: true,
1600 stop_at_indent: true,
1601 },
1602 window,
1603 cx,
1604 );
1605 assert_eq!(
1606 editor.selections.display_ranges(cx),
1607 &[
1608 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1609 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1610 ]
1611 );
1612 });
1613
1614 _ = editor.update(cx, |editor, window, cx| {
1615 editor.select_to_beginning_of_line(
1616 &SelectToBeginningOfLine {
1617 stop_at_soft_wraps: true,
1618 stop_at_indent: true,
1619 },
1620 window,
1621 cx,
1622 );
1623 assert_eq!(
1624 editor.selections.display_ranges(cx),
1625 &[
1626 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1627 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1628 ]
1629 );
1630 });
1631
1632 _ = editor.update(cx, |editor, window, cx| {
1633 editor.select_to_beginning_of_line(
1634 &SelectToBeginningOfLine {
1635 stop_at_soft_wraps: true,
1636 stop_at_indent: true,
1637 },
1638 window,
1639 cx,
1640 );
1641 assert_eq!(
1642 editor.selections.display_ranges(cx),
1643 &[
1644 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1645 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1646 ]
1647 );
1648 });
1649
1650 _ = editor.update(cx, |editor, window, cx| {
1651 editor.select_to_end_of_line(
1652 &SelectToEndOfLine {
1653 stop_at_soft_wraps: true,
1654 },
1655 window,
1656 cx,
1657 );
1658 assert_eq!(
1659 editor.selections.display_ranges(cx),
1660 &[
1661 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1662 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1663 ]
1664 );
1665 });
1666
1667 _ = editor.update(cx, |editor, window, cx| {
1668 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1669 assert_eq!(editor.display_text(cx), "ab\n de");
1670 assert_eq!(
1671 editor.selections.display_ranges(cx),
1672 &[
1673 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1674 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1675 ]
1676 );
1677 });
1678
1679 _ = editor.update(cx, |editor, window, cx| {
1680 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1681 assert_eq!(editor.display_text(cx), "\n");
1682 assert_eq!(
1683 editor.selections.display_ranges(cx),
1684 &[
1685 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1686 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1687 ]
1688 );
1689 });
1690}
1691
1692#[gpui::test]
1693fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1694 init_test(cx, |_| {});
1695 let move_to_beg = MoveToBeginningOfLine {
1696 stop_at_soft_wraps: false,
1697 stop_at_indent: false,
1698 };
1699
1700 let move_to_end = MoveToEndOfLine {
1701 stop_at_soft_wraps: false,
1702 };
1703
1704 let editor = cx.add_window(|window, cx| {
1705 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1706 build_editor(buffer, window, cx)
1707 });
1708
1709 _ = editor.update(cx, |editor, window, cx| {
1710 editor.set_wrap_width(Some(140.0.into()), cx);
1711
1712 // We expect the following lines after wrapping
1713 // ```
1714 // thequickbrownfox
1715 // jumpedoverthelazydo
1716 // gs
1717 // ```
1718 // The final `gs` was soft-wrapped onto a new line.
1719 assert_eq!(
1720 "thequickbrownfox\njumpedoverthelaz\nydogs",
1721 editor.display_text(cx),
1722 );
1723
1724 // First, let's assert behavior on the first line, that was not soft-wrapped.
1725 // Start the cursor at the `k` on the first line
1726 editor.change_selections(None, window, cx, |s| {
1727 s.select_display_ranges([
1728 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1729 ]);
1730 });
1731
1732 // Moving to the beginning of the line should put us at the beginning of the line.
1733 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1734 assert_eq!(
1735 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1736 editor.selections.display_ranges(cx)
1737 );
1738
1739 // Moving to the end of the line should put us at the end of the line.
1740 editor.move_to_end_of_line(&move_to_end, window, cx);
1741 assert_eq!(
1742 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1743 editor.selections.display_ranges(cx)
1744 );
1745
1746 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1747 // Start the cursor at the last line (`y` that was wrapped to a new line)
1748 editor.change_selections(None, window, cx, |s| {
1749 s.select_display_ranges([
1750 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1751 ]);
1752 });
1753
1754 // Moving to the beginning of the line should put us at the start of the second line of
1755 // display text, i.e., the `j`.
1756 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1757 assert_eq!(
1758 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1759 editor.selections.display_ranges(cx)
1760 );
1761
1762 // Moving to the beginning of the line again should be a no-op.
1763 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1764 assert_eq!(
1765 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1766 editor.selections.display_ranges(cx)
1767 );
1768
1769 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1770 // next display line.
1771 editor.move_to_end_of_line(&move_to_end, window, cx);
1772 assert_eq!(
1773 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1774 editor.selections.display_ranges(cx)
1775 );
1776
1777 // Moving to the end of the line again should be a no-op.
1778 editor.move_to_end_of_line(&move_to_end, window, cx);
1779 assert_eq!(
1780 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1781 editor.selections.display_ranges(cx)
1782 );
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let move_to_beg = MoveToBeginningOfLine {
1791 stop_at_soft_wraps: true,
1792 stop_at_indent: true,
1793 };
1794
1795 let select_to_beg = SelectToBeginningOfLine {
1796 stop_at_soft_wraps: true,
1797 stop_at_indent: true,
1798 };
1799
1800 let delete_to_beg = DeleteToBeginningOfLine {
1801 stop_at_indent: true,
1802 };
1803
1804 let move_to_end = MoveToEndOfLine {
1805 stop_at_soft_wraps: false,
1806 };
1807
1808 let editor = cx.add_window(|window, cx| {
1809 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1810 build_editor(buffer, window, cx)
1811 });
1812
1813 _ = editor.update(cx, |editor, window, cx| {
1814 editor.change_selections(None, window, cx, |s| {
1815 s.select_display_ranges([
1816 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1817 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1818 ]);
1819 });
1820
1821 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1822 // and the second cursor at the first non-whitespace character in the line.
1823 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1824 assert_eq!(
1825 editor.selections.display_ranges(cx),
1826 &[
1827 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1828 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1829 ]
1830 );
1831
1832 // Moving to the beginning of the line again should be a no-op for the first cursor,
1833 // and should move the second cursor to the beginning of the line.
1834 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1835 assert_eq!(
1836 editor.selections.display_ranges(cx),
1837 &[
1838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1839 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1840 ]
1841 );
1842
1843 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1844 // and should move the second cursor back to the first non-whitespace character in the line.
1845 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1846 assert_eq!(
1847 editor.selections.display_ranges(cx),
1848 &[
1849 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1851 ]
1852 );
1853
1854 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1855 // and to the first non-whitespace character in the line for the second cursor.
1856 editor.move_to_end_of_line(&move_to_end, window, cx);
1857 editor.move_left(&MoveLeft, window, cx);
1858 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1859 assert_eq!(
1860 editor.selections.display_ranges(cx),
1861 &[
1862 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1863 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1864 ]
1865 );
1866
1867 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1868 // and should select to the beginning of the line for the second cursor.
1869 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1870 assert_eq!(
1871 editor.selections.display_ranges(cx),
1872 &[
1873 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1874 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1875 ]
1876 );
1877
1878 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1879 // and should delete to the first non-whitespace character in the line for the second cursor.
1880 editor.move_to_end_of_line(&move_to_end, window, cx);
1881 editor.move_left(&MoveLeft, window, cx);
1882 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1883 assert_eq!(editor.text(cx), "c\n f");
1884 });
1885}
1886
1887#[gpui::test]
1888fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1889 init_test(cx, |_| {});
1890
1891 let editor = cx.add_window(|window, cx| {
1892 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1893 build_editor(buffer, window, cx)
1894 });
1895 _ = editor.update(cx, |editor, window, cx| {
1896 editor.change_selections(None, window, cx, |s| {
1897 s.select_display_ranges([
1898 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1899 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1900 ])
1901 });
1902
1903 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1904 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1905
1906 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1907 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1908
1909 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1910 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1911
1912 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1913 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1914
1915 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1916 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1917
1918 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1919 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1920
1921 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1922 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1923
1924 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1925 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1926
1927 editor.move_right(&MoveRight, window, cx);
1928 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1929 assert_selection_ranges(
1930 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1931 editor,
1932 cx,
1933 );
1934
1935 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1936 assert_selection_ranges(
1937 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1938 editor,
1939 cx,
1940 );
1941
1942 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1943 assert_selection_ranges(
1944 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1945 editor,
1946 cx,
1947 );
1948 });
1949}
1950
1951#[gpui::test]
1952fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1953 init_test(cx, |_| {});
1954
1955 let editor = cx.add_window(|window, cx| {
1956 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1957 build_editor(buffer, window, cx)
1958 });
1959
1960 _ = editor.update(cx, |editor, window, cx| {
1961 editor.set_wrap_width(Some(140.0.into()), cx);
1962 assert_eq!(
1963 editor.display_text(cx),
1964 "use one::{\n two::three::\n four::five\n};"
1965 );
1966
1967 editor.change_selections(None, window, cx, |s| {
1968 s.select_display_ranges([
1969 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1970 ]);
1971 });
1972
1973 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1974 assert_eq!(
1975 editor.selections.display_ranges(cx),
1976 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1977 );
1978
1979 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1980 assert_eq!(
1981 editor.selections.display_ranges(cx),
1982 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1983 );
1984
1985 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1986 assert_eq!(
1987 editor.selections.display_ranges(cx),
1988 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1989 );
1990
1991 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1992 assert_eq!(
1993 editor.selections.display_ranges(cx),
1994 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1995 );
1996
1997 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1998 assert_eq!(
1999 editor.selections.display_ranges(cx),
2000 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2001 );
2002
2003 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2004 assert_eq!(
2005 editor.selections.display_ranges(cx),
2006 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2007 );
2008 });
2009}
2010
2011#[gpui::test]
2012async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2013 init_test(cx, |_| {});
2014 let mut cx = EditorTestContext::new(cx).await;
2015
2016 let line_height = cx.editor(|editor, window, _| {
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(window.rem_size())
2022 });
2023 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2024
2025 cx.set_state(
2026 &r#"ˇone
2027 two
2028
2029 three
2030 fourˇ
2031 five
2032
2033 six"#
2034 .unindent(),
2035 );
2036
2037 cx.update_editor(|editor, window, cx| {
2038 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2039 });
2040 cx.assert_editor_state(
2041 &r#"one
2042 two
2043 ˇ
2044 three
2045 four
2046 five
2047 ˇ
2048 six"#
2049 .unindent(),
2050 );
2051
2052 cx.update_editor(|editor, window, cx| {
2053 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2054 });
2055 cx.assert_editor_state(
2056 &r#"one
2057 two
2058
2059 three
2060 four
2061 five
2062 ˇ
2063 sixˇ"#
2064 .unindent(),
2065 );
2066
2067 cx.update_editor(|editor, window, cx| {
2068 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2069 });
2070 cx.assert_editor_state(
2071 &r#"one
2072 two
2073
2074 three
2075 four
2076 five
2077
2078 sixˇ"#
2079 .unindent(),
2080 );
2081
2082 cx.update_editor(|editor, window, cx| {
2083 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2084 });
2085 cx.assert_editor_state(
2086 &r#"one
2087 two
2088
2089 three
2090 four
2091 five
2092 ˇ
2093 six"#
2094 .unindent(),
2095 );
2096
2097 cx.update_editor(|editor, window, cx| {
2098 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2099 });
2100 cx.assert_editor_state(
2101 &r#"one
2102 two
2103 ˇ
2104 three
2105 four
2106 five
2107
2108 six"#
2109 .unindent(),
2110 );
2111
2112 cx.update_editor(|editor, window, cx| {
2113 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2114 });
2115 cx.assert_editor_state(
2116 &r#"ˇone
2117 two
2118
2119 three
2120 four
2121 five
2122
2123 six"#
2124 .unindent(),
2125 );
2126}
2127
2128#[gpui::test]
2129async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2130 init_test(cx, |_| {});
2131 let mut cx = EditorTestContext::new(cx).await;
2132 let line_height = cx.editor(|editor, window, _| {
2133 editor
2134 .style()
2135 .unwrap()
2136 .text
2137 .line_height_in_pixels(window.rem_size())
2138 });
2139 let window = cx.window;
2140 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2141
2142 cx.set_state(
2143 r#"ˇone
2144 two
2145 three
2146 four
2147 five
2148 six
2149 seven
2150 eight
2151 nine
2152 ten
2153 "#,
2154 );
2155
2156 cx.update_editor(|editor, window, cx| {
2157 assert_eq!(
2158 editor.snapshot(window, cx).scroll_position(),
2159 gpui::Point::new(0., 0.)
2160 );
2161 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2162 assert_eq!(
2163 editor.snapshot(window, cx).scroll_position(),
2164 gpui::Point::new(0., 3.)
2165 );
2166 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2167 assert_eq!(
2168 editor.snapshot(window, cx).scroll_position(),
2169 gpui::Point::new(0., 6.)
2170 );
2171 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2172 assert_eq!(
2173 editor.snapshot(window, cx).scroll_position(),
2174 gpui::Point::new(0., 3.)
2175 );
2176
2177 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2178 assert_eq!(
2179 editor.snapshot(window, cx).scroll_position(),
2180 gpui::Point::new(0., 1.)
2181 );
2182 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2183 assert_eq!(
2184 editor.snapshot(window, cx).scroll_position(),
2185 gpui::Point::new(0., 3.)
2186 );
2187 });
2188}
2189
2190#[gpui::test]
2191async fn test_autoscroll(cx: &mut TestAppContext) {
2192 init_test(cx, |_| {});
2193 let mut cx = EditorTestContext::new(cx).await;
2194
2195 let line_height = cx.update_editor(|editor, window, cx| {
2196 editor.set_vertical_scroll_margin(2, cx);
2197 editor
2198 .style()
2199 .unwrap()
2200 .text
2201 .line_height_in_pixels(window.rem_size())
2202 });
2203 let window = cx.window;
2204 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2205
2206 cx.set_state(
2207 r#"ˇone
2208 two
2209 three
2210 four
2211 five
2212 six
2213 seven
2214 eight
2215 nine
2216 ten
2217 "#,
2218 );
2219 cx.update_editor(|editor, window, cx| {
2220 assert_eq!(
2221 editor.snapshot(window, cx).scroll_position(),
2222 gpui::Point::new(0., 0.0)
2223 );
2224 });
2225
2226 // Add a cursor below the visible area. Since both cursors cannot fit
2227 // on screen, the editor autoscrolls to reveal the newest cursor, and
2228 // allows the vertical scroll margin below that cursor.
2229 cx.update_editor(|editor, window, cx| {
2230 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2231 selections.select_ranges([
2232 Point::new(0, 0)..Point::new(0, 0),
2233 Point::new(6, 0)..Point::new(6, 0),
2234 ]);
2235 })
2236 });
2237 cx.update_editor(|editor, window, cx| {
2238 assert_eq!(
2239 editor.snapshot(window, cx).scroll_position(),
2240 gpui::Point::new(0., 3.0)
2241 );
2242 });
2243
2244 // Move down. The editor cursor scrolls down to track the newest cursor.
2245 cx.update_editor(|editor, window, cx| {
2246 editor.move_down(&Default::default(), window, cx);
2247 });
2248 cx.update_editor(|editor, window, cx| {
2249 assert_eq!(
2250 editor.snapshot(window, cx).scroll_position(),
2251 gpui::Point::new(0., 4.0)
2252 );
2253 });
2254
2255 // Add a cursor above the visible area. Since both cursors fit on screen,
2256 // the editor scrolls to show both.
2257 cx.update_editor(|editor, window, cx| {
2258 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2259 selections.select_ranges([
2260 Point::new(1, 0)..Point::new(1, 0),
2261 Point::new(6, 0)..Point::new(6, 0),
2262 ]);
2263 })
2264 });
2265 cx.update_editor(|editor, window, cx| {
2266 assert_eq!(
2267 editor.snapshot(window, cx).scroll_position(),
2268 gpui::Point::new(0., 1.0)
2269 );
2270 });
2271}
2272
2273#[gpui::test]
2274async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2275 init_test(cx, |_| {});
2276 let mut cx = EditorTestContext::new(cx).await;
2277
2278 let line_height = cx.editor(|editor, window, _cx| {
2279 editor
2280 .style()
2281 .unwrap()
2282 .text
2283 .line_height_in_pixels(window.rem_size())
2284 });
2285 let window = cx.window;
2286 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2287 cx.set_state(
2288 &r#"
2289 ˇone
2290 two
2291 threeˇ
2292 four
2293 five
2294 six
2295 seven
2296 eight
2297 nine
2298 ten
2299 "#
2300 .unindent(),
2301 );
2302
2303 cx.update_editor(|editor, window, cx| {
2304 editor.move_page_down(&MovePageDown::default(), window, cx)
2305 });
2306 cx.assert_editor_state(
2307 &r#"
2308 one
2309 two
2310 three
2311 ˇfour
2312 five
2313 sixˇ
2314 seven
2315 eight
2316 nine
2317 ten
2318 "#
2319 .unindent(),
2320 );
2321
2322 cx.update_editor(|editor, window, cx| {
2323 editor.move_page_down(&MovePageDown::default(), window, cx)
2324 });
2325 cx.assert_editor_state(
2326 &r#"
2327 one
2328 two
2329 three
2330 four
2331 five
2332 six
2333 ˇseven
2334 eight
2335 nineˇ
2336 ten
2337 "#
2338 .unindent(),
2339 );
2340
2341 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2342 cx.assert_editor_state(
2343 &r#"
2344 one
2345 two
2346 three
2347 ˇfour
2348 five
2349 sixˇ
2350 seven
2351 eight
2352 nine
2353 ten
2354 "#
2355 .unindent(),
2356 );
2357
2358 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2359 cx.assert_editor_state(
2360 &r#"
2361 ˇone
2362 two
2363 threeˇ
2364 four
2365 five
2366 six
2367 seven
2368 eight
2369 nine
2370 ten
2371 "#
2372 .unindent(),
2373 );
2374
2375 // Test select collapsing
2376 cx.update_editor(|editor, window, cx| {
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 editor.move_page_down(&MovePageDown::default(), window, cx);
2380 });
2381 cx.assert_editor_state(
2382 &r#"
2383 one
2384 two
2385 three
2386 four
2387 five
2388 six
2389 seven
2390 eight
2391 nine
2392 ˇten
2393 ˇ"#
2394 .unindent(),
2395 );
2396}
2397
2398#[gpui::test]
2399async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2400 init_test(cx, |_| {});
2401 let mut cx = EditorTestContext::new(cx).await;
2402 cx.set_state("one «two threeˇ» four");
2403 cx.update_editor(|editor, window, cx| {
2404 editor.delete_to_beginning_of_line(
2405 &DeleteToBeginningOfLine {
2406 stop_at_indent: false,
2407 },
2408 window,
2409 cx,
2410 );
2411 assert_eq!(editor.text(cx), " four");
2412 });
2413}
2414
2415#[gpui::test]
2416fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2417 init_test(cx, |_| {});
2418
2419 let editor = cx.add_window(|window, cx| {
2420 let buffer = MultiBuffer::build_simple("one two three four", cx);
2421 build_editor(buffer.clone(), window, cx)
2422 });
2423
2424 _ = editor.update(cx, |editor, window, cx| {
2425 editor.change_selections(None, window, cx, |s| {
2426 s.select_display_ranges([
2427 // an empty selection - the preceding word fragment is deleted
2428 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2429 // characters selected - they are deleted
2430 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2431 ])
2432 });
2433 editor.delete_to_previous_word_start(
2434 &DeleteToPreviousWordStart {
2435 ignore_newlines: false,
2436 },
2437 window,
2438 cx,
2439 );
2440 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2441 });
2442
2443 _ = editor.update(cx, |editor, window, cx| {
2444 editor.change_selections(None, window, cx, |s| {
2445 s.select_display_ranges([
2446 // an empty selection - the following word fragment is deleted
2447 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2448 // characters selected - they are deleted
2449 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2450 ])
2451 });
2452 editor.delete_to_next_word_end(
2453 &DeleteToNextWordEnd {
2454 ignore_newlines: false,
2455 },
2456 window,
2457 cx,
2458 );
2459 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2460 });
2461}
2462
2463#[gpui::test]
2464fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2465 init_test(cx, |_| {});
2466
2467 let editor = cx.add_window(|window, cx| {
2468 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2469 build_editor(buffer.clone(), window, cx)
2470 });
2471 let del_to_prev_word_start = DeleteToPreviousWordStart {
2472 ignore_newlines: false,
2473 };
2474 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2475 ignore_newlines: true,
2476 };
2477
2478 _ = editor.update(cx, |editor, window, cx| {
2479 editor.change_selections(None, window, cx, |s| {
2480 s.select_display_ranges([
2481 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2482 ])
2483 });
2484 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2485 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2486 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2487 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2488 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2489 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2490 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2491 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2492 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2493 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2494 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2495 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2496 });
2497}
2498
2499#[gpui::test]
2500fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2501 init_test(cx, |_| {});
2502
2503 let editor = cx.add_window(|window, cx| {
2504 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2505 build_editor(buffer.clone(), window, cx)
2506 });
2507 let del_to_next_word_end = DeleteToNextWordEnd {
2508 ignore_newlines: false,
2509 };
2510 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2511 ignore_newlines: true,
2512 };
2513
2514 _ = editor.update(cx, |editor, window, cx| {
2515 editor.change_selections(None, window, cx, |s| {
2516 s.select_display_ranges([
2517 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2518 ])
2519 });
2520 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2521 assert_eq!(
2522 editor.buffer.read(cx).read(cx).text(),
2523 "one\n two\nthree\n four"
2524 );
2525 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2526 assert_eq!(
2527 editor.buffer.read(cx).read(cx).text(),
2528 "\n two\nthree\n four"
2529 );
2530 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2531 assert_eq!(
2532 editor.buffer.read(cx).read(cx).text(),
2533 "two\nthree\n four"
2534 );
2535 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2536 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2537 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2538 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2539 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2540 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2541 });
2542}
2543
2544#[gpui::test]
2545fn test_newline(cx: &mut TestAppContext) {
2546 init_test(cx, |_| {});
2547
2548 let editor = cx.add_window(|window, cx| {
2549 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2550 build_editor(buffer.clone(), window, cx)
2551 });
2552
2553 _ = editor.update(cx, |editor, window, cx| {
2554 editor.change_selections(None, window, cx, |s| {
2555 s.select_display_ranges([
2556 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2557 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2558 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2559 ])
2560 });
2561
2562 editor.newline(&Newline, window, cx);
2563 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2564 });
2565}
2566
2567#[gpui::test]
2568fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|window, cx| {
2572 let buffer = MultiBuffer::build_simple(
2573 "
2574 a
2575 b(
2576 X
2577 )
2578 c(
2579 X
2580 )
2581 "
2582 .unindent()
2583 .as_str(),
2584 cx,
2585 );
2586 let mut editor = build_editor(buffer.clone(), window, cx);
2587 editor.change_selections(None, window, cx, |s| {
2588 s.select_ranges([
2589 Point::new(2, 4)..Point::new(2, 5),
2590 Point::new(5, 4)..Point::new(5, 5),
2591 ])
2592 });
2593 editor
2594 });
2595
2596 _ = editor.update(cx, |editor, window, cx| {
2597 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2598 editor.buffer.update(cx, |buffer, cx| {
2599 buffer.edit(
2600 [
2601 (Point::new(1, 2)..Point::new(3, 0), ""),
2602 (Point::new(4, 2)..Point::new(6, 0), ""),
2603 ],
2604 None,
2605 cx,
2606 );
2607 assert_eq!(
2608 buffer.read(cx).text(),
2609 "
2610 a
2611 b()
2612 c()
2613 "
2614 .unindent()
2615 );
2616 });
2617 assert_eq!(
2618 editor.selections.ranges(cx),
2619 &[
2620 Point::new(1, 2)..Point::new(1, 2),
2621 Point::new(2, 2)..Point::new(2, 2),
2622 ],
2623 );
2624
2625 editor.newline(&Newline, window, cx);
2626 assert_eq!(
2627 editor.text(cx),
2628 "
2629 a
2630 b(
2631 )
2632 c(
2633 )
2634 "
2635 .unindent()
2636 );
2637
2638 // The selections are moved after the inserted newlines
2639 assert_eq!(
2640 editor.selections.ranges(cx),
2641 &[
2642 Point::new(2, 0)..Point::new(2, 0),
2643 Point::new(4, 0)..Point::new(4, 0),
2644 ],
2645 );
2646 });
2647}
2648
2649#[gpui::test]
2650async fn test_newline_above(cx: &mut TestAppContext) {
2651 init_test(cx, |settings| {
2652 settings.defaults.tab_size = NonZeroU32::new(4)
2653 });
2654
2655 let language = Arc::new(
2656 Language::new(
2657 LanguageConfig::default(),
2658 Some(tree_sitter_rust::LANGUAGE.into()),
2659 )
2660 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2661 .unwrap(),
2662 );
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2666 cx.set_state(indoc! {"
2667 const a: ˇA = (
2668 (ˇ
2669 «const_functionˇ»(ˇ),
2670 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2671 )ˇ
2672 ˇ);ˇ
2673 "});
2674
2675 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2676 cx.assert_editor_state(indoc! {"
2677 ˇ
2678 const a: A = (
2679 ˇ
2680 (
2681 ˇ
2682 ˇ
2683 const_function(),
2684 ˇ
2685 ˇ
2686 ˇ
2687 ˇ
2688 something_else,
2689 ˇ
2690 )
2691 ˇ
2692 ˇ
2693 );
2694 "});
2695}
2696
2697#[gpui::test]
2698async fn test_newline_below(cx: &mut TestAppContext) {
2699 init_test(cx, |settings| {
2700 settings.defaults.tab_size = NonZeroU32::new(4)
2701 });
2702
2703 let language = Arc::new(
2704 Language::new(
2705 LanguageConfig::default(),
2706 Some(tree_sitter_rust::LANGUAGE.into()),
2707 )
2708 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2709 .unwrap(),
2710 );
2711
2712 let mut cx = EditorTestContext::new(cx).await;
2713 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2714 cx.set_state(indoc! {"
2715 const a: ˇA = (
2716 (ˇ
2717 «const_functionˇ»(ˇ),
2718 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2719 )ˇ
2720 ˇ);ˇ
2721 "});
2722
2723 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2724 cx.assert_editor_state(indoc! {"
2725 const a: A = (
2726 ˇ
2727 (
2728 ˇ
2729 const_function(),
2730 ˇ
2731 ˇ
2732 something_else,
2733 ˇ
2734 ˇ
2735 ˇ
2736 ˇ
2737 )
2738 ˇ
2739 );
2740 ˇ
2741 ˇ
2742 "});
2743}
2744
2745#[gpui::test]
2746async fn test_newline_comments(cx: &mut TestAppContext) {
2747 init_test(cx, |settings| {
2748 settings.defaults.tab_size = NonZeroU32::new(4)
2749 });
2750
2751 let language = Arc::new(Language::new(
2752 LanguageConfig {
2753 line_comments: vec!["//".into()],
2754 ..LanguageConfig::default()
2755 },
2756 None,
2757 ));
2758 {
2759 let mut cx = EditorTestContext::new(cx).await;
2760 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2761 cx.set_state(indoc! {"
2762 // Fooˇ
2763 "});
2764
2765 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2766 cx.assert_editor_state(indoc! {"
2767 // Foo
2768 //ˇ
2769 "});
2770 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2771 cx.set_state(indoc! {"
2772 ˇ// Foo
2773 "});
2774 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2775 cx.assert_editor_state(indoc! {"
2776
2777 ˇ// Foo
2778 "});
2779 }
2780 // Ensure that comment continuations can be disabled.
2781 update_test_language_settings(cx, |settings| {
2782 settings.defaults.extend_comment_on_newline = Some(false);
2783 });
2784 let mut cx = EditorTestContext::new(cx).await;
2785 cx.set_state(indoc! {"
2786 // Fooˇ
2787 "});
2788 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2789 cx.assert_editor_state(indoc! {"
2790 // Foo
2791 ˇ
2792 "});
2793}
2794
2795#[gpui::test]
2796fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2797 init_test(cx, |_| {});
2798
2799 let editor = cx.add_window(|window, cx| {
2800 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2801 let mut editor = build_editor(buffer.clone(), window, cx);
2802 editor.change_selections(None, window, cx, |s| {
2803 s.select_ranges([3..4, 11..12, 19..20])
2804 });
2805 editor
2806 });
2807
2808 _ = editor.update(cx, |editor, window, cx| {
2809 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2810 editor.buffer.update(cx, |buffer, cx| {
2811 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2812 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2813 });
2814 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2815
2816 editor.insert("Z", window, cx);
2817 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2818
2819 // The selections are moved after the inserted characters
2820 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2821 });
2822}
2823
2824#[gpui::test]
2825async fn test_tab(cx: &mut TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.tab_size = NonZeroU32::new(3)
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831 cx.set_state(indoc! {"
2832 ˇabˇc
2833 ˇ🏀ˇ🏀ˇefg
2834 dˇ
2835 "});
2836 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2837 cx.assert_editor_state(indoc! {"
2838 ˇab ˇc
2839 ˇ🏀 ˇ🏀 ˇefg
2840 d ˇ
2841 "});
2842
2843 cx.set_state(indoc! {"
2844 a
2845 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2846 "});
2847 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2848 cx.assert_editor_state(indoc! {"
2849 a
2850 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2851 "});
2852}
2853
2854#[gpui::test]
2855async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2856 init_test(cx, |_| {});
2857
2858 let mut cx = EditorTestContext::new(cx).await;
2859 let language = Arc::new(
2860 Language::new(
2861 LanguageConfig::default(),
2862 Some(tree_sitter_rust::LANGUAGE.into()),
2863 )
2864 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2865 .unwrap(),
2866 );
2867 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2868
2869 // cursors that are already at the suggested indent level insert
2870 // a soft tab. cursors that are to the left of the suggested indent
2871 // auto-indent their line.
2872 cx.set_state(indoc! {"
2873 ˇ
2874 const a: B = (
2875 c(
2876 d(
2877 ˇ
2878 )
2879 ˇ
2880 ˇ )
2881 );
2882 "});
2883 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2884 cx.assert_editor_state(indoc! {"
2885 ˇ
2886 const a: B = (
2887 c(
2888 d(
2889 ˇ
2890 )
2891 ˇ
2892 ˇ)
2893 );
2894 "});
2895
2896 // handle auto-indent when there are multiple cursors on the same line
2897 cx.set_state(indoc! {"
2898 const a: B = (
2899 c(
2900 ˇ ˇ
2901 ˇ )
2902 );
2903 "});
2904 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2905 cx.assert_editor_state(indoc! {"
2906 const a: B = (
2907 c(
2908 ˇ
2909 ˇ)
2910 );
2911 "});
2912}
2913
2914#[gpui::test]
2915async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2916 init_test(cx, |settings| {
2917 settings.defaults.tab_size = NonZeroU32::new(4)
2918 });
2919
2920 let language = Arc::new(
2921 Language::new(
2922 LanguageConfig::default(),
2923 Some(tree_sitter_rust::LANGUAGE.into()),
2924 )
2925 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2926 .unwrap(),
2927 );
2928
2929 let mut cx = EditorTestContext::new(cx).await;
2930 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2931 cx.set_state(indoc! {"
2932 fn a() {
2933 if b {
2934 \t ˇc
2935 }
2936 }
2937 "});
2938
2939 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2940 cx.assert_editor_state(indoc! {"
2941 fn a() {
2942 if b {
2943 ˇc
2944 }
2945 }
2946 "});
2947}
2948
2949#[gpui::test]
2950async fn test_indent_outdent(cx: &mut TestAppContext) {
2951 init_test(cx, |settings| {
2952 settings.defaults.tab_size = NonZeroU32::new(4);
2953 });
2954
2955 let mut cx = EditorTestContext::new(cx).await;
2956
2957 cx.set_state(indoc! {"
2958 «oneˇ» «twoˇ»
2959 three
2960 four
2961 "});
2962 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2963 cx.assert_editor_state(indoc! {"
2964 «oneˇ» «twoˇ»
2965 three
2966 four
2967 "});
2968
2969 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2970 cx.assert_editor_state(indoc! {"
2971 «oneˇ» «twoˇ»
2972 three
2973 four
2974 "});
2975
2976 // select across line ending
2977 cx.set_state(indoc! {"
2978 one two
2979 t«hree
2980 ˇ» four
2981 "});
2982 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2983 cx.assert_editor_state(indoc! {"
2984 one two
2985 t«hree
2986 ˇ» four
2987 "});
2988
2989 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2990 cx.assert_editor_state(indoc! {"
2991 one two
2992 t«hree
2993 ˇ» four
2994 "});
2995
2996 // Ensure that indenting/outdenting works when the cursor is at column 0.
2997 cx.set_state(indoc! {"
2998 one two
2999 ˇthree
3000 four
3001 "});
3002 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3003 cx.assert_editor_state(indoc! {"
3004 one two
3005 ˇthree
3006 four
3007 "});
3008
3009 cx.set_state(indoc! {"
3010 one two
3011 ˇ three
3012 four
3013 "});
3014 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3015 cx.assert_editor_state(indoc! {"
3016 one two
3017 ˇthree
3018 four
3019 "});
3020}
3021
3022#[gpui::test]
3023async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3024 init_test(cx, |settings| {
3025 settings.defaults.hard_tabs = Some(true);
3026 });
3027
3028 let mut cx = EditorTestContext::new(cx).await;
3029
3030 // select two ranges on one line
3031 cx.set_state(indoc! {"
3032 «oneˇ» «twoˇ»
3033 three
3034 four
3035 "});
3036 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3037 cx.assert_editor_state(indoc! {"
3038 \t«oneˇ» «twoˇ»
3039 three
3040 four
3041 "});
3042 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3043 cx.assert_editor_state(indoc! {"
3044 \t\t«oneˇ» «twoˇ»
3045 three
3046 four
3047 "});
3048 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3049 cx.assert_editor_state(indoc! {"
3050 \t«oneˇ» «twoˇ»
3051 three
3052 four
3053 "});
3054 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3055 cx.assert_editor_state(indoc! {"
3056 «oneˇ» «twoˇ»
3057 three
3058 four
3059 "});
3060
3061 // select across a line ending
3062 cx.set_state(indoc! {"
3063 one two
3064 t«hree
3065 ˇ»four
3066 "});
3067 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3068 cx.assert_editor_state(indoc! {"
3069 one two
3070 \tt«hree
3071 ˇ»four
3072 "});
3073 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3074 cx.assert_editor_state(indoc! {"
3075 one two
3076 \t\tt«hree
3077 ˇ»four
3078 "});
3079 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3080 cx.assert_editor_state(indoc! {"
3081 one two
3082 \tt«hree
3083 ˇ»four
3084 "});
3085 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3086 cx.assert_editor_state(indoc! {"
3087 one two
3088 t«hree
3089 ˇ»four
3090 "});
3091
3092 // Ensure that indenting/outdenting works when the cursor is at column 0.
3093 cx.set_state(indoc! {"
3094 one two
3095 ˇthree
3096 four
3097 "});
3098 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3099 cx.assert_editor_state(indoc! {"
3100 one two
3101 ˇthree
3102 four
3103 "});
3104 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3105 cx.assert_editor_state(indoc! {"
3106 one two
3107 \tˇthree
3108 four
3109 "});
3110 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3111 cx.assert_editor_state(indoc! {"
3112 one two
3113 ˇthree
3114 four
3115 "});
3116}
3117
3118#[gpui::test]
3119fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3120 init_test(cx, |settings| {
3121 settings.languages.extend([
3122 (
3123 "TOML".into(),
3124 LanguageSettingsContent {
3125 tab_size: NonZeroU32::new(2),
3126 ..Default::default()
3127 },
3128 ),
3129 (
3130 "Rust".into(),
3131 LanguageSettingsContent {
3132 tab_size: NonZeroU32::new(4),
3133 ..Default::default()
3134 },
3135 ),
3136 ]);
3137 });
3138
3139 let toml_language = Arc::new(Language::new(
3140 LanguageConfig {
3141 name: "TOML".into(),
3142 ..Default::default()
3143 },
3144 None,
3145 ));
3146 let rust_language = Arc::new(Language::new(
3147 LanguageConfig {
3148 name: "Rust".into(),
3149 ..Default::default()
3150 },
3151 None,
3152 ));
3153
3154 let toml_buffer =
3155 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3156 let rust_buffer =
3157 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3158 let multibuffer = cx.new(|cx| {
3159 let mut multibuffer = MultiBuffer::new(ReadWrite);
3160 multibuffer.push_excerpts(
3161 toml_buffer.clone(),
3162 [ExcerptRange {
3163 context: Point::new(0, 0)..Point::new(2, 0),
3164 primary: None,
3165 }],
3166 cx,
3167 );
3168 multibuffer.push_excerpts(
3169 rust_buffer.clone(),
3170 [ExcerptRange {
3171 context: Point::new(0, 0)..Point::new(1, 0),
3172 primary: None,
3173 }],
3174 cx,
3175 );
3176 multibuffer
3177 });
3178
3179 cx.add_window(|window, cx| {
3180 let mut editor = build_editor(multibuffer, window, cx);
3181
3182 assert_eq!(
3183 editor.text(cx),
3184 indoc! {"
3185 a = 1
3186 b = 2
3187
3188 const c: usize = 3;
3189 "}
3190 );
3191
3192 select_ranges(
3193 &mut editor,
3194 indoc! {"
3195 «aˇ» = 1
3196 b = 2
3197
3198 «const c:ˇ» usize = 3;
3199 "},
3200 window,
3201 cx,
3202 );
3203
3204 editor.tab(&Tab, window, cx);
3205 assert_text_with_selections(
3206 &mut editor,
3207 indoc! {"
3208 «aˇ» = 1
3209 b = 2
3210
3211 «const c:ˇ» usize = 3;
3212 "},
3213 cx,
3214 );
3215 editor.backtab(&Backtab, window, cx);
3216 assert_text_with_selections(
3217 &mut editor,
3218 indoc! {"
3219 «aˇ» = 1
3220 b = 2
3221
3222 «const c:ˇ» usize = 3;
3223 "},
3224 cx,
3225 );
3226
3227 editor
3228 });
3229}
3230
3231#[gpui::test]
3232async fn test_backspace(cx: &mut TestAppContext) {
3233 init_test(cx, |_| {});
3234
3235 let mut cx = EditorTestContext::new(cx).await;
3236
3237 // Basic backspace
3238 cx.set_state(indoc! {"
3239 onˇe two three
3240 fou«rˇ» five six
3241 seven «ˇeight nine
3242 »ten
3243 "});
3244 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3245 cx.assert_editor_state(indoc! {"
3246 oˇe two three
3247 fouˇ five six
3248 seven ˇten
3249 "});
3250
3251 // Test backspace inside and around indents
3252 cx.set_state(indoc! {"
3253 zero
3254 ˇone
3255 ˇtwo
3256 ˇ ˇ ˇ three
3257 ˇ ˇ four
3258 "});
3259 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3260 cx.assert_editor_state(indoc! {"
3261 zero
3262 ˇone
3263 ˇtwo
3264 ˇ threeˇ four
3265 "});
3266
3267 // Test backspace with line_mode set to true
3268 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3269 cx.set_state(indoc! {"
3270 The ˇquick ˇbrown
3271 fox jumps over
3272 the lazy dog
3273 ˇThe qu«ick bˇ»rown"});
3274 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3275 cx.assert_editor_state(indoc! {"
3276 ˇfox jumps over
3277 the lazy dogˇ"});
3278}
3279
3280#[gpui::test]
3281async fn test_delete(cx: &mut TestAppContext) {
3282 init_test(cx, |_| {});
3283
3284 let mut cx = EditorTestContext::new(cx).await;
3285 cx.set_state(indoc! {"
3286 onˇe two three
3287 fou«rˇ» five six
3288 seven «ˇeight nine
3289 »ten
3290 "});
3291 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3292 cx.assert_editor_state(indoc! {"
3293 onˇ two three
3294 fouˇ five six
3295 seven ˇten
3296 "});
3297
3298 // Test backspace with line_mode set to true
3299 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3300 cx.set_state(indoc! {"
3301 The ˇquick ˇbrown
3302 fox «ˇjum»ps over
3303 the lazy dog
3304 ˇThe qu«ick bˇ»rown"});
3305 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3306 cx.assert_editor_state("ˇthe lazy dogˇ");
3307}
3308
3309#[gpui::test]
3310fn test_delete_line(cx: &mut TestAppContext) {
3311 init_test(cx, |_| {});
3312
3313 let editor = cx.add_window(|window, cx| {
3314 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3315 build_editor(buffer, window, cx)
3316 });
3317 _ = editor.update(cx, |editor, window, cx| {
3318 editor.change_selections(None, window, cx, |s| {
3319 s.select_display_ranges([
3320 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3321 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3322 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3323 ])
3324 });
3325 editor.delete_line(&DeleteLine, window, cx);
3326 assert_eq!(editor.display_text(cx), "ghi");
3327 assert_eq!(
3328 editor.selections.display_ranges(cx),
3329 vec![
3330 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3331 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3332 ]
3333 );
3334 });
3335
3336 let editor = cx.add_window(|window, cx| {
3337 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3338 build_editor(buffer, window, cx)
3339 });
3340 _ = editor.update(cx, |editor, window, cx| {
3341 editor.change_selections(None, window, cx, |s| {
3342 s.select_display_ranges([
3343 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3344 ])
3345 });
3346 editor.delete_line(&DeleteLine, window, cx);
3347 assert_eq!(editor.display_text(cx), "ghi\n");
3348 assert_eq!(
3349 editor.selections.display_ranges(cx),
3350 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3351 );
3352 });
3353}
3354
3355#[gpui::test]
3356fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3357 init_test(cx, |_| {});
3358
3359 cx.add_window(|window, cx| {
3360 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3361 let mut editor = build_editor(buffer.clone(), window, cx);
3362 let buffer = buffer.read(cx).as_singleton().unwrap();
3363
3364 assert_eq!(
3365 editor.selections.ranges::<Point>(cx),
3366 &[Point::new(0, 0)..Point::new(0, 0)]
3367 );
3368
3369 // When on single line, replace newline at end by space
3370 editor.join_lines(&JoinLines, window, cx);
3371 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3372 assert_eq!(
3373 editor.selections.ranges::<Point>(cx),
3374 &[Point::new(0, 3)..Point::new(0, 3)]
3375 );
3376
3377 // When multiple lines are selected, remove newlines that are spanned by the selection
3378 editor.change_selections(None, window, cx, |s| {
3379 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3380 });
3381 editor.join_lines(&JoinLines, window, cx);
3382 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3383 assert_eq!(
3384 editor.selections.ranges::<Point>(cx),
3385 &[Point::new(0, 11)..Point::new(0, 11)]
3386 );
3387
3388 // Undo should be transactional
3389 editor.undo(&Undo, window, cx);
3390 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3391 assert_eq!(
3392 editor.selections.ranges::<Point>(cx),
3393 &[Point::new(0, 5)..Point::new(2, 2)]
3394 );
3395
3396 // When joining an empty line don't insert a space
3397 editor.change_selections(None, window, cx, |s| {
3398 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3399 });
3400 editor.join_lines(&JoinLines, window, cx);
3401 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3402 assert_eq!(
3403 editor.selections.ranges::<Point>(cx),
3404 [Point::new(2, 3)..Point::new(2, 3)]
3405 );
3406
3407 // We can remove trailing newlines
3408 editor.join_lines(&JoinLines, window, cx);
3409 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3410 assert_eq!(
3411 editor.selections.ranges::<Point>(cx),
3412 [Point::new(2, 3)..Point::new(2, 3)]
3413 );
3414
3415 // We don't blow up on the last line
3416 editor.join_lines(&JoinLines, window, cx);
3417 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3418 assert_eq!(
3419 editor.selections.ranges::<Point>(cx),
3420 [Point::new(2, 3)..Point::new(2, 3)]
3421 );
3422
3423 // reset to test indentation
3424 editor.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 [
3427 (Point::new(1, 0)..Point::new(1, 2), " "),
3428 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3429 ],
3430 None,
3431 cx,
3432 )
3433 });
3434
3435 // We remove any leading spaces
3436 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3437 editor.change_selections(None, window, cx, |s| {
3438 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3439 });
3440 editor.join_lines(&JoinLines, window, cx);
3441 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3442
3443 // We don't insert a space for a line containing only spaces
3444 editor.join_lines(&JoinLines, window, cx);
3445 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3446
3447 // We ignore any leading tabs
3448 editor.join_lines(&JoinLines, window, cx);
3449 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3450
3451 editor
3452 });
3453}
3454
3455#[gpui::test]
3456fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 cx.add_window(|window, cx| {
3460 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3461 let mut editor = build_editor(buffer.clone(), window, cx);
3462 let buffer = buffer.read(cx).as_singleton().unwrap();
3463
3464 editor.change_selections(None, window, cx, |s| {
3465 s.select_ranges([
3466 Point::new(0, 2)..Point::new(1, 1),
3467 Point::new(1, 2)..Point::new(1, 2),
3468 Point::new(3, 1)..Point::new(3, 2),
3469 ])
3470 });
3471
3472 editor.join_lines(&JoinLines, window, cx);
3473 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3474
3475 assert_eq!(
3476 editor.selections.ranges::<Point>(cx),
3477 [
3478 Point::new(0, 7)..Point::new(0, 7),
3479 Point::new(1, 3)..Point::new(1, 3)
3480 ]
3481 );
3482 editor
3483 });
3484}
3485
3486#[gpui::test]
3487async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3488 init_test(cx, |_| {});
3489
3490 let mut cx = EditorTestContext::new(cx).await;
3491
3492 let diff_base = r#"
3493 Line 0
3494 Line 1
3495 Line 2
3496 Line 3
3497 "#
3498 .unindent();
3499
3500 cx.set_state(
3501 &r#"
3502 ˇLine 0
3503 Line 1
3504 Line 2
3505 Line 3
3506 "#
3507 .unindent(),
3508 );
3509
3510 cx.set_head_text(&diff_base);
3511 executor.run_until_parked();
3512
3513 // Join lines
3514 cx.update_editor(|editor, window, cx| {
3515 editor.join_lines(&JoinLines, window, cx);
3516 });
3517 executor.run_until_parked();
3518
3519 cx.assert_editor_state(
3520 &r#"
3521 Line 0ˇ Line 1
3522 Line 2
3523 Line 3
3524 "#
3525 .unindent(),
3526 );
3527 // Join again
3528 cx.update_editor(|editor, window, cx| {
3529 editor.join_lines(&JoinLines, window, cx);
3530 });
3531 executor.run_until_parked();
3532
3533 cx.assert_editor_state(
3534 &r#"
3535 Line 0 Line 1ˇ Line 2
3536 Line 3
3537 "#
3538 .unindent(),
3539 );
3540}
3541
3542#[gpui::test]
3543async fn test_custom_newlines_cause_no_false_positive_diffs(
3544 executor: BackgroundExecutor,
3545 cx: &mut TestAppContext,
3546) {
3547 init_test(cx, |_| {});
3548 let mut cx = EditorTestContext::new(cx).await;
3549 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3550 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3551 executor.run_until_parked();
3552
3553 cx.update_editor(|editor, window, cx| {
3554 let snapshot = editor.snapshot(window, cx);
3555 assert_eq!(
3556 snapshot
3557 .buffer_snapshot
3558 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3559 .collect::<Vec<_>>(),
3560 Vec::new(),
3561 "Should not have any diffs for files with custom newlines"
3562 );
3563 });
3564}
3565
3566#[gpui::test]
3567async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let mut cx = EditorTestContext::new(cx).await;
3571
3572 // Test sort_lines_case_insensitive()
3573 cx.set_state(indoc! {"
3574 «z
3575 y
3576 x
3577 Z
3578 Y
3579 Xˇ»
3580 "});
3581 cx.update_editor(|e, window, cx| {
3582 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3583 });
3584 cx.assert_editor_state(indoc! {"
3585 «x
3586 X
3587 y
3588 Y
3589 z
3590 Zˇ»
3591 "});
3592
3593 // Test reverse_lines()
3594 cx.set_state(indoc! {"
3595 «5
3596 4
3597 3
3598 2
3599 1ˇ»
3600 "});
3601 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3602 cx.assert_editor_state(indoc! {"
3603 «1
3604 2
3605 3
3606 4
3607 5ˇ»
3608 "});
3609
3610 // Skip testing shuffle_line()
3611
3612 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3613 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3614
3615 // Don't manipulate when cursor is on single line, but expand the selection
3616 cx.set_state(indoc! {"
3617 ddˇdd
3618 ccc
3619 bb
3620 a
3621 "});
3622 cx.update_editor(|e, window, cx| {
3623 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3624 });
3625 cx.assert_editor_state(indoc! {"
3626 «ddddˇ»
3627 ccc
3628 bb
3629 a
3630 "});
3631
3632 // Basic manipulate case
3633 // Start selection moves to column 0
3634 // End of selection shrinks to fit shorter line
3635 cx.set_state(indoc! {"
3636 dd«d
3637 ccc
3638 bb
3639 aaaaaˇ»
3640 "});
3641 cx.update_editor(|e, window, cx| {
3642 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3643 });
3644 cx.assert_editor_state(indoc! {"
3645 «aaaaa
3646 bb
3647 ccc
3648 dddˇ»
3649 "});
3650
3651 // Manipulate case with newlines
3652 cx.set_state(indoc! {"
3653 dd«d
3654 ccc
3655
3656 bb
3657 aaaaa
3658
3659 ˇ»
3660 "});
3661 cx.update_editor(|e, window, cx| {
3662 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3663 });
3664 cx.assert_editor_state(indoc! {"
3665 «
3666
3667 aaaaa
3668 bb
3669 ccc
3670 dddˇ»
3671
3672 "});
3673
3674 // Adding new line
3675 cx.set_state(indoc! {"
3676 aa«a
3677 bbˇ»b
3678 "});
3679 cx.update_editor(|e, window, cx| {
3680 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3681 });
3682 cx.assert_editor_state(indoc! {"
3683 «aaa
3684 bbb
3685 added_lineˇ»
3686 "});
3687
3688 // Removing line
3689 cx.set_state(indoc! {"
3690 aa«a
3691 bbbˇ»
3692 "});
3693 cx.update_editor(|e, window, cx| {
3694 e.manipulate_lines(window, cx, |lines| {
3695 lines.pop();
3696 })
3697 });
3698 cx.assert_editor_state(indoc! {"
3699 «aaaˇ»
3700 "});
3701
3702 // Removing all lines
3703 cx.set_state(indoc! {"
3704 aa«a
3705 bbbˇ»
3706 "});
3707 cx.update_editor(|e, window, cx| {
3708 e.manipulate_lines(window, cx, |lines| {
3709 lines.drain(..);
3710 })
3711 });
3712 cx.assert_editor_state(indoc! {"
3713 ˇ
3714 "});
3715}
3716
3717#[gpui::test]
3718async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3719 init_test(cx, |_| {});
3720
3721 let mut cx = EditorTestContext::new(cx).await;
3722
3723 // Consider continuous selection as single selection
3724 cx.set_state(indoc! {"
3725 Aaa«aa
3726 cˇ»c«c
3727 bb
3728 aaaˇ»aa
3729 "});
3730 cx.update_editor(|e, window, cx| {
3731 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3732 });
3733 cx.assert_editor_state(indoc! {"
3734 «Aaaaa
3735 ccc
3736 bb
3737 aaaaaˇ»
3738 "});
3739
3740 cx.set_state(indoc! {"
3741 Aaa«aa
3742 cˇ»c«c
3743 bb
3744 aaaˇ»aa
3745 "});
3746 cx.update_editor(|e, window, cx| {
3747 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3748 });
3749 cx.assert_editor_state(indoc! {"
3750 «Aaaaa
3751 ccc
3752 bbˇ»
3753 "});
3754
3755 // Consider non continuous selection as distinct dedup operations
3756 cx.set_state(indoc! {"
3757 «aaaaa
3758 bb
3759 aaaaa
3760 aaaaaˇ»
3761
3762 aaa«aaˇ»
3763 "});
3764 cx.update_editor(|e, window, cx| {
3765 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3766 });
3767 cx.assert_editor_state(indoc! {"
3768 «aaaaa
3769 bbˇ»
3770
3771 «aaaaaˇ»
3772 "});
3773}
3774
3775#[gpui::test]
3776async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3777 init_test(cx, |_| {});
3778
3779 let mut cx = EditorTestContext::new(cx).await;
3780
3781 cx.set_state(indoc! {"
3782 «Aaa
3783 aAa
3784 Aaaˇ»
3785 "});
3786 cx.update_editor(|e, window, cx| {
3787 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3788 });
3789 cx.assert_editor_state(indoc! {"
3790 «Aaa
3791 aAaˇ»
3792 "});
3793
3794 cx.set_state(indoc! {"
3795 «Aaa
3796 aAa
3797 aaAˇ»
3798 "});
3799 cx.update_editor(|e, window, cx| {
3800 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3801 });
3802 cx.assert_editor_state(indoc! {"
3803 «Aaaˇ»
3804 "});
3805}
3806
3807#[gpui::test]
3808async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3809 init_test(cx, |_| {});
3810
3811 let mut cx = EditorTestContext::new(cx).await;
3812
3813 // Manipulate with multiple selections on a single line
3814 cx.set_state(indoc! {"
3815 dd«dd
3816 cˇ»c«c
3817 bb
3818 aaaˇ»aa
3819 "});
3820 cx.update_editor(|e, window, cx| {
3821 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3822 });
3823 cx.assert_editor_state(indoc! {"
3824 «aaaaa
3825 bb
3826 ccc
3827 ddddˇ»
3828 "});
3829
3830 // Manipulate with multiple disjoin selections
3831 cx.set_state(indoc! {"
3832 5«
3833 4
3834 3
3835 2
3836 1ˇ»
3837
3838 dd«dd
3839 ccc
3840 bb
3841 aaaˇ»aa
3842 "});
3843 cx.update_editor(|e, window, cx| {
3844 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3845 });
3846 cx.assert_editor_state(indoc! {"
3847 «1
3848 2
3849 3
3850 4
3851 5ˇ»
3852
3853 «aaaaa
3854 bb
3855 ccc
3856 ddddˇ»
3857 "});
3858
3859 // Adding lines on each selection
3860 cx.set_state(indoc! {"
3861 2«
3862 1ˇ»
3863
3864 bb«bb
3865 aaaˇ»aa
3866 "});
3867 cx.update_editor(|e, window, cx| {
3868 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3869 });
3870 cx.assert_editor_state(indoc! {"
3871 «2
3872 1
3873 added lineˇ»
3874
3875 «bbbb
3876 aaaaa
3877 added lineˇ»
3878 "});
3879
3880 // Removing lines on each selection
3881 cx.set_state(indoc! {"
3882 2«
3883 1ˇ»
3884
3885 bb«bb
3886 aaaˇ»aa
3887 "});
3888 cx.update_editor(|e, window, cx| {
3889 e.manipulate_lines(window, cx, |lines| {
3890 lines.pop();
3891 })
3892 });
3893 cx.assert_editor_state(indoc! {"
3894 «2ˇ»
3895
3896 «bbbbˇ»
3897 "});
3898}
3899
3900#[gpui::test]
3901async fn test_manipulate_text(cx: &mut TestAppContext) {
3902 init_test(cx, |_| {});
3903
3904 let mut cx = EditorTestContext::new(cx).await;
3905
3906 // Test convert_to_upper_case()
3907 cx.set_state(indoc! {"
3908 «hello worldˇ»
3909 "});
3910 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3911 cx.assert_editor_state(indoc! {"
3912 «HELLO WORLDˇ»
3913 "});
3914
3915 // Test convert_to_lower_case()
3916 cx.set_state(indoc! {"
3917 «HELLO WORLDˇ»
3918 "});
3919 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3920 cx.assert_editor_state(indoc! {"
3921 «hello worldˇ»
3922 "});
3923
3924 // Test multiple line, single selection case
3925 cx.set_state(indoc! {"
3926 «The quick brown
3927 fox jumps over
3928 the lazy dogˇ»
3929 "});
3930 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3931 cx.assert_editor_state(indoc! {"
3932 «The Quick Brown
3933 Fox Jumps Over
3934 The Lazy Dogˇ»
3935 "});
3936
3937 // Test multiple line, single selection case
3938 cx.set_state(indoc! {"
3939 «The quick brown
3940 fox jumps over
3941 the lazy dogˇ»
3942 "});
3943 cx.update_editor(|e, window, cx| {
3944 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3945 });
3946 cx.assert_editor_state(indoc! {"
3947 «TheQuickBrown
3948 FoxJumpsOver
3949 TheLazyDogˇ»
3950 "});
3951
3952 // From here on out, test more complex cases of manipulate_text()
3953
3954 // Test no selection case - should affect words cursors are in
3955 // Cursor at beginning, middle, and end of word
3956 cx.set_state(indoc! {"
3957 ˇhello big beauˇtiful worldˇ
3958 "});
3959 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3960 cx.assert_editor_state(indoc! {"
3961 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3962 "});
3963
3964 // Test multiple selections on a single line and across multiple lines
3965 cx.set_state(indoc! {"
3966 «Theˇ» quick «brown
3967 foxˇ» jumps «overˇ»
3968 the «lazyˇ» dog
3969 "});
3970 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3971 cx.assert_editor_state(indoc! {"
3972 «THEˇ» quick «BROWN
3973 FOXˇ» jumps «OVERˇ»
3974 the «LAZYˇ» dog
3975 "});
3976
3977 // Test case where text length grows
3978 cx.set_state(indoc! {"
3979 «tschüߡ»
3980 "});
3981 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3982 cx.assert_editor_state(indoc! {"
3983 «TSCHÜSSˇ»
3984 "});
3985
3986 // Test to make sure we don't crash when text shrinks
3987 cx.set_state(indoc! {"
3988 aaa_bbbˇ
3989 "});
3990 cx.update_editor(|e, window, cx| {
3991 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3992 });
3993 cx.assert_editor_state(indoc! {"
3994 «aaaBbbˇ»
3995 "});
3996
3997 // Test to make sure we all aware of the fact that each word can grow and shrink
3998 // Final selections should be aware of this fact
3999 cx.set_state(indoc! {"
4000 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4001 "});
4002 cx.update_editor(|e, window, cx| {
4003 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4004 });
4005 cx.assert_editor_state(indoc! {"
4006 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4007 "});
4008
4009 cx.set_state(indoc! {"
4010 «hElLo, WoRld!ˇ»
4011 "});
4012 cx.update_editor(|e, window, cx| {
4013 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4014 });
4015 cx.assert_editor_state(indoc! {"
4016 «HeLlO, wOrLD!ˇ»
4017 "});
4018}
4019
4020#[gpui::test]
4021fn test_duplicate_line(cx: &mut TestAppContext) {
4022 init_test(cx, |_| {});
4023
4024 let editor = cx.add_window(|window, cx| {
4025 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4026 build_editor(buffer, window, cx)
4027 });
4028 _ = editor.update(cx, |editor, window, cx| {
4029 editor.change_selections(None, window, cx, |s| {
4030 s.select_display_ranges([
4031 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4032 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4033 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4034 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4035 ])
4036 });
4037 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4038 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4039 assert_eq!(
4040 editor.selections.display_ranges(cx),
4041 vec![
4042 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4043 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4044 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4045 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4046 ]
4047 );
4048 });
4049
4050 let editor = cx.add_window(|window, cx| {
4051 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4052 build_editor(buffer, window, cx)
4053 });
4054 _ = editor.update(cx, |editor, window, cx| {
4055 editor.change_selections(None, window, cx, |s| {
4056 s.select_display_ranges([
4057 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4058 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4059 ])
4060 });
4061 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4062 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4063 assert_eq!(
4064 editor.selections.display_ranges(cx),
4065 vec![
4066 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4067 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4068 ]
4069 );
4070 });
4071
4072 // With `move_upwards` the selections stay in place, except for
4073 // the lines inserted above them
4074 let editor = cx.add_window(|window, cx| {
4075 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4076 build_editor(buffer, window, cx)
4077 });
4078 _ = editor.update(cx, |editor, window, cx| {
4079 editor.change_selections(None, window, cx, |s| {
4080 s.select_display_ranges([
4081 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4082 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4083 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4084 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4085 ])
4086 });
4087 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4088 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4089 assert_eq!(
4090 editor.selections.display_ranges(cx),
4091 vec![
4092 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4093 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4094 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4095 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4096 ]
4097 );
4098 });
4099
4100 let editor = cx.add_window(|window, cx| {
4101 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4102 build_editor(buffer, window, cx)
4103 });
4104 _ = editor.update(cx, |editor, window, cx| {
4105 editor.change_selections(None, window, cx, |s| {
4106 s.select_display_ranges([
4107 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4108 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4109 ])
4110 });
4111 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4112 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4113 assert_eq!(
4114 editor.selections.display_ranges(cx),
4115 vec![
4116 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4117 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4118 ]
4119 );
4120 });
4121
4122 let editor = cx.add_window(|window, cx| {
4123 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4124 build_editor(buffer, window, cx)
4125 });
4126 _ = editor.update(cx, |editor, window, cx| {
4127 editor.change_selections(None, window, cx, |s| {
4128 s.select_display_ranges([
4129 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4130 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4131 ])
4132 });
4133 editor.duplicate_selection(&DuplicateSelection, window, cx);
4134 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4135 assert_eq!(
4136 editor.selections.display_ranges(cx),
4137 vec![
4138 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4139 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4140 ]
4141 );
4142 });
4143}
4144
4145#[gpui::test]
4146fn test_move_line_up_down(cx: &mut TestAppContext) {
4147 init_test(cx, |_| {});
4148
4149 let editor = cx.add_window(|window, cx| {
4150 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4151 build_editor(buffer, window, cx)
4152 });
4153 _ = editor.update(cx, |editor, window, cx| {
4154 editor.fold_creases(
4155 vec![
4156 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4158 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4159 ],
4160 true,
4161 window,
4162 cx,
4163 );
4164 editor.change_selections(None, window, cx, |s| {
4165 s.select_display_ranges([
4166 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4167 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4168 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4169 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4170 ])
4171 });
4172 assert_eq!(
4173 editor.display_text(cx),
4174 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4175 );
4176
4177 editor.move_line_up(&MoveLineUp, window, cx);
4178 assert_eq!(
4179 editor.display_text(cx),
4180 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4181 );
4182 assert_eq!(
4183 editor.selections.display_ranges(cx),
4184 vec![
4185 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4186 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4187 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4188 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4189 ]
4190 );
4191 });
4192
4193 _ = editor.update(cx, |editor, window, cx| {
4194 editor.move_line_down(&MoveLineDown, window, cx);
4195 assert_eq!(
4196 editor.display_text(cx),
4197 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4198 );
4199 assert_eq!(
4200 editor.selections.display_ranges(cx),
4201 vec![
4202 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4203 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4204 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4206 ]
4207 );
4208 });
4209
4210 _ = editor.update(cx, |editor, window, cx| {
4211 editor.move_line_down(&MoveLineDown, window, cx);
4212 assert_eq!(
4213 editor.display_text(cx),
4214 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4215 );
4216 assert_eq!(
4217 editor.selections.display_ranges(cx),
4218 vec![
4219 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4220 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4221 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4222 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4223 ]
4224 );
4225 });
4226
4227 _ = editor.update(cx, |editor, window, cx| {
4228 editor.move_line_up(&MoveLineUp, window, cx);
4229 assert_eq!(
4230 editor.display_text(cx),
4231 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4232 );
4233 assert_eq!(
4234 editor.selections.display_ranges(cx),
4235 vec![
4236 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4237 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4238 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4239 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4240 ]
4241 );
4242 });
4243}
4244
4245#[gpui::test]
4246fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4247 init_test(cx, |_| {});
4248
4249 let editor = cx.add_window(|window, cx| {
4250 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4251 build_editor(buffer, window, cx)
4252 });
4253 _ = editor.update(cx, |editor, window, cx| {
4254 let snapshot = editor.buffer.read(cx).snapshot(cx);
4255 editor.insert_blocks(
4256 [BlockProperties {
4257 style: BlockStyle::Fixed,
4258 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4259 height: 1,
4260 render: Arc::new(|_| div().into_any()),
4261 priority: 0,
4262 }],
4263 Some(Autoscroll::fit()),
4264 cx,
4265 );
4266 editor.change_selections(None, window, cx, |s| {
4267 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4268 });
4269 editor.move_line_down(&MoveLineDown, window, cx);
4270 });
4271}
4272
4273#[gpui::test]
4274async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4275 init_test(cx, |_| {});
4276
4277 let mut cx = EditorTestContext::new(cx).await;
4278 cx.set_state(
4279 &"
4280 ˇzero
4281 one
4282 two
4283 three
4284 four
4285 five
4286 "
4287 .unindent(),
4288 );
4289
4290 // Create a four-line block that replaces three lines of text.
4291 cx.update_editor(|editor, window, cx| {
4292 let snapshot = editor.snapshot(window, cx);
4293 let snapshot = &snapshot.buffer_snapshot;
4294 let placement = BlockPlacement::Replace(
4295 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4296 );
4297 editor.insert_blocks(
4298 [BlockProperties {
4299 placement,
4300 height: 4,
4301 style: BlockStyle::Sticky,
4302 render: Arc::new(|_| gpui::div().into_any_element()),
4303 priority: 0,
4304 }],
4305 None,
4306 cx,
4307 );
4308 });
4309
4310 // Move down so that the cursor touches the block.
4311 cx.update_editor(|editor, window, cx| {
4312 editor.move_down(&Default::default(), window, cx);
4313 });
4314 cx.assert_editor_state(
4315 &"
4316 zero
4317 «one
4318 two
4319 threeˇ»
4320 four
4321 five
4322 "
4323 .unindent(),
4324 );
4325
4326 // Move down past the block.
4327 cx.update_editor(|editor, window, cx| {
4328 editor.move_down(&Default::default(), window, cx);
4329 });
4330 cx.assert_editor_state(
4331 &"
4332 zero
4333 one
4334 two
4335 three
4336 ˇfour
4337 five
4338 "
4339 .unindent(),
4340 );
4341}
4342
4343#[gpui::test]
4344fn test_transpose(cx: &mut TestAppContext) {
4345 init_test(cx, |_| {});
4346
4347 _ = cx.add_window(|window, cx| {
4348 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4349 editor.set_style(EditorStyle::default(), window, cx);
4350 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4351 editor.transpose(&Default::default(), window, cx);
4352 assert_eq!(editor.text(cx), "bac");
4353 assert_eq!(editor.selections.ranges(cx), [2..2]);
4354
4355 editor.transpose(&Default::default(), window, cx);
4356 assert_eq!(editor.text(cx), "bca");
4357 assert_eq!(editor.selections.ranges(cx), [3..3]);
4358
4359 editor.transpose(&Default::default(), window, cx);
4360 assert_eq!(editor.text(cx), "bac");
4361 assert_eq!(editor.selections.ranges(cx), [3..3]);
4362
4363 editor
4364 });
4365
4366 _ = cx.add_window(|window, cx| {
4367 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4368 editor.set_style(EditorStyle::default(), window, cx);
4369 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4370 editor.transpose(&Default::default(), window, cx);
4371 assert_eq!(editor.text(cx), "acb\nde");
4372 assert_eq!(editor.selections.ranges(cx), [3..3]);
4373
4374 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4375 editor.transpose(&Default::default(), window, cx);
4376 assert_eq!(editor.text(cx), "acbd\ne");
4377 assert_eq!(editor.selections.ranges(cx), [5..5]);
4378
4379 editor.transpose(&Default::default(), window, cx);
4380 assert_eq!(editor.text(cx), "acbde\n");
4381 assert_eq!(editor.selections.ranges(cx), [6..6]);
4382
4383 editor.transpose(&Default::default(), window, cx);
4384 assert_eq!(editor.text(cx), "acbd\ne");
4385 assert_eq!(editor.selections.ranges(cx), [6..6]);
4386
4387 editor
4388 });
4389
4390 _ = cx.add_window(|window, cx| {
4391 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4392 editor.set_style(EditorStyle::default(), window, cx);
4393 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4394 editor.transpose(&Default::default(), window, cx);
4395 assert_eq!(editor.text(cx), "bacd\ne");
4396 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4397
4398 editor.transpose(&Default::default(), window, cx);
4399 assert_eq!(editor.text(cx), "bcade\n");
4400 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4401
4402 editor.transpose(&Default::default(), window, cx);
4403 assert_eq!(editor.text(cx), "bcda\ne");
4404 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4405
4406 editor.transpose(&Default::default(), window, cx);
4407 assert_eq!(editor.text(cx), "bcade\n");
4408 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4409
4410 editor.transpose(&Default::default(), window, cx);
4411 assert_eq!(editor.text(cx), "bcaed\n");
4412 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4413
4414 editor
4415 });
4416
4417 _ = cx.add_window(|window, cx| {
4418 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4419 editor.set_style(EditorStyle::default(), window, cx);
4420 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4421 editor.transpose(&Default::default(), window, cx);
4422 assert_eq!(editor.text(cx), "🏀🍐✋");
4423 assert_eq!(editor.selections.ranges(cx), [8..8]);
4424
4425 editor.transpose(&Default::default(), window, cx);
4426 assert_eq!(editor.text(cx), "🏀✋🍐");
4427 assert_eq!(editor.selections.ranges(cx), [11..11]);
4428
4429 editor.transpose(&Default::default(), window, cx);
4430 assert_eq!(editor.text(cx), "🏀🍐✋");
4431 assert_eq!(editor.selections.ranges(cx), [11..11]);
4432
4433 editor
4434 });
4435}
4436
4437#[gpui::test]
4438async fn test_rewrap(cx: &mut TestAppContext) {
4439 init_test(cx, |settings| {
4440 settings.languages.extend([
4441 (
4442 "Markdown".into(),
4443 LanguageSettingsContent {
4444 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4445 ..Default::default()
4446 },
4447 ),
4448 (
4449 "Plain Text".into(),
4450 LanguageSettingsContent {
4451 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4452 ..Default::default()
4453 },
4454 ),
4455 ])
4456 });
4457
4458 let mut cx = EditorTestContext::new(cx).await;
4459
4460 let language_with_c_comments = Arc::new(Language::new(
4461 LanguageConfig {
4462 line_comments: vec!["// ".into()],
4463 ..LanguageConfig::default()
4464 },
4465 None,
4466 ));
4467 let language_with_pound_comments = Arc::new(Language::new(
4468 LanguageConfig {
4469 line_comments: vec!["# ".into()],
4470 ..LanguageConfig::default()
4471 },
4472 None,
4473 ));
4474 let markdown_language = Arc::new(Language::new(
4475 LanguageConfig {
4476 name: "Markdown".into(),
4477 ..LanguageConfig::default()
4478 },
4479 None,
4480 ));
4481 let language_with_doc_comments = Arc::new(Language::new(
4482 LanguageConfig {
4483 line_comments: vec!["// ".into(), "/// ".into()],
4484 ..LanguageConfig::default()
4485 },
4486 Some(tree_sitter_rust::LANGUAGE.into()),
4487 ));
4488
4489 let plaintext_language = Arc::new(Language::new(
4490 LanguageConfig {
4491 name: "Plain Text".into(),
4492 ..LanguageConfig::default()
4493 },
4494 None,
4495 ));
4496
4497 assert_rewrap(
4498 indoc! {"
4499 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4500 "},
4501 indoc! {"
4502 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4503 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4504 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4505 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4506 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4507 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4508 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4509 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4510 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4511 // porttitor id. Aliquam id accumsan eros.
4512 "},
4513 language_with_c_comments.clone(),
4514 &mut cx,
4515 );
4516
4517 // Test that rewrapping works inside of a selection
4518 assert_rewrap(
4519 indoc! {"
4520 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4521 "},
4522 indoc! {"
4523 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4524 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4525 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4526 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4527 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4528 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4529 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4530 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4531 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4532 // porttitor id. Aliquam id accumsan eros.ˇ»
4533 "},
4534 language_with_c_comments.clone(),
4535 &mut cx,
4536 );
4537
4538 // Test that cursors that expand to the same region are collapsed.
4539 assert_rewrap(
4540 indoc! {"
4541 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4542 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4543 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4544 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4545 "},
4546 indoc! {"
4547 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4548 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4549 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4550 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4551 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4552 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4553 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4554 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4555 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4556 // porttitor id. Aliquam id accumsan eros.
4557 "},
4558 language_with_c_comments.clone(),
4559 &mut cx,
4560 );
4561
4562 // Test that non-contiguous selections are treated separately.
4563 assert_rewrap(
4564 indoc! {"
4565 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4566 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4567 //
4568 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4569 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4570 "},
4571 indoc! {"
4572 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4573 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4574 // auctor, eu lacinia sapien scelerisque.
4575 //
4576 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4577 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4578 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4579 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4580 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4581 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4582 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4583 "},
4584 language_with_c_comments.clone(),
4585 &mut cx,
4586 );
4587
4588 // Test that different comment prefixes are supported.
4589 assert_rewrap(
4590 indoc! {"
4591 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4592 "},
4593 indoc! {"
4594 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4595 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4596 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4597 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4598 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4599 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4600 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4601 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4602 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4603 # accumsan eros.
4604 "},
4605 language_with_pound_comments.clone(),
4606 &mut cx,
4607 );
4608
4609 // Test that rewrapping is ignored outside of comments in most languages.
4610 assert_rewrap(
4611 indoc! {"
4612 /// Adds two numbers.
4613 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4614 fn add(a: u32, b: u32) -> u32 {
4615 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4616 }
4617 "},
4618 indoc! {"
4619 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4620 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4621 fn add(a: u32, b: u32) -> u32 {
4622 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4623 }
4624 "},
4625 language_with_doc_comments.clone(),
4626 &mut cx,
4627 );
4628
4629 // Test that rewrapping works in Markdown and Plain Text languages.
4630 assert_rewrap(
4631 indoc! {"
4632 # Hello
4633
4634 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4635 "},
4636 indoc! {"
4637 # Hello
4638
4639 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4640 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4641 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4642 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4643 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4644 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4645 Integer sit amet scelerisque nisi.
4646 "},
4647 markdown_language,
4648 &mut cx,
4649 );
4650
4651 assert_rewrap(
4652 indoc! {"
4653 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4654 "},
4655 indoc! {"
4656 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4657 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4658 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4659 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4660 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4661 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4662 Integer sit amet scelerisque nisi.
4663 "},
4664 plaintext_language,
4665 &mut cx,
4666 );
4667
4668 // Test rewrapping unaligned comments in a selection.
4669 assert_rewrap(
4670 indoc! {"
4671 fn foo() {
4672 if true {
4673 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4674 // Praesent semper egestas tellus id dignissim.ˇ»
4675 do_something();
4676 } else {
4677 //
4678 }
4679 }
4680 "},
4681 indoc! {"
4682 fn foo() {
4683 if true {
4684 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4685 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4686 // egestas tellus id dignissim.ˇ»
4687 do_something();
4688 } else {
4689 //
4690 }
4691 }
4692 "},
4693 language_with_doc_comments.clone(),
4694 &mut cx,
4695 );
4696
4697 assert_rewrap(
4698 indoc! {"
4699 fn foo() {
4700 if true {
4701 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4702 // Praesent semper egestas tellus id dignissim.»
4703 do_something();
4704 } else {
4705 //
4706 }
4707
4708 }
4709 "},
4710 indoc! {"
4711 fn foo() {
4712 if true {
4713 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4714 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4715 // egestas tellus id dignissim.»
4716 do_something();
4717 } else {
4718 //
4719 }
4720
4721 }
4722 "},
4723 language_with_doc_comments.clone(),
4724 &mut cx,
4725 );
4726
4727 #[track_caller]
4728 fn assert_rewrap(
4729 unwrapped_text: &str,
4730 wrapped_text: &str,
4731 language: Arc<Language>,
4732 cx: &mut EditorTestContext,
4733 ) {
4734 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4735 cx.set_state(unwrapped_text);
4736 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4737 cx.assert_editor_state(wrapped_text);
4738 }
4739}
4740
4741#[gpui::test]
4742async fn test_hard_wrap(cx: &mut TestAppContext) {
4743 init_test(cx, |_| {});
4744 let mut cx = EditorTestContext::new(cx).await;
4745
4746 cx.update_editor(|editor, _, cx| {
4747 editor.set_hard_wrap(Some(14), cx);
4748 });
4749
4750 cx.set_state(indoc!(
4751 "
4752 one two three ˇ
4753 "
4754 ));
4755 cx.simulate_input("four");
4756 cx.run_until_parked();
4757
4758 cx.assert_editor_state(indoc!(
4759 "
4760 one two three
4761 fourˇ
4762 "
4763 ));
4764}
4765
4766#[gpui::test]
4767async fn test_clipboard(cx: &mut TestAppContext) {
4768 init_test(cx, |_| {});
4769
4770 let mut cx = EditorTestContext::new(cx).await;
4771
4772 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4773 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4774 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4775
4776 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4777 cx.set_state("two ˇfour ˇsix ˇ");
4778 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4779 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4780
4781 // Paste again but with only two cursors. Since the number of cursors doesn't
4782 // match the number of slices in the clipboard, the entire clipboard text
4783 // is pasted at each cursor.
4784 cx.set_state("ˇtwo one✅ four three six five ˇ");
4785 cx.update_editor(|e, window, cx| {
4786 e.handle_input("( ", window, cx);
4787 e.paste(&Paste, window, cx);
4788 e.handle_input(") ", window, cx);
4789 });
4790 cx.assert_editor_state(
4791 &([
4792 "( one✅ ",
4793 "three ",
4794 "five ) ˇtwo one✅ four three six five ( one✅ ",
4795 "three ",
4796 "five ) ˇ",
4797 ]
4798 .join("\n")),
4799 );
4800
4801 // Cut with three selections, one of which is full-line.
4802 cx.set_state(indoc! {"
4803 1«2ˇ»3
4804 4ˇ567
4805 «8ˇ»9"});
4806 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4807 cx.assert_editor_state(indoc! {"
4808 1ˇ3
4809 ˇ9"});
4810
4811 // Paste with three selections, noticing how the copied selection that was full-line
4812 // gets inserted before the second cursor.
4813 cx.set_state(indoc! {"
4814 1ˇ3
4815 9ˇ
4816 «oˇ»ne"});
4817 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4818 cx.assert_editor_state(indoc! {"
4819 12ˇ3
4820 4567
4821 9ˇ
4822 8ˇne"});
4823
4824 // Copy with a single cursor only, which writes the whole line into the clipboard.
4825 cx.set_state(indoc! {"
4826 The quick brown
4827 fox juˇmps over
4828 the lazy dog"});
4829 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4830 assert_eq!(
4831 cx.read_from_clipboard()
4832 .and_then(|item| item.text().as_deref().map(str::to_string)),
4833 Some("fox jumps over\n".to_string())
4834 );
4835
4836 // Paste with three selections, noticing how the copied full-line selection is inserted
4837 // before the empty selections but replaces the selection that is non-empty.
4838 cx.set_state(indoc! {"
4839 Tˇhe quick brown
4840 «foˇ»x jumps over
4841 tˇhe lazy dog"});
4842 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4843 cx.assert_editor_state(indoc! {"
4844 fox jumps over
4845 Tˇhe quick brown
4846 fox jumps over
4847 ˇx jumps over
4848 fox jumps over
4849 tˇhe lazy dog"});
4850}
4851
4852#[gpui::test]
4853async fn test_paste_multiline(cx: &mut TestAppContext) {
4854 init_test(cx, |_| {});
4855
4856 let mut cx = EditorTestContext::new(cx).await;
4857 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4858
4859 // Cut an indented block, without the leading whitespace.
4860 cx.set_state(indoc! {"
4861 const a: B = (
4862 c(),
4863 «d(
4864 e,
4865 f
4866 )ˇ»
4867 );
4868 "});
4869 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4870 cx.assert_editor_state(indoc! {"
4871 const a: B = (
4872 c(),
4873 ˇ
4874 );
4875 "});
4876
4877 // Paste it at the same position.
4878 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4879 cx.assert_editor_state(indoc! {"
4880 const a: B = (
4881 c(),
4882 d(
4883 e,
4884 f
4885 )ˇ
4886 );
4887 "});
4888
4889 // Paste it at a line with a lower indent level.
4890 cx.set_state(indoc! {"
4891 ˇ
4892 const a: B = (
4893 c(),
4894 );
4895 "});
4896 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4897 cx.assert_editor_state(indoc! {"
4898 d(
4899 e,
4900 f
4901 )ˇ
4902 const a: B = (
4903 c(),
4904 );
4905 "});
4906
4907 // Cut an indented block, with the leading whitespace.
4908 cx.set_state(indoc! {"
4909 const a: B = (
4910 c(),
4911 « d(
4912 e,
4913 f
4914 )
4915 ˇ»);
4916 "});
4917 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4918 cx.assert_editor_state(indoc! {"
4919 const a: B = (
4920 c(),
4921 ˇ);
4922 "});
4923
4924 // Paste it at the same position.
4925 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4926 cx.assert_editor_state(indoc! {"
4927 const a: B = (
4928 c(),
4929 d(
4930 e,
4931 f
4932 )
4933 ˇ);
4934 "});
4935
4936 // Paste it at a line with a higher indent level.
4937 cx.set_state(indoc! {"
4938 const a: B = (
4939 c(),
4940 d(
4941 e,
4942 fˇ
4943 )
4944 );
4945 "});
4946 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4947 cx.assert_editor_state(indoc! {"
4948 const a: B = (
4949 c(),
4950 d(
4951 e,
4952 f d(
4953 e,
4954 f
4955 )
4956 ˇ
4957 )
4958 );
4959 "});
4960
4961 // Copy an indented block, starting mid-line
4962 cx.set_state(indoc! {"
4963 const a: B = (
4964 c(),
4965 somethin«g(
4966 e,
4967 f
4968 )ˇ»
4969 );
4970 "});
4971 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4972
4973 // Paste it on a line with a lower indent level
4974 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4975 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4976 cx.assert_editor_state(indoc! {"
4977 const a: B = (
4978 c(),
4979 something(
4980 e,
4981 f
4982 )
4983 );
4984 g(
4985 e,
4986 f
4987 )ˇ"});
4988}
4989
4990#[gpui::test]
4991async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4992 init_test(cx, |_| {});
4993
4994 cx.write_to_clipboard(ClipboardItem::new_string(
4995 " d(\n e\n );\n".into(),
4996 ));
4997
4998 let mut cx = EditorTestContext::new(cx).await;
4999 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5000
5001 cx.set_state(indoc! {"
5002 fn a() {
5003 b();
5004 if c() {
5005 ˇ
5006 }
5007 }
5008 "});
5009
5010 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5011 cx.assert_editor_state(indoc! {"
5012 fn a() {
5013 b();
5014 if c() {
5015 d(
5016 e
5017 );
5018 ˇ
5019 }
5020 }
5021 "});
5022
5023 cx.set_state(indoc! {"
5024 fn a() {
5025 b();
5026 ˇ
5027 }
5028 "});
5029
5030 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5031 cx.assert_editor_state(indoc! {"
5032 fn a() {
5033 b();
5034 d(
5035 e
5036 );
5037 ˇ
5038 }
5039 "});
5040}
5041
5042#[gpui::test]
5043fn test_select_all(cx: &mut TestAppContext) {
5044 init_test(cx, |_| {});
5045
5046 let editor = cx.add_window(|window, cx| {
5047 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5048 build_editor(buffer, window, cx)
5049 });
5050 _ = editor.update(cx, |editor, window, cx| {
5051 editor.select_all(&SelectAll, window, cx);
5052 assert_eq!(
5053 editor.selections.display_ranges(cx),
5054 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5055 );
5056 });
5057}
5058
5059#[gpui::test]
5060fn test_select_line(cx: &mut TestAppContext) {
5061 init_test(cx, |_| {});
5062
5063 let editor = cx.add_window(|window, cx| {
5064 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5065 build_editor(buffer, window, cx)
5066 });
5067 _ = editor.update(cx, |editor, window, cx| {
5068 editor.change_selections(None, window, cx, |s| {
5069 s.select_display_ranges([
5070 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5071 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5072 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5073 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5074 ])
5075 });
5076 editor.select_line(&SelectLine, window, cx);
5077 assert_eq!(
5078 editor.selections.display_ranges(cx),
5079 vec![
5080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5081 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5082 ]
5083 );
5084 });
5085
5086 _ = editor.update(cx, |editor, window, cx| {
5087 editor.select_line(&SelectLine, window, cx);
5088 assert_eq!(
5089 editor.selections.display_ranges(cx),
5090 vec![
5091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5092 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5093 ]
5094 );
5095 });
5096
5097 _ = editor.update(cx, |editor, window, cx| {
5098 editor.select_line(&SelectLine, window, cx);
5099 assert_eq!(
5100 editor.selections.display_ranges(cx),
5101 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5102 );
5103 });
5104}
5105
5106#[gpui::test]
5107async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5108 init_test(cx, |_| {});
5109 let mut cx = EditorTestContext::new(cx).await;
5110
5111 #[track_caller]
5112 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5113 cx.set_state(initial_state);
5114 cx.update_editor(|e, window, cx| {
5115 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5116 });
5117 cx.assert_editor_state(expected_state);
5118 }
5119
5120 // Selection starts and ends at the middle of lines, left-to-right
5121 test(
5122 &mut cx,
5123 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5124 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5125 );
5126 // Same thing, right-to-left
5127 test(
5128 &mut cx,
5129 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5130 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5131 );
5132
5133 // Whole buffer, left-to-right, last line *doesn't* end with newline
5134 test(
5135 &mut cx,
5136 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5137 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5138 );
5139 // Same thing, right-to-left
5140 test(
5141 &mut cx,
5142 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5143 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5144 );
5145
5146 // Whole buffer, left-to-right, last line ends with newline
5147 test(
5148 &mut cx,
5149 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5150 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5151 );
5152 // Same thing, right-to-left
5153 test(
5154 &mut cx,
5155 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5156 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5157 );
5158
5159 // Starts at the end of a line, ends at the start of another
5160 test(
5161 &mut cx,
5162 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5163 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let editor = cx.add_window(|window, cx| {
5172 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5173 build_editor(buffer, window, cx)
5174 });
5175
5176 // setup
5177 _ = editor.update(cx, |editor, window, cx| {
5178 editor.fold_creases(
5179 vec![
5180 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5181 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5182 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5183 ],
5184 true,
5185 window,
5186 cx,
5187 );
5188 assert_eq!(
5189 editor.display_text(cx),
5190 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5191 );
5192 });
5193
5194 _ = editor.update(cx, |editor, window, cx| {
5195 editor.change_selections(None, window, cx, |s| {
5196 s.select_display_ranges([
5197 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5198 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5199 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5200 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5201 ])
5202 });
5203 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5204 assert_eq!(
5205 editor.display_text(cx),
5206 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5207 );
5208 });
5209 EditorTestContext::for_editor(editor, cx)
5210 .await
5211 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5212
5213 _ = editor.update(cx, |editor, window, cx| {
5214 editor.change_selections(None, window, cx, |s| {
5215 s.select_display_ranges([
5216 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5217 ])
5218 });
5219 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5220 assert_eq!(
5221 editor.display_text(cx),
5222 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5223 );
5224 assert_eq!(
5225 editor.selections.display_ranges(cx),
5226 [
5227 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5228 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5229 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5230 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5231 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5232 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5233 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5234 ]
5235 );
5236 });
5237 EditorTestContext::for_editor(editor, cx)
5238 .await
5239 .assert_editor_state(
5240 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249
5250 cx.set_state(indoc!(
5251 r#"abc
5252 defˇghi
5253
5254 jk
5255 nlmo
5256 "#
5257 ));
5258
5259 cx.update_editor(|editor, window, cx| {
5260 editor.add_selection_above(&Default::default(), window, cx);
5261 });
5262
5263 cx.assert_editor_state(indoc!(
5264 r#"abcˇ
5265 defˇghi
5266
5267 jk
5268 nlmo
5269 "#
5270 ));
5271
5272 cx.update_editor(|editor, window, cx| {
5273 editor.add_selection_above(&Default::default(), window, cx);
5274 });
5275
5276 cx.assert_editor_state(indoc!(
5277 r#"abcˇ
5278 defˇghi
5279
5280 jk
5281 nlmo
5282 "#
5283 ));
5284
5285 cx.update_editor(|editor, window, cx| {
5286 editor.add_selection_below(&Default::default(), window, cx);
5287 });
5288
5289 cx.assert_editor_state(indoc!(
5290 r#"abc
5291 defˇghi
5292
5293 jk
5294 nlmo
5295 "#
5296 ));
5297
5298 cx.update_editor(|editor, window, cx| {
5299 editor.undo_selection(&Default::default(), window, cx);
5300 });
5301
5302 cx.assert_editor_state(indoc!(
5303 r#"abcˇ
5304 defˇghi
5305
5306 jk
5307 nlmo
5308 "#
5309 ));
5310
5311 cx.update_editor(|editor, window, cx| {
5312 editor.redo_selection(&Default::default(), window, cx);
5313 });
5314
5315 cx.assert_editor_state(indoc!(
5316 r#"abc
5317 defˇghi
5318
5319 jk
5320 nlmo
5321 "#
5322 ));
5323
5324 cx.update_editor(|editor, window, cx| {
5325 editor.add_selection_below(&Default::default(), window, cx);
5326 });
5327
5328 cx.assert_editor_state(indoc!(
5329 r#"abc
5330 defˇghi
5331
5332 jk
5333 nlmˇo
5334 "#
5335 ));
5336
5337 cx.update_editor(|editor, window, cx| {
5338 editor.add_selection_below(&Default::default(), window, cx);
5339 });
5340
5341 cx.assert_editor_state(indoc!(
5342 r#"abc
5343 defˇghi
5344
5345 jk
5346 nlmˇo
5347 "#
5348 ));
5349
5350 // change selections
5351 cx.set_state(indoc!(
5352 r#"abc
5353 def«ˇg»hi
5354
5355 jk
5356 nlmo
5357 "#
5358 ));
5359
5360 cx.update_editor(|editor, window, cx| {
5361 editor.add_selection_below(&Default::default(), window, cx);
5362 });
5363
5364 cx.assert_editor_state(indoc!(
5365 r#"abc
5366 def«ˇg»hi
5367
5368 jk
5369 nlm«ˇo»
5370 "#
5371 ));
5372
5373 cx.update_editor(|editor, window, cx| {
5374 editor.add_selection_below(&Default::default(), window, cx);
5375 });
5376
5377 cx.assert_editor_state(indoc!(
5378 r#"abc
5379 def«ˇg»hi
5380
5381 jk
5382 nlm«ˇo»
5383 "#
5384 ));
5385
5386 cx.update_editor(|editor, window, cx| {
5387 editor.add_selection_above(&Default::default(), window, cx);
5388 });
5389
5390 cx.assert_editor_state(indoc!(
5391 r#"abc
5392 def«ˇg»hi
5393
5394 jk
5395 nlmo
5396 "#
5397 ));
5398
5399 cx.update_editor(|editor, window, cx| {
5400 editor.add_selection_above(&Default::default(), window, cx);
5401 });
5402
5403 cx.assert_editor_state(indoc!(
5404 r#"abc
5405 def«ˇg»hi
5406
5407 jk
5408 nlmo
5409 "#
5410 ));
5411
5412 // Change selections again
5413 cx.set_state(indoc!(
5414 r#"a«bc
5415 defgˇ»hi
5416
5417 jk
5418 nlmo
5419 "#
5420 ));
5421
5422 cx.update_editor(|editor, window, cx| {
5423 editor.add_selection_below(&Default::default(), window, cx);
5424 });
5425
5426 cx.assert_editor_state(indoc!(
5427 r#"a«bcˇ»
5428 d«efgˇ»hi
5429
5430 j«kˇ»
5431 nlmo
5432 "#
5433 ));
5434
5435 cx.update_editor(|editor, window, cx| {
5436 editor.add_selection_below(&Default::default(), window, cx);
5437 });
5438 cx.assert_editor_state(indoc!(
5439 r#"a«bcˇ»
5440 d«efgˇ»hi
5441
5442 j«kˇ»
5443 n«lmoˇ»
5444 "#
5445 ));
5446 cx.update_editor(|editor, window, cx| {
5447 editor.add_selection_above(&Default::default(), window, cx);
5448 });
5449
5450 cx.assert_editor_state(indoc!(
5451 r#"a«bcˇ»
5452 d«efgˇ»hi
5453
5454 j«kˇ»
5455 nlmo
5456 "#
5457 ));
5458
5459 // Change selections again
5460 cx.set_state(indoc!(
5461 r#"abc
5462 d«ˇefghi
5463
5464 jk
5465 nlm»o
5466 "#
5467 ));
5468
5469 cx.update_editor(|editor, window, cx| {
5470 editor.add_selection_above(&Default::default(), window, cx);
5471 });
5472
5473 cx.assert_editor_state(indoc!(
5474 r#"a«ˇbc»
5475 d«ˇef»ghi
5476
5477 j«ˇk»
5478 n«ˇlm»o
5479 "#
5480 ));
5481
5482 cx.update_editor(|editor, window, cx| {
5483 editor.add_selection_below(&Default::default(), window, cx);
5484 });
5485
5486 cx.assert_editor_state(indoc!(
5487 r#"abc
5488 d«ˇef»ghi
5489
5490 j«ˇk»
5491 n«ˇlm»o
5492 "#
5493 ));
5494}
5495
5496#[gpui::test]
5497async fn test_select_next(cx: &mut TestAppContext) {
5498 init_test(cx, |_| {});
5499
5500 let mut cx = EditorTestContext::new(cx).await;
5501 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5502
5503 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5504 .unwrap();
5505 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5506
5507 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5508 .unwrap();
5509 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5510
5511 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5512 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5513
5514 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5515 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5516
5517 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5518 .unwrap();
5519 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5520
5521 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5522 .unwrap();
5523 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5524}
5525
5526#[gpui::test]
5527async fn test_select_all_matches(cx: &mut TestAppContext) {
5528 init_test(cx, |_| {});
5529
5530 let mut cx = EditorTestContext::new(cx).await;
5531
5532 // Test caret-only selections
5533 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5534 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5535 .unwrap();
5536 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5537
5538 // Test left-to-right selections
5539 cx.set_state("abc\n«abcˇ»\nabc");
5540 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5541 .unwrap();
5542 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5543
5544 // Test right-to-left selections
5545 cx.set_state("abc\n«ˇabc»\nabc");
5546 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5547 .unwrap();
5548 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5549
5550 // Test selecting whitespace with caret selection
5551 cx.set_state("abc\nˇ abc\nabc");
5552 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5553 .unwrap();
5554 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5555
5556 // Test selecting whitespace with left-to-right selection
5557 cx.set_state("abc\n«ˇ »abc\nabc");
5558 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5559 .unwrap();
5560 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5561
5562 // Test no matches with right-to-left selection
5563 cx.set_state("abc\n« ˇ»abc\nabc");
5564 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5565 .unwrap();
5566 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5567}
5568
5569#[gpui::test]
5570async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5571 init_test(cx, |_| {});
5572
5573 let mut cx = EditorTestContext::new(cx).await;
5574 cx.set_state(
5575 r#"let foo = 2;
5576lˇet foo = 2;
5577let fooˇ = 2;
5578let foo = 2;
5579let foo = ˇ2;"#,
5580 );
5581
5582 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5583 .unwrap();
5584 cx.assert_editor_state(
5585 r#"let foo = 2;
5586«letˇ» foo = 2;
5587let «fooˇ» = 2;
5588let foo = 2;
5589let foo = «2ˇ»;"#,
5590 );
5591
5592 // noop for multiple selections with different contents
5593 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5594 .unwrap();
5595 cx.assert_editor_state(
5596 r#"let foo = 2;
5597«letˇ» foo = 2;
5598let «fooˇ» = 2;
5599let foo = 2;
5600let foo = «2ˇ»;"#,
5601 );
5602}
5603
5604#[gpui::test]
5605async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5606 init_test(cx, |_| {});
5607
5608 let mut cx =
5609 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5610
5611 cx.assert_editor_state(indoc! {"
5612 ˇbbb
5613 ccc
5614
5615 bbb
5616 ccc
5617 "});
5618 cx.dispatch_action(SelectPrevious::default());
5619 cx.assert_editor_state(indoc! {"
5620 «bbbˇ»
5621 ccc
5622
5623 bbb
5624 ccc
5625 "});
5626 cx.dispatch_action(SelectPrevious::default());
5627 cx.assert_editor_state(indoc! {"
5628 «bbbˇ»
5629 ccc
5630
5631 «bbbˇ»
5632 ccc
5633 "});
5634}
5635
5636#[gpui::test]
5637async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5638 init_test(cx, |_| {});
5639
5640 let mut cx = EditorTestContext::new(cx).await;
5641 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5642
5643 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5644 .unwrap();
5645 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5646
5647 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5648 .unwrap();
5649 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5650
5651 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5652 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5653
5654 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5655 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5656
5657 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5658 .unwrap();
5659 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5660
5661 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5662 .unwrap();
5663 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5664
5665 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5666 .unwrap();
5667 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5668}
5669
5670#[gpui::test]
5671async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5672 init_test(cx, |_| {});
5673
5674 let mut cx = EditorTestContext::new(cx).await;
5675 cx.set_state("aˇ");
5676
5677 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5678 .unwrap();
5679 cx.assert_editor_state("«aˇ»");
5680 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5681 .unwrap();
5682 cx.assert_editor_state("«aˇ»");
5683}
5684
5685#[gpui::test]
5686async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5687 init_test(cx, |_| {});
5688
5689 let mut cx = EditorTestContext::new(cx).await;
5690 cx.set_state(
5691 r#"let foo = 2;
5692lˇet foo = 2;
5693let fooˇ = 2;
5694let foo = 2;
5695let foo = ˇ2;"#,
5696 );
5697
5698 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5699 .unwrap();
5700 cx.assert_editor_state(
5701 r#"let foo = 2;
5702«letˇ» foo = 2;
5703let «fooˇ» = 2;
5704let foo = 2;
5705let foo = «2ˇ»;"#,
5706 );
5707
5708 // noop for multiple selections with different contents
5709 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5710 .unwrap();
5711 cx.assert_editor_state(
5712 r#"let foo = 2;
5713«letˇ» foo = 2;
5714let «fooˇ» = 2;
5715let foo = 2;
5716let foo = «2ˇ»;"#,
5717 );
5718}
5719
5720#[gpui::test]
5721async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5722 init_test(cx, |_| {});
5723
5724 let mut cx = EditorTestContext::new(cx).await;
5725 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5726
5727 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5728 .unwrap();
5729 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5730
5731 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5732 .unwrap();
5733 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5734
5735 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5736 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5737
5738 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5739 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5740
5741 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5742 .unwrap();
5743 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5744
5745 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5746 .unwrap();
5747 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5748}
5749
5750#[gpui::test]
5751async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5752 init_test(cx, |_| {});
5753
5754 let language = Arc::new(Language::new(
5755 LanguageConfig::default(),
5756 Some(tree_sitter_rust::LANGUAGE.into()),
5757 ));
5758
5759 let text = r#"
5760 use mod1::mod2::{mod3, mod4};
5761
5762 fn fn_1(param1: bool, param2: &str) {
5763 let var1 = "text";
5764 }
5765 "#
5766 .unindent();
5767
5768 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5769 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5770 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5771
5772 editor
5773 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5774 .await;
5775
5776 editor.update_in(cx, |editor, window, cx| {
5777 editor.change_selections(None, window, cx, |s| {
5778 s.select_display_ranges([
5779 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5780 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5781 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5782 ]);
5783 });
5784 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5785 });
5786 editor.update(cx, |editor, cx| {
5787 assert_text_with_selections(
5788 editor,
5789 indoc! {r#"
5790 use mod1::mod2::{mod3, «mod4ˇ»};
5791
5792 fn fn_1«ˇ(param1: bool, param2: &str)» {
5793 let var1 = "«textˇ»";
5794 }
5795 "#},
5796 cx,
5797 );
5798 });
5799
5800 editor.update_in(cx, |editor, window, cx| {
5801 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5802 });
5803 editor.update(cx, |editor, cx| {
5804 assert_text_with_selections(
5805 editor,
5806 indoc! {r#"
5807 use mod1::mod2::«{mod3, mod4}ˇ»;
5808
5809 «ˇfn fn_1(param1: bool, param2: &str) {
5810 let var1 = "text";
5811 }»
5812 "#},
5813 cx,
5814 );
5815 });
5816
5817 editor.update_in(cx, |editor, window, cx| {
5818 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5819 });
5820 assert_eq!(
5821 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5822 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5823 );
5824
5825 // Trying to expand the selected syntax node one more time has no effect.
5826 editor.update_in(cx, |editor, window, cx| {
5827 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5828 });
5829 assert_eq!(
5830 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5831 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5832 );
5833
5834 editor.update_in(cx, |editor, window, cx| {
5835 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5836 });
5837 editor.update(cx, |editor, cx| {
5838 assert_text_with_selections(
5839 editor,
5840 indoc! {r#"
5841 use mod1::mod2::«{mod3, mod4}ˇ»;
5842
5843 «ˇfn fn_1(param1: bool, param2: &str) {
5844 let var1 = "text";
5845 }»
5846 "#},
5847 cx,
5848 );
5849 });
5850
5851 editor.update_in(cx, |editor, window, cx| {
5852 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5853 });
5854 editor.update(cx, |editor, cx| {
5855 assert_text_with_selections(
5856 editor,
5857 indoc! {r#"
5858 use mod1::mod2::{mod3, «mod4ˇ»};
5859
5860 fn fn_1«ˇ(param1: bool, param2: &str)» {
5861 let var1 = "«textˇ»";
5862 }
5863 "#},
5864 cx,
5865 );
5866 });
5867
5868 editor.update_in(cx, |editor, window, cx| {
5869 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5870 });
5871 editor.update(cx, |editor, cx| {
5872 assert_text_with_selections(
5873 editor,
5874 indoc! {r#"
5875 use mod1::mod2::{mod3, mo«ˇ»d4};
5876
5877 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5878 let var1 = "te«ˇ»xt";
5879 }
5880 "#},
5881 cx,
5882 );
5883 });
5884
5885 // Trying to shrink the selected syntax node one more time has no effect.
5886 editor.update_in(cx, |editor, window, cx| {
5887 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5888 });
5889 editor.update_in(cx, |editor, _, cx| {
5890 assert_text_with_selections(
5891 editor,
5892 indoc! {r#"
5893 use mod1::mod2::{mod3, mo«ˇ»d4};
5894
5895 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5896 let var1 = "te«ˇ»xt";
5897 }
5898 "#},
5899 cx,
5900 );
5901 });
5902
5903 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5904 // a fold.
5905 editor.update_in(cx, |editor, window, cx| {
5906 editor.fold_creases(
5907 vec![
5908 Crease::simple(
5909 Point::new(0, 21)..Point::new(0, 24),
5910 FoldPlaceholder::test(),
5911 ),
5912 Crease::simple(
5913 Point::new(3, 20)..Point::new(3, 22),
5914 FoldPlaceholder::test(),
5915 ),
5916 ],
5917 true,
5918 window,
5919 cx,
5920 );
5921 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5922 });
5923 editor.update(cx, |editor, cx| {
5924 assert_text_with_selections(
5925 editor,
5926 indoc! {r#"
5927 use mod1::mod2::«{mod3, mod4}ˇ»;
5928
5929 fn fn_1«ˇ(param1: bool, param2: &str)» {
5930 «let var1 = "text";ˇ»
5931 }
5932 "#},
5933 cx,
5934 );
5935 });
5936}
5937
5938#[gpui::test]
5939async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5940 init_test(cx, |_| {});
5941
5942 let base_text = r#"
5943 impl A {
5944 // this is an uncommitted comment
5945
5946 fn b() {
5947 c();
5948 }
5949
5950 // this is another uncommitted comment
5951
5952 fn d() {
5953 // e
5954 // f
5955 }
5956 }
5957
5958 fn g() {
5959 // h
5960 }
5961 "#
5962 .unindent();
5963
5964 let text = r#"
5965 ˇimpl A {
5966
5967 fn b() {
5968 c();
5969 }
5970
5971 fn d() {
5972 // e
5973 // f
5974 }
5975 }
5976
5977 fn g() {
5978 // h
5979 }
5980 "#
5981 .unindent();
5982
5983 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5984 cx.set_state(&text);
5985 cx.set_head_text(&base_text);
5986 cx.update_editor(|editor, window, cx| {
5987 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5988 });
5989
5990 cx.assert_state_with_diff(
5991 "
5992 ˇimpl A {
5993 - // this is an uncommitted comment
5994
5995 fn b() {
5996 c();
5997 }
5998
5999 - // this is another uncommitted comment
6000 -
6001 fn d() {
6002 // e
6003 // f
6004 }
6005 }
6006
6007 fn g() {
6008 // h
6009 }
6010 "
6011 .unindent(),
6012 );
6013
6014 let expected_display_text = "
6015 impl A {
6016 // this is an uncommitted comment
6017
6018 fn b() {
6019 ⋯
6020 }
6021
6022 // this is another uncommitted comment
6023
6024 fn d() {
6025 ⋯
6026 }
6027 }
6028
6029 fn g() {
6030 ⋯
6031 }
6032 "
6033 .unindent();
6034
6035 cx.update_editor(|editor, window, cx| {
6036 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6037 assert_eq!(editor.display_text(cx), expected_display_text);
6038 });
6039}
6040
6041#[gpui::test]
6042async fn test_autoindent(cx: &mut TestAppContext) {
6043 init_test(cx, |_| {});
6044
6045 let language = Arc::new(
6046 Language::new(
6047 LanguageConfig {
6048 brackets: BracketPairConfig {
6049 pairs: vec![
6050 BracketPair {
6051 start: "{".to_string(),
6052 end: "}".to_string(),
6053 close: false,
6054 surround: false,
6055 newline: true,
6056 },
6057 BracketPair {
6058 start: "(".to_string(),
6059 end: ")".to_string(),
6060 close: false,
6061 surround: false,
6062 newline: true,
6063 },
6064 ],
6065 ..Default::default()
6066 },
6067 ..Default::default()
6068 },
6069 Some(tree_sitter_rust::LANGUAGE.into()),
6070 )
6071 .with_indents_query(
6072 r#"
6073 (_ "(" ")" @end) @indent
6074 (_ "{" "}" @end) @indent
6075 "#,
6076 )
6077 .unwrap(),
6078 );
6079
6080 let text = "fn a() {}";
6081
6082 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6083 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6084 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6085 editor
6086 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6087 .await;
6088
6089 editor.update_in(cx, |editor, window, cx| {
6090 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6091 editor.newline(&Newline, window, cx);
6092 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6093 assert_eq!(
6094 editor.selections.ranges(cx),
6095 &[
6096 Point::new(1, 4)..Point::new(1, 4),
6097 Point::new(3, 4)..Point::new(3, 4),
6098 Point::new(5, 0)..Point::new(5, 0)
6099 ]
6100 );
6101 });
6102}
6103
6104#[gpui::test]
6105async fn test_autoindent_selections(cx: &mut TestAppContext) {
6106 init_test(cx, |_| {});
6107
6108 {
6109 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6110 cx.set_state(indoc! {"
6111 impl A {
6112
6113 fn b() {}
6114
6115 «fn c() {
6116
6117 }ˇ»
6118 }
6119 "});
6120
6121 cx.update_editor(|editor, window, cx| {
6122 editor.autoindent(&Default::default(), window, cx);
6123 });
6124
6125 cx.assert_editor_state(indoc! {"
6126 impl A {
6127
6128 fn b() {}
6129
6130 «fn c() {
6131
6132 }ˇ»
6133 }
6134 "});
6135 }
6136
6137 {
6138 let mut cx = EditorTestContext::new_multibuffer(
6139 cx,
6140 [indoc! { "
6141 impl A {
6142 «
6143 // a
6144 fn b(){}
6145 »
6146 «
6147 }
6148 fn c(){}
6149 »
6150 "}],
6151 );
6152
6153 let buffer = cx.update_editor(|editor, _, cx| {
6154 let buffer = editor.buffer().update(cx, |buffer, _| {
6155 buffer.all_buffers().iter().next().unwrap().clone()
6156 });
6157 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6158 buffer
6159 });
6160
6161 cx.run_until_parked();
6162 cx.update_editor(|editor, window, cx| {
6163 editor.select_all(&Default::default(), window, cx);
6164 editor.autoindent(&Default::default(), window, cx)
6165 });
6166 cx.run_until_parked();
6167
6168 cx.update(|_, cx| {
6169 pretty_assertions::assert_eq!(
6170 buffer.read(cx).text(),
6171 indoc! { "
6172 impl A {
6173
6174 // a
6175 fn b(){}
6176
6177
6178 }
6179 fn c(){}
6180
6181 " }
6182 )
6183 });
6184 }
6185}
6186
6187#[gpui::test]
6188async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6189 init_test(cx, |_| {});
6190
6191 let mut cx = EditorTestContext::new(cx).await;
6192
6193 let language = Arc::new(Language::new(
6194 LanguageConfig {
6195 brackets: BracketPairConfig {
6196 pairs: vec![
6197 BracketPair {
6198 start: "{".to_string(),
6199 end: "}".to_string(),
6200 close: true,
6201 surround: true,
6202 newline: true,
6203 },
6204 BracketPair {
6205 start: "(".to_string(),
6206 end: ")".to_string(),
6207 close: true,
6208 surround: true,
6209 newline: true,
6210 },
6211 BracketPair {
6212 start: "/*".to_string(),
6213 end: " */".to_string(),
6214 close: true,
6215 surround: true,
6216 newline: true,
6217 },
6218 BracketPair {
6219 start: "[".to_string(),
6220 end: "]".to_string(),
6221 close: false,
6222 surround: false,
6223 newline: true,
6224 },
6225 BracketPair {
6226 start: "\"".to_string(),
6227 end: "\"".to_string(),
6228 close: true,
6229 surround: true,
6230 newline: false,
6231 },
6232 BracketPair {
6233 start: "<".to_string(),
6234 end: ">".to_string(),
6235 close: false,
6236 surround: true,
6237 newline: true,
6238 },
6239 ],
6240 ..Default::default()
6241 },
6242 autoclose_before: "})]".to_string(),
6243 ..Default::default()
6244 },
6245 Some(tree_sitter_rust::LANGUAGE.into()),
6246 ));
6247
6248 cx.language_registry().add(language.clone());
6249 cx.update_buffer(|buffer, cx| {
6250 buffer.set_language(Some(language), cx);
6251 });
6252
6253 cx.set_state(
6254 &r#"
6255 🏀ˇ
6256 εˇ
6257 ❤️ˇ
6258 "#
6259 .unindent(),
6260 );
6261
6262 // autoclose multiple nested brackets at multiple cursors
6263 cx.update_editor(|editor, window, cx| {
6264 editor.handle_input("{", window, cx);
6265 editor.handle_input("{", window, cx);
6266 editor.handle_input("{", window, cx);
6267 });
6268 cx.assert_editor_state(
6269 &"
6270 🏀{{{ˇ}}}
6271 ε{{{ˇ}}}
6272 ❤️{{{ˇ}}}
6273 "
6274 .unindent(),
6275 );
6276
6277 // insert a different closing bracket
6278 cx.update_editor(|editor, window, cx| {
6279 editor.handle_input(")", window, cx);
6280 });
6281 cx.assert_editor_state(
6282 &"
6283 🏀{{{)ˇ}}}
6284 ε{{{)ˇ}}}
6285 ❤️{{{)ˇ}}}
6286 "
6287 .unindent(),
6288 );
6289
6290 // skip over the auto-closed brackets when typing a closing bracket
6291 cx.update_editor(|editor, window, cx| {
6292 editor.move_right(&MoveRight, window, cx);
6293 editor.handle_input("}", window, cx);
6294 editor.handle_input("}", window, cx);
6295 editor.handle_input("}", window, cx);
6296 });
6297 cx.assert_editor_state(
6298 &"
6299 🏀{{{)}}}}ˇ
6300 ε{{{)}}}}ˇ
6301 ❤️{{{)}}}}ˇ
6302 "
6303 .unindent(),
6304 );
6305
6306 // autoclose multi-character pairs
6307 cx.set_state(
6308 &"
6309 ˇ
6310 ˇ
6311 "
6312 .unindent(),
6313 );
6314 cx.update_editor(|editor, window, cx| {
6315 editor.handle_input("/", window, cx);
6316 editor.handle_input("*", window, cx);
6317 });
6318 cx.assert_editor_state(
6319 &"
6320 /*ˇ */
6321 /*ˇ */
6322 "
6323 .unindent(),
6324 );
6325
6326 // one cursor autocloses a multi-character pair, one cursor
6327 // does not autoclose.
6328 cx.set_state(
6329 &"
6330 /ˇ
6331 ˇ
6332 "
6333 .unindent(),
6334 );
6335 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6336 cx.assert_editor_state(
6337 &"
6338 /*ˇ */
6339 *ˇ
6340 "
6341 .unindent(),
6342 );
6343
6344 // Don't autoclose if the next character isn't whitespace and isn't
6345 // listed in the language's "autoclose_before" section.
6346 cx.set_state("ˇa b");
6347 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6348 cx.assert_editor_state("{ˇa b");
6349
6350 // Don't autoclose if `close` is false for the bracket pair
6351 cx.set_state("ˇ");
6352 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6353 cx.assert_editor_state("[ˇ");
6354
6355 // Surround with brackets if text is selected
6356 cx.set_state("«aˇ» b");
6357 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6358 cx.assert_editor_state("{«aˇ»} b");
6359
6360 // Autoclose when not immediately after a word character
6361 cx.set_state("a ˇ");
6362 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6363 cx.assert_editor_state("a \"ˇ\"");
6364
6365 // Autoclose pair where the start and end characters are the same
6366 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6367 cx.assert_editor_state("a \"\"ˇ");
6368
6369 // Don't autoclose when immediately after a word character
6370 cx.set_state("aˇ");
6371 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6372 cx.assert_editor_state("a\"ˇ");
6373
6374 // Do autoclose when after a non-word character
6375 cx.set_state("{ˇ");
6376 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6377 cx.assert_editor_state("{\"ˇ\"");
6378
6379 // Non identical pairs autoclose regardless of preceding character
6380 cx.set_state("aˇ");
6381 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6382 cx.assert_editor_state("a{ˇ}");
6383
6384 // Don't autoclose pair if autoclose is disabled
6385 cx.set_state("ˇ");
6386 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6387 cx.assert_editor_state("<ˇ");
6388
6389 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6390 cx.set_state("«aˇ» b");
6391 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6392 cx.assert_editor_state("<«aˇ»> b");
6393}
6394
6395#[gpui::test]
6396async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6397 init_test(cx, |settings| {
6398 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6399 });
6400
6401 let mut cx = EditorTestContext::new(cx).await;
6402
6403 let language = Arc::new(Language::new(
6404 LanguageConfig {
6405 brackets: BracketPairConfig {
6406 pairs: vec![
6407 BracketPair {
6408 start: "{".to_string(),
6409 end: "}".to_string(),
6410 close: true,
6411 surround: true,
6412 newline: true,
6413 },
6414 BracketPair {
6415 start: "(".to_string(),
6416 end: ")".to_string(),
6417 close: true,
6418 surround: true,
6419 newline: true,
6420 },
6421 BracketPair {
6422 start: "[".to_string(),
6423 end: "]".to_string(),
6424 close: false,
6425 surround: false,
6426 newline: true,
6427 },
6428 ],
6429 ..Default::default()
6430 },
6431 autoclose_before: "})]".to_string(),
6432 ..Default::default()
6433 },
6434 Some(tree_sitter_rust::LANGUAGE.into()),
6435 ));
6436
6437 cx.language_registry().add(language.clone());
6438 cx.update_buffer(|buffer, cx| {
6439 buffer.set_language(Some(language), cx);
6440 });
6441
6442 cx.set_state(
6443 &"
6444 ˇ
6445 ˇ
6446 ˇ
6447 "
6448 .unindent(),
6449 );
6450
6451 // ensure only matching closing brackets are skipped over
6452 cx.update_editor(|editor, window, cx| {
6453 editor.handle_input("}", window, cx);
6454 editor.move_left(&MoveLeft, window, cx);
6455 editor.handle_input(")", window, cx);
6456 editor.move_left(&MoveLeft, window, cx);
6457 });
6458 cx.assert_editor_state(
6459 &"
6460 ˇ)}
6461 ˇ)}
6462 ˇ)}
6463 "
6464 .unindent(),
6465 );
6466
6467 // skip-over closing brackets at multiple cursors
6468 cx.update_editor(|editor, window, cx| {
6469 editor.handle_input(")", window, cx);
6470 editor.handle_input("}", window, cx);
6471 });
6472 cx.assert_editor_state(
6473 &"
6474 )}ˇ
6475 )}ˇ
6476 )}ˇ
6477 "
6478 .unindent(),
6479 );
6480
6481 // ignore non-close brackets
6482 cx.update_editor(|editor, window, cx| {
6483 editor.handle_input("]", window, cx);
6484 editor.move_left(&MoveLeft, window, cx);
6485 editor.handle_input("]", window, cx);
6486 });
6487 cx.assert_editor_state(
6488 &"
6489 )}]ˇ]
6490 )}]ˇ]
6491 )}]ˇ]
6492 "
6493 .unindent(),
6494 );
6495}
6496
6497#[gpui::test]
6498async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6499 init_test(cx, |_| {});
6500
6501 let mut cx = EditorTestContext::new(cx).await;
6502
6503 let html_language = Arc::new(
6504 Language::new(
6505 LanguageConfig {
6506 name: "HTML".into(),
6507 brackets: BracketPairConfig {
6508 pairs: vec![
6509 BracketPair {
6510 start: "<".into(),
6511 end: ">".into(),
6512 close: true,
6513 ..Default::default()
6514 },
6515 BracketPair {
6516 start: "{".into(),
6517 end: "}".into(),
6518 close: true,
6519 ..Default::default()
6520 },
6521 BracketPair {
6522 start: "(".into(),
6523 end: ")".into(),
6524 close: true,
6525 ..Default::default()
6526 },
6527 ],
6528 ..Default::default()
6529 },
6530 autoclose_before: "})]>".into(),
6531 ..Default::default()
6532 },
6533 Some(tree_sitter_html::LANGUAGE.into()),
6534 )
6535 .with_injection_query(
6536 r#"
6537 (script_element
6538 (raw_text) @injection.content
6539 (#set! injection.language "javascript"))
6540 "#,
6541 )
6542 .unwrap(),
6543 );
6544
6545 let javascript_language = Arc::new(Language::new(
6546 LanguageConfig {
6547 name: "JavaScript".into(),
6548 brackets: BracketPairConfig {
6549 pairs: vec![
6550 BracketPair {
6551 start: "/*".into(),
6552 end: " */".into(),
6553 close: true,
6554 ..Default::default()
6555 },
6556 BracketPair {
6557 start: "{".into(),
6558 end: "}".into(),
6559 close: true,
6560 ..Default::default()
6561 },
6562 BracketPair {
6563 start: "(".into(),
6564 end: ")".into(),
6565 close: true,
6566 ..Default::default()
6567 },
6568 ],
6569 ..Default::default()
6570 },
6571 autoclose_before: "})]>".into(),
6572 ..Default::default()
6573 },
6574 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6575 ));
6576
6577 cx.language_registry().add(html_language.clone());
6578 cx.language_registry().add(javascript_language.clone());
6579
6580 cx.update_buffer(|buffer, cx| {
6581 buffer.set_language(Some(html_language), cx);
6582 });
6583
6584 cx.set_state(
6585 &r#"
6586 <body>ˇ
6587 <script>
6588 var x = 1;ˇ
6589 </script>
6590 </body>ˇ
6591 "#
6592 .unindent(),
6593 );
6594
6595 // Precondition: different languages are active at different locations.
6596 cx.update_editor(|editor, window, cx| {
6597 let snapshot = editor.snapshot(window, cx);
6598 let cursors = editor.selections.ranges::<usize>(cx);
6599 let languages = cursors
6600 .iter()
6601 .map(|c| snapshot.language_at(c.start).unwrap().name())
6602 .collect::<Vec<_>>();
6603 assert_eq!(
6604 languages,
6605 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6606 );
6607 });
6608
6609 // Angle brackets autoclose in HTML, but not JavaScript.
6610 cx.update_editor(|editor, window, cx| {
6611 editor.handle_input("<", window, cx);
6612 editor.handle_input("a", window, cx);
6613 });
6614 cx.assert_editor_state(
6615 &r#"
6616 <body><aˇ>
6617 <script>
6618 var x = 1;<aˇ
6619 </script>
6620 </body><aˇ>
6621 "#
6622 .unindent(),
6623 );
6624
6625 // Curly braces and parens autoclose in both HTML and JavaScript.
6626 cx.update_editor(|editor, window, cx| {
6627 editor.handle_input(" b=", window, cx);
6628 editor.handle_input("{", window, cx);
6629 editor.handle_input("c", window, cx);
6630 editor.handle_input("(", window, cx);
6631 });
6632 cx.assert_editor_state(
6633 &r#"
6634 <body><a b={c(ˇ)}>
6635 <script>
6636 var x = 1;<a b={c(ˇ)}
6637 </script>
6638 </body><a b={c(ˇ)}>
6639 "#
6640 .unindent(),
6641 );
6642
6643 // Brackets that were already autoclosed are skipped.
6644 cx.update_editor(|editor, window, cx| {
6645 editor.handle_input(")", window, cx);
6646 editor.handle_input("d", window, cx);
6647 editor.handle_input("}", window, cx);
6648 });
6649 cx.assert_editor_state(
6650 &r#"
6651 <body><a b={c()d}ˇ>
6652 <script>
6653 var x = 1;<a b={c()d}ˇ
6654 </script>
6655 </body><a b={c()d}ˇ>
6656 "#
6657 .unindent(),
6658 );
6659 cx.update_editor(|editor, window, cx| {
6660 editor.handle_input(">", window, cx);
6661 });
6662 cx.assert_editor_state(
6663 &r#"
6664 <body><a b={c()d}>ˇ
6665 <script>
6666 var x = 1;<a b={c()d}>ˇ
6667 </script>
6668 </body><a b={c()d}>ˇ
6669 "#
6670 .unindent(),
6671 );
6672
6673 // Reset
6674 cx.set_state(
6675 &r#"
6676 <body>ˇ
6677 <script>
6678 var x = 1;ˇ
6679 </script>
6680 </body>ˇ
6681 "#
6682 .unindent(),
6683 );
6684
6685 cx.update_editor(|editor, window, cx| {
6686 editor.handle_input("<", window, cx);
6687 });
6688 cx.assert_editor_state(
6689 &r#"
6690 <body><ˇ>
6691 <script>
6692 var x = 1;<ˇ
6693 </script>
6694 </body><ˇ>
6695 "#
6696 .unindent(),
6697 );
6698
6699 // When backspacing, the closing angle brackets are removed.
6700 cx.update_editor(|editor, window, cx| {
6701 editor.backspace(&Backspace, window, cx);
6702 });
6703 cx.assert_editor_state(
6704 &r#"
6705 <body>ˇ
6706 <script>
6707 var x = 1;ˇ
6708 </script>
6709 </body>ˇ
6710 "#
6711 .unindent(),
6712 );
6713
6714 // Block comments autoclose in JavaScript, but not HTML.
6715 cx.update_editor(|editor, window, cx| {
6716 editor.handle_input("/", window, cx);
6717 editor.handle_input("*", window, cx);
6718 });
6719 cx.assert_editor_state(
6720 &r#"
6721 <body>/*ˇ
6722 <script>
6723 var x = 1;/*ˇ */
6724 </script>
6725 </body>/*ˇ
6726 "#
6727 .unindent(),
6728 );
6729}
6730
6731#[gpui::test]
6732async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6733 init_test(cx, |_| {});
6734
6735 let mut cx = EditorTestContext::new(cx).await;
6736
6737 let rust_language = Arc::new(
6738 Language::new(
6739 LanguageConfig {
6740 name: "Rust".into(),
6741 brackets: serde_json::from_value(json!([
6742 { "start": "{", "end": "}", "close": true, "newline": true },
6743 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6744 ]))
6745 .unwrap(),
6746 autoclose_before: "})]>".into(),
6747 ..Default::default()
6748 },
6749 Some(tree_sitter_rust::LANGUAGE.into()),
6750 )
6751 .with_override_query("(string_literal) @string")
6752 .unwrap(),
6753 );
6754
6755 cx.language_registry().add(rust_language.clone());
6756 cx.update_buffer(|buffer, cx| {
6757 buffer.set_language(Some(rust_language), cx);
6758 });
6759
6760 cx.set_state(
6761 &r#"
6762 let x = ˇ
6763 "#
6764 .unindent(),
6765 );
6766
6767 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6768 cx.update_editor(|editor, window, cx| {
6769 editor.handle_input("\"", window, cx);
6770 });
6771 cx.assert_editor_state(
6772 &r#"
6773 let x = "ˇ"
6774 "#
6775 .unindent(),
6776 );
6777
6778 // Inserting another quotation mark. The cursor moves across the existing
6779 // automatically-inserted quotation mark.
6780 cx.update_editor(|editor, window, cx| {
6781 editor.handle_input("\"", window, cx);
6782 });
6783 cx.assert_editor_state(
6784 &r#"
6785 let x = ""ˇ
6786 "#
6787 .unindent(),
6788 );
6789
6790 // Reset
6791 cx.set_state(
6792 &r#"
6793 let x = ˇ
6794 "#
6795 .unindent(),
6796 );
6797
6798 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6799 cx.update_editor(|editor, window, cx| {
6800 editor.handle_input("\"", window, cx);
6801 editor.handle_input(" ", window, cx);
6802 editor.move_left(&Default::default(), window, cx);
6803 editor.handle_input("\\", window, cx);
6804 editor.handle_input("\"", window, cx);
6805 });
6806 cx.assert_editor_state(
6807 &r#"
6808 let x = "\"ˇ "
6809 "#
6810 .unindent(),
6811 );
6812
6813 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6814 // mark. Nothing is inserted.
6815 cx.update_editor(|editor, window, cx| {
6816 editor.move_right(&Default::default(), window, cx);
6817 editor.handle_input("\"", window, cx);
6818 });
6819 cx.assert_editor_state(
6820 &r#"
6821 let x = "\" "ˇ
6822 "#
6823 .unindent(),
6824 );
6825}
6826
6827#[gpui::test]
6828async fn test_surround_with_pair(cx: &mut TestAppContext) {
6829 init_test(cx, |_| {});
6830
6831 let language = Arc::new(Language::new(
6832 LanguageConfig {
6833 brackets: BracketPairConfig {
6834 pairs: vec![
6835 BracketPair {
6836 start: "{".to_string(),
6837 end: "}".to_string(),
6838 close: true,
6839 surround: true,
6840 newline: true,
6841 },
6842 BracketPair {
6843 start: "/* ".to_string(),
6844 end: "*/".to_string(),
6845 close: true,
6846 surround: true,
6847 ..Default::default()
6848 },
6849 ],
6850 ..Default::default()
6851 },
6852 ..Default::default()
6853 },
6854 Some(tree_sitter_rust::LANGUAGE.into()),
6855 ));
6856
6857 let text = r#"
6858 a
6859 b
6860 c
6861 "#
6862 .unindent();
6863
6864 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6865 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6866 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6867 editor
6868 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6869 .await;
6870
6871 editor.update_in(cx, |editor, window, cx| {
6872 editor.change_selections(None, window, cx, |s| {
6873 s.select_display_ranges([
6874 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6875 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6876 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6877 ])
6878 });
6879
6880 editor.handle_input("{", window, cx);
6881 editor.handle_input("{", window, cx);
6882 editor.handle_input("{", window, cx);
6883 assert_eq!(
6884 editor.text(cx),
6885 "
6886 {{{a}}}
6887 {{{b}}}
6888 {{{c}}}
6889 "
6890 .unindent()
6891 );
6892 assert_eq!(
6893 editor.selections.display_ranges(cx),
6894 [
6895 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6896 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6897 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6898 ]
6899 );
6900
6901 editor.undo(&Undo, window, cx);
6902 editor.undo(&Undo, window, cx);
6903 editor.undo(&Undo, window, cx);
6904 assert_eq!(
6905 editor.text(cx),
6906 "
6907 a
6908 b
6909 c
6910 "
6911 .unindent()
6912 );
6913 assert_eq!(
6914 editor.selections.display_ranges(cx),
6915 [
6916 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6917 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6918 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6919 ]
6920 );
6921
6922 // Ensure inserting the first character of a multi-byte bracket pair
6923 // doesn't surround the selections with the bracket.
6924 editor.handle_input("/", window, cx);
6925 assert_eq!(
6926 editor.text(cx),
6927 "
6928 /
6929 /
6930 /
6931 "
6932 .unindent()
6933 );
6934 assert_eq!(
6935 editor.selections.display_ranges(cx),
6936 [
6937 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6938 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6939 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6940 ]
6941 );
6942
6943 editor.undo(&Undo, window, cx);
6944 assert_eq!(
6945 editor.text(cx),
6946 "
6947 a
6948 b
6949 c
6950 "
6951 .unindent()
6952 );
6953 assert_eq!(
6954 editor.selections.display_ranges(cx),
6955 [
6956 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6957 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6958 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6959 ]
6960 );
6961
6962 // Ensure inserting the last character of a multi-byte bracket pair
6963 // doesn't surround the selections with the bracket.
6964 editor.handle_input("*", window, cx);
6965 assert_eq!(
6966 editor.text(cx),
6967 "
6968 *
6969 *
6970 *
6971 "
6972 .unindent()
6973 );
6974 assert_eq!(
6975 editor.selections.display_ranges(cx),
6976 [
6977 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6978 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6979 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6980 ]
6981 );
6982 });
6983}
6984
6985#[gpui::test]
6986async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6987 init_test(cx, |_| {});
6988
6989 let language = Arc::new(Language::new(
6990 LanguageConfig {
6991 brackets: BracketPairConfig {
6992 pairs: vec![BracketPair {
6993 start: "{".to_string(),
6994 end: "}".to_string(),
6995 close: true,
6996 surround: true,
6997 newline: true,
6998 }],
6999 ..Default::default()
7000 },
7001 autoclose_before: "}".to_string(),
7002 ..Default::default()
7003 },
7004 Some(tree_sitter_rust::LANGUAGE.into()),
7005 ));
7006
7007 let text = r#"
7008 a
7009 b
7010 c
7011 "#
7012 .unindent();
7013
7014 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7015 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7016 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7017 editor
7018 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7019 .await;
7020
7021 editor.update_in(cx, |editor, window, cx| {
7022 editor.change_selections(None, window, cx, |s| {
7023 s.select_ranges([
7024 Point::new(0, 1)..Point::new(0, 1),
7025 Point::new(1, 1)..Point::new(1, 1),
7026 Point::new(2, 1)..Point::new(2, 1),
7027 ])
7028 });
7029
7030 editor.handle_input("{", window, cx);
7031 editor.handle_input("{", window, cx);
7032 editor.handle_input("_", window, cx);
7033 assert_eq!(
7034 editor.text(cx),
7035 "
7036 a{{_}}
7037 b{{_}}
7038 c{{_}}
7039 "
7040 .unindent()
7041 );
7042 assert_eq!(
7043 editor.selections.ranges::<Point>(cx),
7044 [
7045 Point::new(0, 4)..Point::new(0, 4),
7046 Point::new(1, 4)..Point::new(1, 4),
7047 Point::new(2, 4)..Point::new(2, 4)
7048 ]
7049 );
7050
7051 editor.backspace(&Default::default(), window, cx);
7052 editor.backspace(&Default::default(), window, cx);
7053 assert_eq!(
7054 editor.text(cx),
7055 "
7056 a{}
7057 b{}
7058 c{}
7059 "
7060 .unindent()
7061 );
7062 assert_eq!(
7063 editor.selections.ranges::<Point>(cx),
7064 [
7065 Point::new(0, 2)..Point::new(0, 2),
7066 Point::new(1, 2)..Point::new(1, 2),
7067 Point::new(2, 2)..Point::new(2, 2)
7068 ]
7069 );
7070
7071 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7072 assert_eq!(
7073 editor.text(cx),
7074 "
7075 a
7076 b
7077 c
7078 "
7079 .unindent()
7080 );
7081 assert_eq!(
7082 editor.selections.ranges::<Point>(cx),
7083 [
7084 Point::new(0, 1)..Point::new(0, 1),
7085 Point::new(1, 1)..Point::new(1, 1),
7086 Point::new(2, 1)..Point::new(2, 1)
7087 ]
7088 );
7089 });
7090}
7091
7092#[gpui::test]
7093async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7094 init_test(cx, |settings| {
7095 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7096 });
7097
7098 let mut cx = EditorTestContext::new(cx).await;
7099
7100 let language = Arc::new(Language::new(
7101 LanguageConfig {
7102 brackets: BracketPairConfig {
7103 pairs: vec![
7104 BracketPair {
7105 start: "{".to_string(),
7106 end: "}".to_string(),
7107 close: true,
7108 surround: true,
7109 newline: true,
7110 },
7111 BracketPair {
7112 start: "(".to_string(),
7113 end: ")".to_string(),
7114 close: true,
7115 surround: true,
7116 newline: true,
7117 },
7118 BracketPair {
7119 start: "[".to_string(),
7120 end: "]".to_string(),
7121 close: false,
7122 surround: true,
7123 newline: true,
7124 },
7125 ],
7126 ..Default::default()
7127 },
7128 autoclose_before: "})]".to_string(),
7129 ..Default::default()
7130 },
7131 Some(tree_sitter_rust::LANGUAGE.into()),
7132 ));
7133
7134 cx.language_registry().add(language.clone());
7135 cx.update_buffer(|buffer, cx| {
7136 buffer.set_language(Some(language), cx);
7137 });
7138
7139 cx.set_state(
7140 &"
7141 {(ˇ)}
7142 [[ˇ]]
7143 {(ˇ)}
7144 "
7145 .unindent(),
7146 );
7147
7148 cx.update_editor(|editor, window, cx| {
7149 editor.backspace(&Default::default(), window, cx);
7150 editor.backspace(&Default::default(), window, cx);
7151 });
7152
7153 cx.assert_editor_state(
7154 &"
7155 ˇ
7156 ˇ]]
7157 ˇ
7158 "
7159 .unindent(),
7160 );
7161
7162 cx.update_editor(|editor, window, cx| {
7163 editor.handle_input("{", window, cx);
7164 editor.handle_input("{", window, cx);
7165 editor.move_right(&MoveRight, window, cx);
7166 editor.move_right(&MoveRight, window, cx);
7167 editor.move_left(&MoveLeft, window, cx);
7168 editor.move_left(&MoveLeft, window, cx);
7169 editor.backspace(&Default::default(), window, cx);
7170 });
7171
7172 cx.assert_editor_state(
7173 &"
7174 {ˇ}
7175 {ˇ}]]
7176 {ˇ}
7177 "
7178 .unindent(),
7179 );
7180
7181 cx.update_editor(|editor, window, cx| {
7182 editor.backspace(&Default::default(), window, cx);
7183 });
7184
7185 cx.assert_editor_state(
7186 &"
7187 ˇ
7188 ˇ]]
7189 ˇ
7190 "
7191 .unindent(),
7192 );
7193}
7194
7195#[gpui::test]
7196async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7197 init_test(cx, |_| {});
7198
7199 let language = Arc::new(Language::new(
7200 LanguageConfig::default(),
7201 Some(tree_sitter_rust::LANGUAGE.into()),
7202 ));
7203
7204 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7205 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7206 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7207 editor
7208 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7209 .await;
7210
7211 editor.update_in(cx, |editor, window, cx| {
7212 editor.set_auto_replace_emoji_shortcode(true);
7213
7214 editor.handle_input("Hello ", window, cx);
7215 editor.handle_input(":wave", window, cx);
7216 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7217
7218 editor.handle_input(":", window, cx);
7219 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7220
7221 editor.handle_input(" :smile", window, cx);
7222 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7223
7224 editor.handle_input(":", window, cx);
7225 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7226
7227 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7228 editor.handle_input(":wave", window, cx);
7229 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7230
7231 editor.handle_input(":", window, cx);
7232 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7233
7234 editor.handle_input(":1", window, cx);
7235 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7236
7237 editor.handle_input(":", window, cx);
7238 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7239
7240 // Ensure shortcode does not get replaced when it is part of a word
7241 editor.handle_input(" Test:wave", window, cx);
7242 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7243
7244 editor.handle_input(":", window, cx);
7245 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7246
7247 editor.set_auto_replace_emoji_shortcode(false);
7248
7249 // Ensure shortcode does not get replaced when auto replace is off
7250 editor.handle_input(" :wave", window, cx);
7251 assert_eq!(
7252 editor.text(cx),
7253 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7254 );
7255
7256 editor.handle_input(":", window, cx);
7257 assert_eq!(
7258 editor.text(cx),
7259 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7260 );
7261 });
7262}
7263
7264#[gpui::test]
7265async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7266 init_test(cx, |_| {});
7267
7268 let (text, insertion_ranges) = marked_text_ranges(
7269 indoc! {"
7270 ˇ
7271 "},
7272 false,
7273 );
7274
7275 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7276 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7277
7278 _ = editor.update_in(cx, |editor, window, cx| {
7279 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7280
7281 editor
7282 .insert_snippet(&insertion_ranges, snippet, window, cx)
7283 .unwrap();
7284
7285 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7286 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7287 assert_eq!(editor.text(cx), expected_text);
7288 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7289 }
7290
7291 assert(
7292 editor,
7293 cx,
7294 indoc! {"
7295 type «» =•
7296 "},
7297 );
7298
7299 assert!(editor.context_menu_visible(), "There should be a matches");
7300 });
7301}
7302
7303#[gpui::test]
7304async fn test_snippets(cx: &mut TestAppContext) {
7305 init_test(cx, |_| {});
7306
7307 let (text, insertion_ranges) = marked_text_ranges(
7308 indoc! {"
7309 a.ˇ b
7310 a.ˇ b
7311 a.ˇ b
7312 "},
7313 false,
7314 );
7315
7316 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7317 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7318
7319 editor.update_in(cx, |editor, window, cx| {
7320 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7321
7322 editor
7323 .insert_snippet(&insertion_ranges, snippet, window, cx)
7324 .unwrap();
7325
7326 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7327 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7328 assert_eq!(editor.text(cx), expected_text);
7329 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7330 }
7331
7332 assert(
7333 editor,
7334 cx,
7335 indoc! {"
7336 a.f(«one», two, «three») b
7337 a.f(«one», two, «three») b
7338 a.f(«one», two, «three») b
7339 "},
7340 );
7341
7342 // Can't move earlier than the first tab stop
7343 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7344 assert(
7345 editor,
7346 cx,
7347 indoc! {"
7348 a.f(«one», two, «three») b
7349 a.f(«one», two, «three») b
7350 a.f(«one», two, «three») b
7351 "},
7352 );
7353
7354 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7355 assert(
7356 editor,
7357 cx,
7358 indoc! {"
7359 a.f(one, «two», three) b
7360 a.f(one, «two», three) b
7361 a.f(one, «two», three) b
7362 "},
7363 );
7364
7365 editor.move_to_prev_snippet_tabstop(window, cx);
7366 assert(
7367 editor,
7368 cx,
7369 indoc! {"
7370 a.f(«one», two, «three») b
7371 a.f(«one», two, «three») b
7372 a.f(«one», two, «three») b
7373 "},
7374 );
7375
7376 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7377 assert(
7378 editor,
7379 cx,
7380 indoc! {"
7381 a.f(one, «two», three) b
7382 a.f(one, «two», three) b
7383 a.f(one, «two», three) b
7384 "},
7385 );
7386 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7387 assert(
7388 editor,
7389 cx,
7390 indoc! {"
7391 a.f(one, two, three)ˇ b
7392 a.f(one, two, three)ˇ b
7393 a.f(one, two, three)ˇ b
7394 "},
7395 );
7396
7397 // As soon as the last tab stop is reached, snippet state is gone
7398 editor.move_to_prev_snippet_tabstop(window, cx);
7399 assert(
7400 editor,
7401 cx,
7402 indoc! {"
7403 a.f(one, two, three)ˇ b
7404 a.f(one, two, three)ˇ b
7405 a.f(one, two, three)ˇ b
7406 "},
7407 );
7408 });
7409}
7410
7411#[gpui::test]
7412async fn test_document_format_during_save(cx: &mut TestAppContext) {
7413 init_test(cx, |_| {});
7414
7415 let fs = FakeFs::new(cx.executor());
7416 fs.insert_file(path!("/file.rs"), Default::default()).await;
7417
7418 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7419
7420 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7421 language_registry.add(rust_lang());
7422 let mut fake_servers = language_registry.register_fake_lsp(
7423 "Rust",
7424 FakeLspAdapter {
7425 capabilities: lsp::ServerCapabilities {
7426 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7427 ..Default::default()
7428 },
7429 ..Default::default()
7430 },
7431 );
7432
7433 let buffer = project
7434 .update(cx, |project, cx| {
7435 project.open_local_buffer(path!("/file.rs"), cx)
7436 })
7437 .await
7438 .unwrap();
7439
7440 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7441 let (editor, cx) = cx.add_window_view(|window, cx| {
7442 build_editor_with_project(project.clone(), buffer, window, cx)
7443 });
7444 editor.update_in(cx, |editor, window, cx| {
7445 editor.set_text("one\ntwo\nthree\n", window, cx)
7446 });
7447 assert!(cx.read(|cx| editor.is_dirty(cx)));
7448
7449 cx.executor().start_waiting();
7450 let fake_server = fake_servers.next().await.unwrap();
7451
7452 let save = editor
7453 .update_in(cx, |editor, window, cx| {
7454 editor.save(true, project.clone(), window, cx)
7455 })
7456 .unwrap();
7457 fake_server
7458 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7459 assert_eq!(
7460 params.text_document.uri,
7461 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7462 );
7463 assert_eq!(params.options.tab_size, 4);
7464 Ok(Some(vec![lsp::TextEdit::new(
7465 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7466 ", ".to_string(),
7467 )]))
7468 })
7469 .next()
7470 .await;
7471 cx.executor().start_waiting();
7472 save.await;
7473
7474 assert_eq!(
7475 editor.update(cx, |editor, cx| editor.text(cx)),
7476 "one, two\nthree\n"
7477 );
7478 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7479
7480 editor.update_in(cx, |editor, window, cx| {
7481 editor.set_text("one\ntwo\nthree\n", window, cx)
7482 });
7483 assert!(cx.read(|cx| editor.is_dirty(cx)));
7484
7485 // Ensure we can still save even if formatting hangs.
7486 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7487 assert_eq!(
7488 params.text_document.uri,
7489 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7490 );
7491 futures::future::pending::<()>().await;
7492 unreachable!()
7493 });
7494 let save = editor
7495 .update_in(cx, |editor, window, cx| {
7496 editor.save(true, project.clone(), window, cx)
7497 })
7498 .unwrap();
7499 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7500 cx.executor().start_waiting();
7501 save.await;
7502 assert_eq!(
7503 editor.update(cx, |editor, cx| editor.text(cx)),
7504 "one\ntwo\nthree\n"
7505 );
7506 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7507
7508 // For non-dirty buffer, no formatting request should be sent
7509 let save = editor
7510 .update_in(cx, |editor, window, cx| {
7511 editor.save(true, project.clone(), window, cx)
7512 })
7513 .unwrap();
7514 let _pending_format_request = fake_server
7515 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7516 panic!("Should not be invoked on non-dirty buffer");
7517 })
7518 .next();
7519 cx.executor().start_waiting();
7520 save.await;
7521
7522 // Set rust language override and assert overridden tabsize is sent to language server
7523 update_test_language_settings(cx, |settings| {
7524 settings.languages.insert(
7525 "Rust".into(),
7526 LanguageSettingsContent {
7527 tab_size: NonZeroU32::new(8),
7528 ..Default::default()
7529 },
7530 );
7531 });
7532
7533 editor.update_in(cx, |editor, window, cx| {
7534 editor.set_text("somehting_new\n", window, cx)
7535 });
7536 assert!(cx.read(|cx| editor.is_dirty(cx)));
7537 let save = editor
7538 .update_in(cx, |editor, window, cx| {
7539 editor.save(true, project.clone(), window, cx)
7540 })
7541 .unwrap();
7542 fake_server
7543 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7544 assert_eq!(
7545 params.text_document.uri,
7546 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7547 );
7548 assert_eq!(params.options.tab_size, 8);
7549 Ok(Some(vec![]))
7550 })
7551 .next()
7552 .await;
7553 cx.executor().start_waiting();
7554 save.await;
7555}
7556
7557#[gpui::test]
7558async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7559 init_test(cx, |_| {});
7560
7561 let cols = 4;
7562 let rows = 10;
7563 let sample_text_1 = sample_text(rows, cols, 'a');
7564 assert_eq!(
7565 sample_text_1,
7566 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7567 );
7568 let sample_text_2 = sample_text(rows, cols, 'l');
7569 assert_eq!(
7570 sample_text_2,
7571 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7572 );
7573 let sample_text_3 = sample_text(rows, cols, 'v');
7574 assert_eq!(
7575 sample_text_3,
7576 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7577 );
7578
7579 let fs = FakeFs::new(cx.executor());
7580 fs.insert_tree(
7581 path!("/a"),
7582 json!({
7583 "main.rs": sample_text_1,
7584 "other.rs": sample_text_2,
7585 "lib.rs": sample_text_3,
7586 }),
7587 )
7588 .await;
7589
7590 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7591 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7592 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7593
7594 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7595 language_registry.add(rust_lang());
7596 let mut fake_servers = language_registry.register_fake_lsp(
7597 "Rust",
7598 FakeLspAdapter {
7599 capabilities: lsp::ServerCapabilities {
7600 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7601 ..Default::default()
7602 },
7603 ..Default::default()
7604 },
7605 );
7606
7607 let worktree = project.update(cx, |project, cx| {
7608 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7609 assert_eq!(worktrees.len(), 1);
7610 worktrees.pop().unwrap()
7611 });
7612 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7613
7614 let buffer_1 = project
7615 .update(cx, |project, cx| {
7616 project.open_buffer((worktree_id, "main.rs"), cx)
7617 })
7618 .await
7619 .unwrap();
7620 let buffer_2 = project
7621 .update(cx, |project, cx| {
7622 project.open_buffer((worktree_id, "other.rs"), cx)
7623 })
7624 .await
7625 .unwrap();
7626 let buffer_3 = project
7627 .update(cx, |project, cx| {
7628 project.open_buffer((worktree_id, "lib.rs"), cx)
7629 })
7630 .await
7631 .unwrap();
7632
7633 let multi_buffer = cx.new(|cx| {
7634 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7635 multi_buffer.push_excerpts(
7636 buffer_1.clone(),
7637 [
7638 ExcerptRange {
7639 context: Point::new(0, 0)..Point::new(3, 0),
7640 primary: None,
7641 },
7642 ExcerptRange {
7643 context: Point::new(5, 0)..Point::new(7, 0),
7644 primary: None,
7645 },
7646 ExcerptRange {
7647 context: Point::new(9, 0)..Point::new(10, 4),
7648 primary: None,
7649 },
7650 ],
7651 cx,
7652 );
7653 multi_buffer.push_excerpts(
7654 buffer_2.clone(),
7655 [
7656 ExcerptRange {
7657 context: Point::new(0, 0)..Point::new(3, 0),
7658 primary: None,
7659 },
7660 ExcerptRange {
7661 context: Point::new(5, 0)..Point::new(7, 0),
7662 primary: None,
7663 },
7664 ExcerptRange {
7665 context: Point::new(9, 0)..Point::new(10, 4),
7666 primary: None,
7667 },
7668 ],
7669 cx,
7670 );
7671 multi_buffer.push_excerpts(
7672 buffer_3.clone(),
7673 [
7674 ExcerptRange {
7675 context: Point::new(0, 0)..Point::new(3, 0),
7676 primary: None,
7677 },
7678 ExcerptRange {
7679 context: Point::new(5, 0)..Point::new(7, 0),
7680 primary: None,
7681 },
7682 ExcerptRange {
7683 context: Point::new(9, 0)..Point::new(10, 4),
7684 primary: None,
7685 },
7686 ],
7687 cx,
7688 );
7689 multi_buffer
7690 });
7691 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7692 Editor::new(
7693 EditorMode::Full,
7694 multi_buffer,
7695 Some(project.clone()),
7696 window,
7697 cx,
7698 )
7699 });
7700
7701 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7702 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7703 s.select_ranges(Some(1..2))
7704 });
7705 editor.insert("|one|two|three|", window, cx);
7706 });
7707 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7708 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7709 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7710 s.select_ranges(Some(60..70))
7711 });
7712 editor.insert("|four|five|six|", window, cx);
7713 });
7714 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7715
7716 // First two buffers should be edited, but not the third one.
7717 assert_eq!(
7718 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7719 "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}",
7720 );
7721 buffer_1.update(cx, |buffer, _| {
7722 assert!(buffer.is_dirty());
7723 assert_eq!(
7724 buffer.text(),
7725 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7726 )
7727 });
7728 buffer_2.update(cx, |buffer, _| {
7729 assert!(buffer.is_dirty());
7730 assert_eq!(
7731 buffer.text(),
7732 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7733 )
7734 });
7735 buffer_3.update(cx, |buffer, _| {
7736 assert!(!buffer.is_dirty());
7737 assert_eq!(buffer.text(), sample_text_3,)
7738 });
7739 cx.executor().run_until_parked();
7740
7741 cx.executor().start_waiting();
7742 let save = multi_buffer_editor
7743 .update_in(cx, |editor, window, cx| {
7744 editor.save(true, project.clone(), window, cx)
7745 })
7746 .unwrap();
7747
7748 let fake_server = fake_servers.next().await.unwrap();
7749 fake_server
7750 .server
7751 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7752 Ok(Some(vec![lsp::TextEdit::new(
7753 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7754 format!("[{} formatted]", params.text_document.uri),
7755 )]))
7756 })
7757 .detach();
7758 save.await;
7759
7760 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7761 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7762 assert_eq!(
7763 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7764 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}"),
7765 );
7766 buffer_1.update(cx, |buffer, _| {
7767 assert!(!buffer.is_dirty());
7768 assert_eq!(
7769 buffer.text(),
7770 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7771 )
7772 });
7773 buffer_2.update(cx, |buffer, _| {
7774 assert!(!buffer.is_dirty());
7775 assert_eq!(
7776 buffer.text(),
7777 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7778 )
7779 });
7780 buffer_3.update(cx, |buffer, _| {
7781 assert!(!buffer.is_dirty());
7782 assert_eq!(buffer.text(), sample_text_3,)
7783 });
7784}
7785
7786#[gpui::test]
7787async fn test_range_format_during_save(cx: &mut TestAppContext) {
7788 init_test(cx, |_| {});
7789
7790 let fs = FakeFs::new(cx.executor());
7791 fs.insert_file(path!("/file.rs"), Default::default()).await;
7792
7793 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7794
7795 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7796 language_registry.add(rust_lang());
7797 let mut fake_servers = language_registry.register_fake_lsp(
7798 "Rust",
7799 FakeLspAdapter {
7800 capabilities: lsp::ServerCapabilities {
7801 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7802 ..Default::default()
7803 },
7804 ..Default::default()
7805 },
7806 );
7807
7808 let buffer = project
7809 .update(cx, |project, cx| {
7810 project.open_local_buffer(path!("/file.rs"), cx)
7811 })
7812 .await
7813 .unwrap();
7814
7815 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7816 let (editor, cx) = cx.add_window_view(|window, cx| {
7817 build_editor_with_project(project.clone(), buffer, window, cx)
7818 });
7819 editor.update_in(cx, |editor, window, cx| {
7820 editor.set_text("one\ntwo\nthree\n", window, cx)
7821 });
7822 assert!(cx.read(|cx| editor.is_dirty(cx)));
7823
7824 cx.executor().start_waiting();
7825 let fake_server = fake_servers.next().await.unwrap();
7826
7827 let save = editor
7828 .update_in(cx, |editor, window, cx| {
7829 editor.save(true, project.clone(), window, cx)
7830 })
7831 .unwrap();
7832 fake_server
7833 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7834 assert_eq!(
7835 params.text_document.uri,
7836 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7837 );
7838 assert_eq!(params.options.tab_size, 4);
7839 Ok(Some(vec![lsp::TextEdit::new(
7840 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7841 ", ".to_string(),
7842 )]))
7843 })
7844 .next()
7845 .await;
7846 cx.executor().start_waiting();
7847 save.await;
7848 assert_eq!(
7849 editor.update(cx, |editor, cx| editor.text(cx)),
7850 "one, two\nthree\n"
7851 );
7852 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7853
7854 editor.update_in(cx, |editor, window, cx| {
7855 editor.set_text("one\ntwo\nthree\n", window, cx)
7856 });
7857 assert!(cx.read(|cx| editor.is_dirty(cx)));
7858
7859 // Ensure we can still save even if formatting hangs.
7860 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7861 move |params, _| async move {
7862 assert_eq!(
7863 params.text_document.uri,
7864 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7865 );
7866 futures::future::pending::<()>().await;
7867 unreachable!()
7868 },
7869 );
7870 let save = editor
7871 .update_in(cx, |editor, window, cx| {
7872 editor.save(true, project.clone(), window, cx)
7873 })
7874 .unwrap();
7875 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7876 cx.executor().start_waiting();
7877 save.await;
7878 assert_eq!(
7879 editor.update(cx, |editor, cx| editor.text(cx)),
7880 "one\ntwo\nthree\n"
7881 );
7882 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7883
7884 // For non-dirty buffer, no formatting request should be sent
7885 let save = editor
7886 .update_in(cx, |editor, window, cx| {
7887 editor.save(true, project.clone(), window, cx)
7888 })
7889 .unwrap();
7890 let _pending_format_request = fake_server
7891 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7892 panic!("Should not be invoked on non-dirty buffer");
7893 })
7894 .next();
7895 cx.executor().start_waiting();
7896 save.await;
7897
7898 // Set Rust language override and assert overridden tabsize is sent to language server
7899 update_test_language_settings(cx, |settings| {
7900 settings.languages.insert(
7901 "Rust".into(),
7902 LanguageSettingsContent {
7903 tab_size: NonZeroU32::new(8),
7904 ..Default::default()
7905 },
7906 );
7907 });
7908
7909 editor.update_in(cx, |editor, window, cx| {
7910 editor.set_text("somehting_new\n", window, cx)
7911 });
7912 assert!(cx.read(|cx| editor.is_dirty(cx)));
7913 let save = editor
7914 .update_in(cx, |editor, window, cx| {
7915 editor.save(true, project.clone(), window, cx)
7916 })
7917 .unwrap();
7918 fake_server
7919 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7920 assert_eq!(
7921 params.text_document.uri,
7922 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7923 );
7924 assert_eq!(params.options.tab_size, 8);
7925 Ok(Some(vec![]))
7926 })
7927 .next()
7928 .await;
7929 cx.executor().start_waiting();
7930 save.await;
7931}
7932
7933#[gpui::test]
7934async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7935 init_test(cx, |settings| {
7936 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7937 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7938 ))
7939 });
7940
7941 let fs = FakeFs::new(cx.executor());
7942 fs.insert_file(path!("/file.rs"), Default::default()).await;
7943
7944 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7945
7946 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7947 language_registry.add(Arc::new(Language::new(
7948 LanguageConfig {
7949 name: "Rust".into(),
7950 matcher: LanguageMatcher {
7951 path_suffixes: vec!["rs".to_string()],
7952 ..Default::default()
7953 },
7954 ..LanguageConfig::default()
7955 },
7956 Some(tree_sitter_rust::LANGUAGE.into()),
7957 )));
7958 update_test_language_settings(cx, |settings| {
7959 // Enable Prettier formatting for the same buffer, and ensure
7960 // LSP is called instead of Prettier.
7961 settings.defaults.prettier = Some(PrettierSettings {
7962 allowed: true,
7963 ..PrettierSettings::default()
7964 });
7965 });
7966 let mut fake_servers = language_registry.register_fake_lsp(
7967 "Rust",
7968 FakeLspAdapter {
7969 capabilities: lsp::ServerCapabilities {
7970 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7971 ..Default::default()
7972 },
7973 ..Default::default()
7974 },
7975 );
7976
7977 let buffer = project
7978 .update(cx, |project, cx| {
7979 project.open_local_buffer(path!("/file.rs"), cx)
7980 })
7981 .await
7982 .unwrap();
7983
7984 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7985 let (editor, cx) = cx.add_window_view(|window, cx| {
7986 build_editor_with_project(project.clone(), buffer, window, cx)
7987 });
7988 editor.update_in(cx, |editor, window, cx| {
7989 editor.set_text("one\ntwo\nthree\n", window, cx)
7990 });
7991
7992 cx.executor().start_waiting();
7993 let fake_server = fake_servers.next().await.unwrap();
7994
7995 let format = editor
7996 .update_in(cx, |editor, window, cx| {
7997 editor.perform_format(
7998 project.clone(),
7999 FormatTrigger::Manual,
8000 FormatTarget::Buffers,
8001 window,
8002 cx,
8003 )
8004 })
8005 .unwrap();
8006 fake_server
8007 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8008 assert_eq!(
8009 params.text_document.uri,
8010 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8011 );
8012 assert_eq!(params.options.tab_size, 4);
8013 Ok(Some(vec![lsp::TextEdit::new(
8014 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8015 ", ".to_string(),
8016 )]))
8017 })
8018 .next()
8019 .await;
8020 cx.executor().start_waiting();
8021 format.await;
8022 assert_eq!(
8023 editor.update(cx, |editor, cx| editor.text(cx)),
8024 "one, two\nthree\n"
8025 );
8026
8027 editor.update_in(cx, |editor, window, cx| {
8028 editor.set_text("one\ntwo\nthree\n", window, cx)
8029 });
8030 // Ensure we don't lock if formatting hangs.
8031 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8032 assert_eq!(
8033 params.text_document.uri,
8034 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8035 );
8036 futures::future::pending::<()>().await;
8037 unreachable!()
8038 });
8039 let format = editor
8040 .update_in(cx, |editor, window, cx| {
8041 editor.perform_format(
8042 project,
8043 FormatTrigger::Manual,
8044 FormatTarget::Buffers,
8045 window,
8046 cx,
8047 )
8048 })
8049 .unwrap();
8050 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8051 cx.executor().start_waiting();
8052 format.await;
8053 assert_eq!(
8054 editor.update(cx, |editor, cx| editor.text(cx)),
8055 "one\ntwo\nthree\n"
8056 );
8057}
8058
8059#[gpui::test]
8060async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8061 init_test(cx, |settings| {
8062 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8063 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8064 ))
8065 });
8066
8067 let fs = FakeFs::new(cx.executor());
8068 fs.insert_file(path!("/file.ts"), Default::default()).await;
8069
8070 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8071
8072 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8073 language_registry.add(Arc::new(Language::new(
8074 LanguageConfig {
8075 name: "TypeScript".into(),
8076 matcher: LanguageMatcher {
8077 path_suffixes: vec!["ts".to_string()],
8078 ..Default::default()
8079 },
8080 ..LanguageConfig::default()
8081 },
8082 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8083 )));
8084 update_test_language_settings(cx, |settings| {
8085 settings.defaults.prettier = Some(PrettierSettings {
8086 allowed: true,
8087 ..PrettierSettings::default()
8088 });
8089 });
8090 let mut fake_servers = language_registry.register_fake_lsp(
8091 "TypeScript",
8092 FakeLspAdapter {
8093 capabilities: lsp::ServerCapabilities {
8094 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8095 ..Default::default()
8096 },
8097 ..Default::default()
8098 },
8099 );
8100
8101 let buffer = project
8102 .update(cx, |project, cx| {
8103 project.open_local_buffer(path!("/file.ts"), cx)
8104 })
8105 .await
8106 .unwrap();
8107
8108 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8109 let (editor, cx) = cx.add_window_view(|window, cx| {
8110 build_editor_with_project(project.clone(), buffer, window, cx)
8111 });
8112 editor.update_in(cx, |editor, window, cx| {
8113 editor.set_text(
8114 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8115 window,
8116 cx,
8117 )
8118 });
8119
8120 cx.executor().start_waiting();
8121 let fake_server = fake_servers.next().await.unwrap();
8122
8123 let format = editor
8124 .update_in(cx, |editor, window, cx| {
8125 editor.perform_code_action_kind(
8126 project.clone(),
8127 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8128 window,
8129 cx,
8130 )
8131 })
8132 .unwrap();
8133 fake_server
8134 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8135 assert_eq!(
8136 params.text_document.uri,
8137 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8138 );
8139 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8140 lsp::CodeAction {
8141 title: "Organize Imports".to_string(),
8142 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8143 edit: Some(lsp::WorkspaceEdit {
8144 changes: Some(
8145 [(
8146 params.text_document.uri.clone(),
8147 vec![lsp::TextEdit::new(
8148 lsp::Range::new(
8149 lsp::Position::new(1, 0),
8150 lsp::Position::new(2, 0),
8151 ),
8152 "".to_string(),
8153 )],
8154 )]
8155 .into_iter()
8156 .collect(),
8157 ),
8158 ..Default::default()
8159 }),
8160 ..Default::default()
8161 },
8162 )]))
8163 })
8164 .next()
8165 .await;
8166 cx.executor().start_waiting();
8167 format.await;
8168 assert_eq!(
8169 editor.update(cx, |editor, cx| editor.text(cx)),
8170 "import { a } from 'module';\n\nconst x = a;\n"
8171 );
8172
8173 editor.update_in(cx, |editor, window, cx| {
8174 editor.set_text(
8175 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8176 window,
8177 cx,
8178 )
8179 });
8180 // Ensure we don't lock if code action hangs.
8181 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8182 move |params, _| async move {
8183 assert_eq!(
8184 params.text_document.uri,
8185 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8186 );
8187 futures::future::pending::<()>().await;
8188 unreachable!()
8189 },
8190 );
8191 let format = editor
8192 .update_in(cx, |editor, window, cx| {
8193 editor.perform_code_action_kind(
8194 project,
8195 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8196 window,
8197 cx,
8198 )
8199 })
8200 .unwrap();
8201 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8202 cx.executor().start_waiting();
8203 format.await;
8204 assert_eq!(
8205 editor.update(cx, |editor, cx| editor.text(cx)),
8206 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8207 );
8208}
8209
8210#[gpui::test]
8211async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8212 init_test(cx, |_| {});
8213
8214 let mut cx = EditorLspTestContext::new_rust(
8215 lsp::ServerCapabilities {
8216 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8217 ..Default::default()
8218 },
8219 cx,
8220 )
8221 .await;
8222
8223 cx.set_state(indoc! {"
8224 one.twoˇ
8225 "});
8226
8227 // The format request takes a long time. When it completes, it inserts
8228 // a newline and an indent before the `.`
8229 cx.lsp
8230 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8231 let executor = cx.background_executor().clone();
8232 async move {
8233 executor.timer(Duration::from_millis(100)).await;
8234 Ok(Some(vec![lsp::TextEdit {
8235 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8236 new_text: "\n ".into(),
8237 }]))
8238 }
8239 });
8240
8241 // Submit a format request.
8242 let format_1 = cx
8243 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8244 .unwrap();
8245 cx.executor().run_until_parked();
8246
8247 // Submit a second format request.
8248 let format_2 = cx
8249 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8250 .unwrap();
8251 cx.executor().run_until_parked();
8252
8253 // Wait for both format requests to complete
8254 cx.executor().advance_clock(Duration::from_millis(200));
8255 cx.executor().start_waiting();
8256 format_1.await.unwrap();
8257 cx.executor().start_waiting();
8258 format_2.await.unwrap();
8259
8260 // The formatting edits only happens once.
8261 cx.assert_editor_state(indoc! {"
8262 one
8263 .twoˇ
8264 "});
8265}
8266
8267#[gpui::test]
8268async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8269 init_test(cx, |settings| {
8270 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8271 });
8272
8273 let mut cx = EditorLspTestContext::new_rust(
8274 lsp::ServerCapabilities {
8275 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8276 ..Default::default()
8277 },
8278 cx,
8279 )
8280 .await;
8281
8282 // Set up a buffer white some trailing whitespace and no trailing newline.
8283 cx.set_state(
8284 &[
8285 "one ", //
8286 "twoˇ", //
8287 "three ", //
8288 "four", //
8289 ]
8290 .join("\n"),
8291 );
8292
8293 // Submit a format request.
8294 let format = cx
8295 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8296 .unwrap();
8297
8298 // Record which buffer changes have been sent to the language server
8299 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8300 cx.lsp
8301 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8302 let buffer_changes = buffer_changes.clone();
8303 move |params, _| {
8304 buffer_changes.lock().extend(
8305 params
8306 .content_changes
8307 .into_iter()
8308 .map(|e| (e.range.unwrap(), e.text)),
8309 );
8310 }
8311 });
8312
8313 // Handle formatting requests to the language server.
8314 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8315 let buffer_changes = buffer_changes.clone();
8316 move |_, _| {
8317 // When formatting is requested, trailing whitespace has already been stripped,
8318 // and the trailing newline has already been added.
8319 assert_eq!(
8320 &buffer_changes.lock()[1..],
8321 &[
8322 (
8323 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8324 "".into()
8325 ),
8326 (
8327 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8328 "".into()
8329 ),
8330 (
8331 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8332 "\n".into()
8333 ),
8334 ]
8335 );
8336
8337 // Insert blank lines between each line of the buffer.
8338 async move {
8339 Ok(Some(vec![
8340 lsp::TextEdit {
8341 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8342 new_text: "\n".into(),
8343 },
8344 lsp::TextEdit {
8345 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8346 new_text: "\n".into(),
8347 },
8348 ]))
8349 }
8350 }
8351 });
8352
8353 // After formatting the buffer, the trailing whitespace is stripped,
8354 // a newline is appended, and the edits provided by the language server
8355 // have been applied.
8356 format.await.unwrap();
8357 cx.assert_editor_state(
8358 &[
8359 "one", //
8360 "", //
8361 "twoˇ", //
8362 "", //
8363 "three", //
8364 "four", //
8365 "", //
8366 ]
8367 .join("\n"),
8368 );
8369
8370 // Undoing the formatting undoes the trailing whitespace removal, the
8371 // trailing newline, and the LSP edits.
8372 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8373 cx.assert_editor_state(
8374 &[
8375 "one ", //
8376 "twoˇ", //
8377 "three ", //
8378 "four", //
8379 ]
8380 .join("\n"),
8381 );
8382}
8383
8384#[gpui::test]
8385async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8386 cx: &mut TestAppContext,
8387) {
8388 init_test(cx, |_| {});
8389
8390 cx.update(|cx| {
8391 cx.update_global::<SettingsStore, _>(|settings, cx| {
8392 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8393 settings.auto_signature_help = Some(true);
8394 });
8395 });
8396 });
8397
8398 let mut cx = EditorLspTestContext::new_rust(
8399 lsp::ServerCapabilities {
8400 signature_help_provider: Some(lsp::SignatureHelpOptions {
8401 ..Default::default()
8402 }),
8403 ..Default::default()
8404 },
8405 cx,
8406 )
8407 .await;
8408
8409 let language = Language::new(
8410 LanguageConfig {
8411 name: "Rust".into(),
8412 brackets: BracketPairConfig {
8413 pairs: vec![
8414 BracketPair {
8415 start: "{".to_string(),
8416 end: "}".to_string(),
8417 close: true,
8418 surround: true,
8419 newline: true,
8420 },
8421 BracketPair {
8422 start: "(".to_string(),
8423 end: ")".to_string(),
8424 close: true,
8425 surround: true,
8426 newline: true,
8427 },
8428 BracketPair {
8429 start: "/*".to_string(),
8430 end: " */".to_string(),
8431 close: true,
8432 surround: true,
8433 newline: true,
8434 },
8435 BracketPair {
8436 start: "[".to_string(),
8437 end: "]".to_string(),
8438 close: false,
8439 surround: false,
8440 newline: true,
8441 },
8442 BracketPair {
8443 start: "\"".to_string(),
8444 end: "\"".to_string(),
8445 close: true,
8446 surround: true,
8447 newline: false,
8448 },
8449 BracketPair {
8450 start: "<".to_string(),
8451 end: ">".to_string(),
8452 close: false,
8453 surround: true,
8454 newline: true,
8455 },
8456 ],
8457 ..Default::default()
8458 },
8459 autoclose_before: "})]".to_string(),
8460 ..Default::default()
8461 },
8462 Some(tree_sitter_rust::LANGUAGE.into()),
8463 );
8464 let language = Arc::new(language);
8465
8466 cx.language_registry().add(language.clone());
8467 cx.update_buffer(|buffer, cx| {
8468 buffer.set_language(Some(language), cx);
8469 });
8470
8471 cx.set_state(
8472 &r#"
8473 fn main() {
8474 sampleˇ
8475 }
8476 "#
8477 .unindent(),
8478 );
8479
8480 cx.update_editor(|editor, window, cx| {
8481 editor.handle_input("(", window, cx);
8482 });
8483 cx.assert_editor_state(
8484 &"
8485 fn main() {
8486 sample(ˇ)
8487 }
8488 "
8489 .unindent(),
8490 );
8491
8492 let mocked_response = lsp::SignatureHelp {
8493 signatures: vec![lsp::SignatureInformation {
8494 label: "fn sample(param1: u8, param2: u8)".to_string(),
8495 documentation: None,
8496 parameters: Some(vec![
8497 lsp::ParameterInformation {
8498 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8499 documentation: None,
8500 },
8501 lsp::ParameterInformation {
8502 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8503 documentation: None,
8504 },
8505 ]),
8506 active_parameter: None,
8507 }],
8508 active_signature: Some(0),
8509 active_parameter: Some(0),
8510 };
8511 handle_signature_help_request(&mut cx, mocked_response).await;
8512
8513 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8514 .await;
8515
8516 cx.editor(|editor, _, _| {
8517 let signature_help_state = editor.signature_help_state.popover().cloned();
8518 assert_eq!(
8519 signature_help_state.unwrap().label,
8520 "param1: u8, param2: u8"
8521 );
8522 });
8523}
8524
8525#[gpui::test]
8526async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8527 init_test(cx, |_| {});
8528
8529 cx.update(|cx| {
8530 cx.update_global::<SettingsStore, _>(|settings, cx| {
8531 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8532 settings.auto_signature_help = Some(false);
8533 settings.show_signature_help_after_edits = Some(false);
8534 });
8535 });
8536 });
8537
8538 let mut cx = EditorLspTestContext::new_rust(
8539 lsp::ServerCapabilities {
8540 signature_help_provider: Some(lsp::SignatureHelpOptions {
8541 ..Default::default()
8542 }),
8543 ..Default::default()
8544 },
8545 cx,
8546 )
8547 .await;
8548
8549 let language = Language::new(
8550 LanguageConfig {
8551 name: "Rust".into(),
8552 brackets: BracketPairConfig {
8553 pairs: vec![
8554 BracketPair {
8555 start: "{".to_string(),
8556 end: "}".to_string(),
8557 close: true,
8558 surround: true,
8559 newline: true,
8560 },
8561 BracketPair {
8562 start: "(".to_string(),
8563 end: ")".to_string(),
8564 close: true,
8565 surround: true,
8566 newline: true,
8567 },
8568 BracketPair {
8569 start: "/*".to_string(),
8570 end: " */".to_string(),
8571 close: true,
8572 surround: true,
8573 newline: true,
8574 },
8575 BracketPair {
8576 start: "[".to_string(),
8577 end: "]".to_string(),
8578 close: false,
8579 surround: false,
8580 newline: true,
8581 },
8582 BracketPair {
8583 start: "\"".to_string(),
8584 end: "\"".to_string(),
8585 close: true,
8586 surround: true,
8587 newline: false,
8588 },
8589 BracketPair {
8590 start: "<".to_string(),
8591 end: ">".to_string(),
8592 close: false,
8593 surround: true,
8594 newline: true,
8595 },
8596 ],
8597 ..Default::default()
8598 },
8599 autoclose_before: "})]".to_string(),
8600 ..Default::default()
8601 },
8602 Some(tree_sitter_rust::LANGUAGE.into()),
8603 );
8604 let language = Arc::new(language);
8605
8606 cx.language_registry().add(language.clone());
8607 cx.update_buffer(|buffer, cx| {
8608 buffer.set_language(Some(language), cx);
8609 });
8610
8611 // Ensure that signature_help is not called when no signature help is enabled.
8612 cx.set_state(
8613 &r#"
8614 fn main() {
8615 sampleˇ
8616 }
8617 "#
8618 .unindent(),
8619 );
8620 cx.update_editor(|editor, window, cx| {
8621 editor.handle_input("(", window, cx);
8622 });
8623 cx.assert_editor_state(
8624 &"
8625 fn main() {
8626 sample(ˇ)
8627 }
8628 "
8629 .unindent(),
8630 );
8631 cx.editor(|editor, _, _| {
8632 assert!(editor.signature_help_state.task().is_none());
8633 });
8634
8635 let mocked_response = lsp::SignatureHelp {
8636 signatures: vec![lsp::SignatureInformation {
8637 label: "fn sample(param1: u8, param2: u8)".to_string(),
8638 documentation: None,
8639 parameters: Some(vec![
8640 lsp::ParameterInformation {
8641 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8642 documentation: None,
8643 },
8644 lsp::ParameterInformation {
8645 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8646 documentation: None,
8647 },
8648 ]),
8649 active_parameter: None,
8650 }],
8651 active_signature: Some(0),
8652 active_parameter: Some(0),
8653 };
8654
8655 // Ensure that signature_help is called when enabled afte edits
8656 cx.update(|_, cx| {
8657 cx.update_global::<SettingsStore, _>(|settings, cx| {
8658 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8659 settings.auto_signature_help = Some(false);
8660 settings.show_signature_help_after_edits = Some(true);
8661 });
8662 });
8663 });
8664 cx.set_state(
8665 &r#"
8666 fn main() {
8667 sampleˇ
8668 }
8669 "#
8670 .unindent(),
8671 );
8672 cx.update_editor(|editor, window, cx| {
8673 editor.handle_input("(", window, cx);
8674 });
8675 cx.assert_editor_state(
8676 &"
8677 fn main() {
8678 sample(ˇ)
8679 }
8680 "
8681 .unindent(),
8682 );
8683 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8684 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8685 .await;
8686 cx.update_editor(|editor, _, _| {
8687 let signature_help_state = editor.signature_help_state.popover().cloned();
8688 assert!(signature_help_state.is_some());
8689 assert_eq!(
8690 signature_help_state.unwrap().label,
8691 "param1: u8, param2: u8"
8692 );
8693 editor.signature_help_state = SignatureHelpState::default();
8694 });
8695
8696 // Ensure that signature_help is called when auto signature help override is enabled
8697 cx.update(|_, cx| {
8698 cx.update_global::<SettingsStore, _>(|settings, cx| {
8699 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8700 settings.auto_signature_help = Some(true);
8701 settings.show_signature_help_after_edits = Some(false);
8702 });
8703 });
8704 });
8705 cx.set_state(
8706 &r#"
8707 fn main() {
8708 sampleˇ
8709 }
8710 "#
8711 .unindent(),
8712 );
8713 cx.update_editor(|editor, window, cx| {
8714 editor.handle_input("(", window, cx);
8715 });
8716 cx.assert_editor_state(
8717 &"
8718 fn main() {
8719 sample(ˇ)
8720 }
8721 "
8722 .unindent(),
8723 );
8724 handle_signature_help_request(&mut cx, mocked_response).await;
8725 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8726 .await;
8727 cx.editor(|editor, _, _| {
8728 let signature_help_state = editor.signature_help_state.popover().cloned();
8729 assert!(signature_help_state.is_some());
8730 assert_eq!(
8731 signature_help_state.unwrap().label,
8732 "param1: u8, param2: u8"
8733 );
8734 });
8735}
8736
8737#[gpui::test]
8738async fn test_signature_help(cx: &mut TestAppContext) {
8739 init_test(cx, |_| {});
8740 cx.update(|cx| {
8741 cx.update_global::<SettingsStore, _>(|settings, cx| {
8742 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8743 settings.auto_signature_help = Some(true);
8744 });
8745 });
8746 });
8747
8748 let mut cx = EditorLspTestContext::new_rust(
8749 lsp::ServerCapabilities {
8750 signature_help_provider: Some(lsp::SignatureHelpOptions {
8751 ..Default::default()
8752 }),
8753 ..Default::default()
8754 },
8755 cx,
8756 )
8757 .await;
8758
8759 // A test that directly calls `show_signature_help`
8760 cx.update_editor(|editor, window, cx| {
8761 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8762 });
8763
8764 let mocked_response = lsp::SignatureHelp {
8765 signatures: vec![lsp::SignatureInformation {
8766 label: "fn sample(param1: u8, param2: u8)".to_string(),
8767 documentation: None,
8768 parameters: Some(vec![
8769 lsp::ParameterInformation {
8770 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8771 documentation: None,
8772 },
8773 lsp::ParameterInformation {
8774 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8775 documentation: None,
8776 },
8777 ]),
8778 active_parameter: None,
8779 }],
8780 active_signature: Some(0),
8781 active_parameter: Some(0),
8782 };
8783 handle_signature_help_request(&mut cx, mocked_response).await;
8784
8785 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8786 .await;
8787
8788 cx.editor(|editor, _, _| {
8789 let signature_help_state = editor.signature_help_state.popover().cloned();
8790 assert!(signature_help_state.is_some());
8791 assert_eq!(
8792 signature_help_state.unwrap().label,
8793 "param1: u8, param2: u8"
8794 );
8795 });
8796
8797 // When exiting outside from inside the brackets, `signature_help` is closed.
8798 cx.set_state(indoc! {"
8799 fn main() {
8800 sample(ˇ);
8801 }
8802
8803 fn sample(param1: u8, param2: u8) {}
8804 "});
8805
8806 cx.update_editor(|editor, window, cx| {
8807 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8808 });
8809
8810 let mocked_response = lsp::SignatureHelp {
8811 signatures: Vec::new(),
8812 active_signature: None,
8813 active_parameter: None,
8814 };
8815 handle_signature_help_request(&mut cx, mocked_response).await;
8816
8817 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8818 .await;
8819
8820 cx.editor(|editor, _, _| {
8821 assert!(!editor.signature_help_state.is_shown());
8822 });
8823
8824 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8825 cx.set_state(indoc! {"
8826 fn main() {
8827 sample(ˇ);
8828 }
8829
8830 fn sample(param1: u8, param2: u8) {}
8831 "});
8832
8833 let mocked_response = lsp::SignatureHelp {
8834 signatures: vec![lsp::SignatureInformation {
8835 label: "fn sample(param1: u8, param2: u8)".to_string(),
8836 documentation: None,
8837 parameters: Some(vec![
8838 lsp::ParameterInformation {
8839 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8840 documentation: None,
8841 },
8842 lsp::ParameterInformation {
8843 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8844 documentation: None,
8845 },
8846 ]),
8847 active_parameter: None,
8848 }],
8849 active_signature: Some(0),
8850 active_parameter: Some(0),
8851 };
8852 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8853 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8854 .await;
8855 cx.editor(|editor, _, _| {
8856 assert!(editor.signature_help_state.is_shown());
8857 });
8858
8859 // Restore the popover with more parameter input
8860 cx.set_state(indoc! {"
8861 fn main() {
8862 sample(param1, param2ˇ);
8863 }
8864
8865 fn sample(param1: u8, param2: u8) {}
8866 "});
8867
8868 let mocked_response = lsp::SignatureHelp {
8869 signatures: vec![lsp::SignatureInformation {
8870 label: "fn sample(param1: u8, param2: u8)".to_string(),
8871 documentation: None,
8872 parameters: Some(vec![
8873 lsp::ParameterInformation {
8874 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8875 documentation: None,
8876 },
8877 lsp::ParameterInformation {
8878 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8879 documentation: None,
8880 },
8881 ]),
8882 active_parameter: None,
8883 }],
8884 active_signature: Some(0),
8885 active_parameter: Some(1),
8886 };
8887 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8888 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8889 .await;
8890
8891 // When selecting a range, the popover is gone.
8892 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8893 cx.update_editor(|editor, window, cx| {
8894 editor.change_selections(None, window, cx, |s| {
8895 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8896 })
8897 });
8898 cx.assert_editor_state(indoc! {"
8899 fn main() {
8900 sample(param1, «ˇparam2»);
8901 }
8902
8903 fn sample(param1: u8, param2: u8) {}
8904 "});
8905 cx.editor(|editor, _, _| {
8906 assert!(!editor.signature_help_state.is_shown());
8907 });
8908
8909 // When unselecting again, the popover is back if within the brackets.
8910 cx.update_editor(|editor, window, cx| {
8911 editor.change_selections(None, window, cx, |s| {
8912 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8913 })
8914 });
8915 cx.assert_editor_state(indoc! {"
8916 fn main() {
8917 sample(param1, ˇparam2);
8918 }
8919
8920 fn sample(param1: u8, param2: u8) {}
8921 "});
8922 handle_signature_help_request(&mut cx, mocked_response).await;
8923 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8924 .await;
8925 cx.editor(|editor, _, _| {
8926 assert!(editor.signature_help_state.is_shown());
8927 });
8928
8929 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8930 cx.update_editor(|editor, window, cx| {
8931 editor.change_selections(None, window, cx, |s| {
8932 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8933 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8934 })
8935 });
8936 cx.assert_editor_state(indoc! {"
8937 fn main() {
8938 sample(param1, ˇparam2);
8939 }
8940
8941 fn sample(param1: u8, param2: u8) {}
8942 "});
8943
8944 let mocked_response = lsp::SignatureHelp {
8945 signatures: vec![lsp::SignatureInformation {
8946 label: "fn sample(param1: u8, param2: u8)".to_string(),
8947 documentation: None,
8948 parameters: Some(vec![
8949 lsp::ParameterInformation {
8950 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8951 documentation: None,
8952 },
8953 lsp::ParameterInformation {
8954 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8955 documentation: None,
8956 },
8957 ]),
8958 active_parameter: None,
8959 }],
8960 active_signature: Some(0),
8961 active_parameter: Some(1),
8962 };
8963 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8964 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8965 .await;
8966 cx.update_editor(|editor, _, cx| {
8967 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8968 });
8969 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8970 .await;
8971 cx.update_editor(|editor, window, cx| {
8972 editor.change_selections(None, window, cx, |s| {
8973 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8974 })
8975 });
8976 cx.assert_editor_state(indoc! {"
8977 fn main() {
8978 sample(param1, «ˇparam2»);
8979 }
8980
8981 fn sample(param1: u8, param2: u8) {}
8982 "});
8983 cx.update_editor(|editor, window, cx| {
8984 editor.change_selections(None, window, cx, |s| {
8985 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8986 })
8987 });
8988 cx.assert_editor_state(indoc! {"
8989 fn main() {
8990 sample(param1, ˇparam2);
8991 }
8992
8993 fn sample(param1: u8, param2: u8) {}
8994 "});
8995 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8996 .await;
8997}
8998
8999#[gpui::test]
9000async fn test_completion(cx: &mut TestAppContext) {
9001 init_test(cx, |_| {});
9002
9003 let mut cx = EditorLspTestContext::new_rust(
9004 lsp::ServerCapabilities {
9005 completion_provider: Some(lsp::CompletionOptions {
9006 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9007 resolve_provider: Some(true),
9008 ..Default::default()
9009 }),
9010 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9011 ..Default::default()
9012 },
9013 cx,
9014 )
9015 .await;
9016 let counter = Arc::new(AtomicUsize::new(0));
9017
9018 cx.set_state(indoc! {"
9019 oneˇ
9020 two
9021 three
9022 "});
9023 cx.simulate_keystroke(".");
9024 handle_completion_request(
9025 &mut cx,
9026 indoc! {"
9027 one.|<>
9028 two
9029 three
9030 "},
9031 vec!["first_completion", "second_completion"],
9032 counter.clone(),
9033 )
9034 .await;
9035 cx.condition(|editor, _| editor.context_menu_visible())
9036 .await;
9037 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9038
9039 let _handler = handle_signature_help_request(
9040 &mut cx,
9041 lsp::SignatureHelp {
9042 signatures: vec![lsp::SignatureInformation {
9043 label: "test signature".to_string(),
9044 documentation: None,
9045 parameters: Some(vec![lsp::ParameterInformation {
9046 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9047 documentation: None,
9048 }]),
9049 active_parameter: None,
9050 }],
9051 active_signature: None,
9052 active_parameter: None,
9053 },
9054 );
9055 cx.update_editor(|editor, window, cx| {
9056 assert!(
9057 !editor.signature_help_state.is_shown(),
9058 "No signature help was called for"
9059 );
9060 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9061 });
9062 cx.run_until_parked();
9063 cx.update_editor(|editor, _, _| {
9064 assert!(
9065 !editor.signature_help_state.is_shown(),
9066 "No signature help should be shown when completions menu is open"
9067 );
9068 });
9069
9070 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9071 editor.context_menu_next(&Default::default(), window, cx);
9072 editor
9073 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9074 .unwrap()
9075 });
9076 cx.assert_editor_state(indoc! {"
9077 one.second_completionˇ
9078 two
9079 three
9080 "});
9081
9082 handle_resolve_completion_request(
9083 &mut cx,
9084 Some(vec![
9085 (
9086 //This overlaps with the primary completion edit which is
9087 //misbehavior from the LSP spec, test that we filter it out
9088 indoc! {"
9089 one.second_ˇcompletion
9090 two
9091 threeˇ
9092 "},
9093 "overlapping additional edit",
9094 ),
9095 (
9096 indoc! {"
9097 one.second_completion
9098 two
9099 threeˇ
9100 "},
9101 "\nadditional edit",
9102 ),
9103 ]),
9104 )
9105 .await;
9106 apply_additional_edits.await.unwrap();
9107 cx.assert_editor_state(indoc! {"
9108 one.second_completionˇ
9109 two
9110 three
9111 additional edit
9112 "});
9113
9114 cx.set_state(indoc! {"
9115 one.second_completion
9116 twoˇ
9117 threeˇ
9118 additional edit
9119 "});
9120 cx.simulate_keystroke(" ");
9121 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9122 cx.simulate_keystroke("s");
9123 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9124
9125 cx.assert_editor_state(indoc! {"
9126 one.second_completion
9127 two sˇ
9128 three sˇ
9129 additional edit
9130 "});
9131 handle_completion_request(
9132 &mut cx,
9133 indoc! {"
9134 one.second_completion
9135 two s
9136 three <s|>
9137 additional edit
9138 "},
9139 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9140 counter.clone(),
9141 )
9142 .await;
9143 cx.condition(|editor, _| editor.context_menu_visible())
9144 .await;
9145 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9146
9147 cx.simulate_keystroke("i");
9148
9149 handle_completion_request(
9150 &mut cx,
9151 indoc! {"
9152 one.second_completion
9153 two si
9154 three <si|>
9155 additional edit
9156 "},
9157 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9158 counter.clone(),
9159 )
9160 .await;
9161 cx.condition(|editor, _| editor.context_menu_visible())
9162 .await;
9163 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9164
9165 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9166 editor
9167 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9168 .unwrap()
9169 });
9170 cx.assert_editor_state(indoc! {"
9171 one.second_completion
9172 two sixth_completionˇ
9173 three sixth_completionˇ
9174 additional edit
9175 "});
9176
9177 apply_additional_edits.await.unwrap();
9178
9179 update_test_language_settings(&mut cx, |settings| {
9180 settings.defaults.show_completions_on_input = Some(false);
9181 });
9182 cx.set_state("editorˇ");
9183 cx.simulate_keystroke(".");
9184 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9185 cx.simulate_keystroke("c");
9186 cx.simulate_keystroke("l");
9187 cx.simulate_keystroke("o");
9188 cx.assert_editor_state("editor.cloˇ");
9189 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9190 cx.update_editor(|editor, window, cx| {
9191 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9192 });
9193 handle_completion_request(
9194 &mut cx,
9195 "editor.<clo|>",
9196 vec!["close", "clobber"],
9197 counter.clone(),
9198 )
9199 .await;
9200 cx.condition(|editor, _| editor.context_menu_visible())
9201 .await;
9202 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9203
9204 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9205 editor
9206 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9207 .unwrap()
9208 });
9209 cx.assert_editor_state("editor.closeˇ");
9210 handle_resolve_completion_request(&mut cx, None).await;
9211 apply_additional_edits.await.unwrap();
9212}
9213
9214#[gpui::test]
9215async fn test_words_completion(cx: &mut TestAppContext) {
9216 let lsp_fetch_timeout_ms = 10;
9217 init_test(cx, |language_settings| {
9218 language_settings.defaults.completions = Some(CompletionSettings {
9219 words: WordsCompletionMode::Fallback,
9220 lsp: true,
9221 lsp_fetch_timeout_ms: 10,
9222 });
9223 });
9224
9225 let mut cx = EditorLspTestContext::new_rust(
9226 lsp::ServerCapabilities {
9227 completion_provider: Some(lsp::CompletionOptions {
9228 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9229 ..lsp::CompletionOptions::default()
9230 }),
9231 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9232 ..lsp::ServerCapabilities::default()
9233 },
9234 cx,
9235 )
9236 .await;
9237
9238 let throttle_completions = Arc::new(AtomicBool::new(false));
9239
9240 let lsp_throttle_completions = throttle_completions.clone();
9241 let _completion_requests_handler =
9242 cx.lsp
9243 .server
9244 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9245 let lsp_throttle_completions = lsp_throttle_completions.clone();
9246 async move {
9247 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9248 cx.background_executor()
9249 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9250 .await;
9251 }
9252 Ok(Some(lsp::CompletionResponse::Array(vec![
9253 lsp::CompletionItem {
9254 label: "first".into(),
9255 ..lsp::CompletionItem::default()
9256 },
9257 lsp::CompletionItem {
9258 label: "last".into(),
9259 ..lsp::CompletionItem::default()
9260 },
9261 ])))
9262 }
9263 });
9264
9265 cx.set_state(indoc! {"
9266 oneˇ
9267 two
9268 three
9269 "});
9270 cx.simulate_keystroke(".");
9271 cx.executor().run_until_parked();
9272 cx.condition(|editor, _| editor.context_menu_visible())
9273 .await;
9274 cx.update_editor(|editor, window, cx| {
9275 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9276 {
9277 assert_eq!(
9278 completion_menu_entries(&menu),
9279 &["first", "last"],
9280 "When LSP server is fast to reply, no fallback word completions are used"
9281 );
9282 } else {
9283 panic!("expected completion menu to be open");
9284 }
9285 editor.cancel(&Cancel, window, cx);
9286 });
9287 cx.executor().run_until_parked();
9288 cx.condition(|editor, _| !editor.context_menu_visible())
9289 .await;
9290
9291 throttle_completions.store(true, atomic::Ordering::Release);
9292 cx.simulate_keystroke(".");
9293 cx.executor()
9294 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9295 cx.executor().run_until_parked();
9296 cx.condition(|editor, _| editor.context_menu_visible())
9297 .await;
9298 cx.update_editor(|editor, _, _| {
9299 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9300 {
9301 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9302 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9303 } else {
9304 panic!("expected completion menu to be open");
9305 }
9306 });
9307}
9308
9309#[gpui::test]
9310async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9311 init_test(cx, |language_settings| {
9312 language_settings.defaults.completions = Some(CompletionSettings {
9313 words: WordsCompletionMode::Enabled,
9314 lsp: true,
9315 lsp_fetch_timeout_ms: 0,
9316 });
9317 });
9318
9319 let mut cx = EditorLspTestContext::new_rust(
9320 lsp::ServerCapabilities {
9321 completion_provider: Some(lsp::CompletionOptions {
9322 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9323 ..lsp::CompletionOptions::default()
9324 }),
9325 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9326 ..lsp::ServerCapabilities::default()
9327 },
9328 cx,
9329 )
9330 .await;
9331
9332 let _completion_requests_handler =
9333 cx.lsp
9334 .server
9335 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9336 Ok(Some(lsp::CompletionResponse::Array(vec![
9337 lsp::CompletionItem {
9338 label: "first".into(),
9339 ..lsp::CompletionItem::default()
9340 },
9341 lsp::CompletionItem {
9342 label: "last".into(),
9343 ..lsp::CompletionItem::default()
9344 },
9345 ])))
9346 });
9347
9348 cx.set_state(indoc! {"ˇ
9349 first
9350 last
9351 second
9352 "});
9353 cx.simulate_keystroke(".");
9354 cx.executor().run_until_parked();
9355 cx.condition(|editor, _| editor.context_menu_visible())
9356 .await;
9357 cx.update_editor(|editor, window, cx| {
9358 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9359 {
9360 assert_eq!(
9361 completion_menu_entries(&menu),
9362 &["first", "last", "second"],
9363 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9364 );
9365 } else {
9366 panic!("expected completion menu to be open");
9367 }
9368 editor.cancel(&Cancel, window, cx);
9369 });
9370}
9371
9372#[gpui::test]
9373async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
9374 init_test(cx, |language_settings| {
9375 language_settings.defaults.completions = Some(CompletionSettings {
9376 words: WordsCompletionMode::Fallback,
9377 lsp: false,
9378 lsp_fetch_timeout_ms: 0,
9379 });
9380 });
9381
9382 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9383
9384 cx.set_state(indoc! {"ˇ
9385 0_usize
9386 let
9387 33
9388 4.5f32
9389 "});
9390 cx.update_editor(|editor, window, cx| {
9391 editor.show_completions(&ShowCompletions::default(), window, cx);
9392 });
9393 cx.executor().run_until_parked();
9394 cx.condition(|editor, _| editor.context_menu_visible())
9395 .await;
9396 cx.update_editor(|editor, window, cx| {
9397 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9398 {
9399 assert_eq!(
9400 completion_menu_entries(&menu),
9401 &["let"],
9402 "With no digits in the completion query, no digits should be in the word completions"
9403 );
9404 } else {
9405 panic!("expected completion menu to be open");
9406 }
9407 editor.cancel(&Cancel, window, cx);
9408 });
9409
9410 cx.set_state(indoc! {"3ˇ
9411 0_usize
9412 let
9413 3
9414 33.35f32
9415 "});
9416 cx.update_editor(|editor, window, cx| {
9417 editor.show_completions(&ShowCompletions::default(), window, cx);
9418 });
9419 cx.executor().run_until_parked();
9420 cx.condition(|editor, _| editor.context_menu_visible())
9421 .await;
9422 cx.update_editor(|editor, _, _| {
9423 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9424 {
9425 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
9426 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
9427 } else {
9428 panic!("expected completion menu to be open");
9429 }
9430 });
9431}
9432
9433#[gpui::test]
9434async fn test_multiline_completion(cx: &mut TestAppContext) {
9435 init_test(cx, |_| {});
9436
9437 let fs = FakeFs::new(cx.executor());
9438 fs.insert_tree(
9439 path!("/a"),
9440 json!({
9441 "main.ts": "a",
9442 }),
9443 )
9444 .await;
9445
9446 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9447 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9448 let typescript_language = Arc::new(Language::new(
9449 LanguageConfig {
9450 name: "TypeScript".into(),
9451 matcher: LanguageMatcher {
9452 path_suffixes: vec!["ts".to_string()],
9453 ..LanguageMatcher::default()
9454 },
9455 line_comments: vec!["// ".into()],
9456 ..LanguageConfig::default()
9457 },
9458 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9459 ));
9460 language_registry.add(typescript_language.clone());
9461 let mut fake_servers = language_registry.register_fake_lsp(
9462 "TypeScript",
9463 FakeLspAdapter {
9464 capabilities: lsp::ServerCapabilities {
9465 completion_provider: Some(lsp::CompletionOptions {
9466 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9467 ..lsp::CompletionOptions::default()
9468 }),
9469 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9470 ..lsp::ServerCapabilities::default()
9471 },
9472 // Emulate vtsls label generation
9473 label_for_completion: Some(Box::new(|item, _| {
9474 let text = if let Some(description) = item
9475 .label_details
9476 .as_ref()
9477 .and_then(|label_details| label_details.description.as_ref())
9478 {
9479 format!("{} {}", item.label, description)
9480 } else if let Some(detail) = &item.detail {
9481 format!("{} {}", item.label, detail)
9482 } else {
9483 item.label.clone()
9484 };
9485 let len = text.len();
9486 Some(language::CodeLabel {
9487 text,
9488 runs: Vec::new(),
9489 filter_range: 0..len,
9490 })
9491 })),
9492 ..FakeLspAdapter::default()
9493 },
9494 );
9495 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9496 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9497 let worktree_id = workspace
9498 .update(cx, |workspace, _window, cx| {
9499 workspace.project().update(cx, |project, cx| {
9500 project.worktrees(cx).next().unwrap().read(cx).id()
9501 })
9502 })
9503 .unwrap();
9504 let _buffer = project
9505 .update(cx, |project, cx| {
9506 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9507 })
9508 .await
9509 .unwrap();
9510 let editor = workspace
9511 .update(cx, |workspace, window, cx| {
9512 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9513 })
9514 .unwrap()
9515 .await
9516 .unwrap()
9517 .downcast::<Editor>()
9518 .unwrap();
9519 let fake_server = fake_servers.next().await.unwrap();
9520
9521 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9522 let multiline_label_2 = "a\nb\nc\n";
9523 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9524 let multiline_description = "d\ne\nf\n";
9525 let multiline_detail_2 = "g\nh\ni\n";
9526
9527 let mut completion_handle =
9528 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9529 Ok(Some(lsp::CompletionResponse::Array(vec![
9530 lsp::CompletionItem {
9531 label: multiline_label.to_string(),
9532 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9533 range: lsp::Range {
9534 start: lsp::Position {
9535 line: params.text_document_position.position.line,
9536 character: params.text_document_position.position.character,
9537 },
9538 end: lsp::Position {
9539 line: params.text_document_position.position.line,
9540 character: params.text_document_position.position.character,
9541 },
9542 },
9543 new_text: "new_text_1".to_string(),
9544 })),
9545 ..lsp::CompletionItem::default()
9546 },
9547 lsp::CompletionItem {
9548 label: "single line label 1".to_string(),
9549 detail: Some(multiline_detail.to_string()),
9550 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9551 range: lsp::Range {
9552 start: lsp::Position {
9553 line: params.text_document_position.position.line,
9554 character: params.text_document_position.position.character,
9555 },
9556 end: lsp::Position {
9557 line: params.text_document_position.position.line,
9558 character: params.text_document_position.position.character,
9559 },
9560 },
9561 new_text: "new_text_2".to_string(),
9562 })),
9563 ..lsp::CompletionItem::default()
9564 },
9565 lsp::CompletionItem {
9566 label: "single line label 2".to_string(),
9567 label_details: Some(lsp::CompletionItemLabelDetails {
9568 description: Some(multiline_description.to_string()),
9569 detail: None,
9570 }),
9571 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9572 range: lsp::Range {
9573 start: lsp::Position {
9574 line: params.text_document_position.position.line,
9575 character: params.text_document_position.position.character,
9576 },
9577 end: lsp::Position {
9578 line: params.text_document_position.position.line,
9579 character: params.text_document_position.position.character,
9580 },
9581 },
9582 new_text: "new_text_2".to_string(),
9583 })),
9584 ..lsp::CompletionItem::default()
9585 },
9586 lsp::CompletionItem {
9587 label: multiline_label_2.to_string(),
9588 detail: Some(multiline_detail_2.to_string()),
9589 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9590 range: lsp::Range {
9591 start: lsp::Position {
9592 line: params.text_document_position.position.line,
9593 character: params.text_document_position.position.character,
9594 },
9595 end: lsp::Position {
9596 line: params.text_document_position.position.line,
9597 character: params.text_document_position.position.character,
9598 },
9599 },
9600 new_text: "new_text_3".to_string(),
9601 })),
9602 ..lsp::CompletionItem::default()
9603 },
9604 lsp::CompletionItem {
9605 label: "Label with many spaces and \t but without newlines".to_string(),
9606 detail: Some(
9607 "Details with many spaces and \t but without newlines".to_string(),
9608 ),
9609 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9610 range: lsp::Range {
9611 start: lsp::Position {
9612 line: params.text_document_position.position.line,
9613 character: params.text_document_position.position.character,
9614 },
9615 end: lsp::Position {
9616 line: params.text_document_position.position.line,
9617 character: params.text_document_position.position.character,
9618 },
9619 },
9620 new_text: "new_text_4".to_string(),
9621 })),
9622 ..lsp::CompletionItem::default()
9623 },
9624 ])))
9625 });
9626
9627 editor.update_in(cx, |editor, window, cx| {
9628 cx.focus_self(window);
9629 editor.move_to_end(&MoveToEnd, window, cx);
9630 editor.handle_input(".", window, cx);
9631 });
9632 cx.run_until_parked();
9633 completion_handle.next().await.unwrap();
9634
9635 editor.update(cx, |editor, _| {
9636 assert!(editor.context_menu_visible());
9637 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9638 {
9639 let completion_labels = menu
9640 .completions
9641 .borrow()
9642 .iter()
9643 .map(|c| c.label.text.clone())
9644 .collect::<Vec<_>>();
9645 assert_eq!(
9646 completion_labels,
9647 &[
9648 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9649 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9650 "single line label 2 d e f ",
9651 "a b c g h i ",
9652 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9653 ],
9654 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9655 );
9656
9657 for completion in menu
9658 .completions
9659 .borrow()
9660 .iter() {
9661 assert_eq!(
9662 completion.label.filter_range,
9663 0..completion.label.text.len(),
9664 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9665 );
9666 }
9667
9668 } else {
9669 panic!("expected completion menu to be open");
9670 }
9671 });
9672}
9673
9674#[gpui::test]
9675async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9676 init_test(cx, |_| {});
9677 let mut cx = EditorLspTestContext::new_rust(
9678 lsp::ServerCapabilities {
9679 completion_provider: Some(lsp::CompletionOptions {
9680 trigger_characters: Some(vec![".".to_string()]),
9681 ..Default::default()
9682 }),
9683 ..Default::default()
9684 },
9685 cx,
9686 )
9687 .await;
9688 cx.lsp
9689 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9690 Ok(Some(lsp::CompletionResponse::Array(vec![
9691 lsp::CompletionItem {
9692 label: "first".into(),
9693 ..Default::default()
9694 },
9695 lsp::CompletionItem {
9696 label: "last".into(),
9697 ..Default::default()
9698 },
9699 ])))
9700 });
9701 cx.set_state("variableˇ");
9702 cx.simulate_keystroke(".");
9703 cx.executor().run_until_parked();
9704
9705 cx.update_editor(|editor, _, _| {
9706 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9707 {
9708 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9709 } else {
9710 panic!("expected completion menu to be open");
9711 }
9712 });
9713
9714 cx.update_editor(|editor, window, cx| {
9715 editor.move_page_down(&MovePageDown::default(), window, cx);
9716 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9717 {
9718 assert!(
9719 menu.selected_item == 1,
9720 "expected PageDown to select the last item from the context menu"
9721 );
9722 } else {
9723 panic!("expected completion menu to stay open after PageDown");
9724 }
9725 });
9726
9727 cx.update_editor(|editor, window, cx| {
9728 editor.move_page_up(&MovePageUp::default(), window, cx);
9729 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9730 {
9731 assert!(
9732 menu.selected_item == 0,
9733 "expected PageUp to select the first item from the context menu"
9734 );
9735 } else {
9736 panic!("expected completion menu to stay open after PageUp");
9737 }
9738 });
9739}
9740
9741#[gpui::test]
9742async fn test_completion_sort(cx: &mut TestAppContext) {
9743 init_test(cx, |_| {});
9744 let mut cx = EditorLspTestContext::new_rust(
9745 lsp::ServerCapabilities {
9746 completion_provider: Some(lsp::CompletionOptions {
9747 trigger_characters: Some(vec![".".to_string()]),
9748 ..Default::default()
9749 }),
9750 ..Default::default()
9751 },
9752 cx,
9753 )
9754 .await;
9755 cx.lsp
9756 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9757 Ok(Some(lsp::CompletionResponse::Array(vec![
9758 lsp::CompletionItem {
9759 label: "Range".into(),
9760 sort_text: Some("a".into()),
9761 ..Default::default()
9762 },
9763 lsp::CompletionItem {
9764 label: "r".into(),
9765 sort_text: Some("b".into()),
9766 ..Default::default()
9767 },
9768 lsp::CompletionItem {
9769 label: "ret".into(),
9770 sort_text: Some("c".into()),
9771 ..Default::default()
9772 },
9773 lsp::CompletionItem {
9774 label: "return".into(),
9775 sort_text: Some("d".into()),
9776 ..Default::default()
9777 },
9778 lsp::CompletionItem {
9779 label: "slice".into(),
9780 sort_text: Some("d".into()),
9781 ..Default::default()
9782 },
9783 ])))
9784 });
9785 cx.set_state("rˇ");
9786 cx.executor().run_until_parked();
9787 cx.update_editor(|editor, window, cx| {
9788 editor.show_completions(
9789 &ShowCompletions {
9790 trigger: Some("r".into()),
9791 },
9792 window,
9793 cx,
9794 );
9795 });
9796 cx.executor().run_until_parked();
9797
9798 cx.update_editor(|editor, _, _| {
9799 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9800 {
9801 assert_eq!(
9802 completion_menu_entries(&menu),
9803 &["r", "ret", "Range", "return"]
9804 );
9805 } else {
9806 panic!("expected completion menu to be open");
9807 }
9808 });
9809}
9810
9811#[gpui::test]
9812async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9813 init_test(cx, |_| {});
9814
9815 let mut cx = EditorLspTestContext::new_rust(
9816 lsp::ServerCapabilities {
9817 completion_provider: Some(lsp::CompletionOptions {
9818 trigger_characters: Some(vec![".".to_string()]),
9819 resolve_provider: Some(true),
9820 ..Default::default()
9821 }),
9822 ..Default::default()
9823 },
9824 cx,
9825 )
9826 .await;
9827
9828 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9829 cx.simulate_keystroke(".");
9830 let completion_item = lsp::CompletionItem {
9831 label: "Some".into(),
9832 kind: Some(lsp::CompletionItemKind::SNIPPET),
9833 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9834 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9835 kind: lsp::MarkupKind::Markdown,
9836 value: "```rust\nSome(2)\n```".to_string(),
9837 })),
9838 deprecated: Some(false),
9839 sort_text: Some("Some".to_string()),
9840 filter_text: Some("Some".to_string()),
9841 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9842 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9843 range: lsp::Range {
9844 start: lsp::Position {
9845 line: 0,
9846 character: 22,
9847 },
9848 end: lsp::Position {
9849 line: 0,
9850 character: 22,
9851 },
9852 },
9853 new_text: "Some(2)".to_string(),
9854 })),
9855 additional_text_edits: Some(vec![lsp::TextEdit {
9856 range: lsp::Range {
9857 start: lsp::Position {
9858 line: 0,
9859 character: 20,
9860 },
9861 end: lsp::Position {
9862 line: 0,
9863 character: 22,
9864 },
9865 },
9866 new_text: "".to_string(),
9867 }]),
9868 ..Default::default()
9869 };
9870
9871 let closure_completion_item = completion_item.clone();
9872 let counter = Arc::new(AtomicUsize::new(0));
9873 let counter_clone = counter.clone();
9874 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9875 let task_completion_item = closure_completion_item.clone();
9876 counter_clone.fetch_add(1, atomic::Ordering::Release);
9877 async move {
9878 Ok(Some(lsp::CompletionResponse::Array(vec![
9879 task_completion_item,
9880 ])))
9881 }
9882 });
9883
9884 cx.condition(|editor, _| editor.context_menu_visible())
9885 .await;
9886 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9887 assert!(request.next().await.is_some());
9888 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9889
9890 cx.simulate_keystroke("S");
9891 cx.simulate_keystroke("o");
9892 cx.simulate_keystroke("m");
9893 cx.condition(|editor, _| editor.context_menu_visible())
9894 .await;
9895 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9896 assert!(request.next().await.is_some());
9897 assert!(request.next().await.is_some());
9898 assert!(request.next().await.is_some());
9899 request.close();
9900 assert!(request.next().await.is_none());
9901 assert_eq!(
9902 counter.load(atomic::Ordering::Acquire),
9903 4,
9904 "With the completions menu open, only one LSP request should happen per input"
9905 );
9906}
9907
9908#[gpui::test]
9909async fn test_toggle_comment(cx: &mut TestAppContext) {
9910 init_test(cx, |_| {});
9911 let mut cx = EditorTestContext::new(cx).await;
9912 let language = Arc::new(Language::new(
9913 LanguageConfig {
9914 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9915 ..Default::default()
9916 },
9917 Some(tree_sitter_rust::LANGUAGE.into()),
9918 ));
9919 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9920
9921 // If multiple selections intersect a line, the line is only toggled once.
9922 cx.set_state(indoc! {"
9923 fn a() {
9924 «//b();
9925 ˇ»// «c();
9926 //ˇ» d();
9927 }
9928 "});
9929
9930 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9931
9932 cx.assert_editor_state(indoc! {"
9933 fn a() {
9934 «b();
9935 c();
9936 ˇ» d();
9937 }
9938 "});
9939
9940 // The comment prefix is inserted at the same column for every line in a
9941 // selection.
9942 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9943
9944 cx.assert_editor_state(indoc! {"
9945 fn a() {
9946 // «b();
9947 // c();
9948 ˇ»// d();
9949 }
9950 "});
9951
9952 // If a selection ends at the beginning of a line, that line is not toggled.
9953 cx.set_selections_state(indoc! {"
9954 fn a() {
9955 // b();
9956 «// c();
9957 ˇ» // d();
9958 }
9959 "});
9960
9961 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9962
9963 cx.assert_editor_state(indoc! {"
9964 fn a() {
9965 // b();
9966 «c();
9967 ˇ» // d();
9968 }
9969 "});
9970
9971 // If a selection span a single line and is empty, the line is toggled.
9972 cx.set_state(indoc! {"
9973 fn a() {
9974 a();
9975 b();
9976 ˇ
9977 }
9978 "});
9979
9980 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9981
9982 cx.assert_editor_state(indoc! {"
9983 fn a() {
9984 a();
9985 b();
9986 //•ˇ
9987 }
9988 "});
9989
9990 // If a selection span multiple lines, empty lines are not toggled.
9991 cx.set_state(indoc! {"
9992 fn a() {
9993 «a();
9994
9995 c();ˇ»
9996 }
9997 "});
9998
9999 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10000
10001 cx.assert_editor_state(indoc! {"
10002 fn a() {
10003 // «a();
10004
10005 // c();ˇ»
10006 }
10007 "});
10008
10009 // If a selection includes multiple comment prefixes, all lines are uncommented.
10010 cx.set_state(indoc! {"
10011 fn a() {
10012 «// a();
10013 /// b();
10014 //! c();ˇ»
10015 }
10016 "});
10017
10018 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10019
10020 cx.assert_editor_state(indoc! {"
10021 fn a() {
10022 «a();
10023 b();
10024 c();ˇ»
10025 }
10026 "});
10027}
10028
10029#[gpui::test]
10030async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10031 init_test(cx, |_| {});
10032 let mut cx = EditorTestContext::new(cx).await;
10033 let language = Arc::new(Language::new(
10034 LanguageConfig {
10035 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10036 ..Default::default()
10037 },
10038 Some(tree_sitter_rust::LANGUAGE.into()),
10039 ));
10040 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10041
10042 let toggle_comments = &ToggleComments {
10043 advance_downwards: false,
10044 ignore_indent: true,
10045 };
10046
10047 // If multiple selections intersect a line, the line is only toggled once.
10048 cx.set_state(indoc! {"
10049 fn a() {
10050 // «b();
10051 // c();
10052 // ˇ» d();
10053 }
10054 "});
10055
10056 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10057
10058 cx.assert_editor_state(indoc! {"
10059 fn a() {
10060 «b();
10061 c();
10062 ˇ» d();
10063 }
10064 "});
10065
10066 // The comment prefix is inserted at the beginning of each line
10067 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10068
10069 cx.assert_editor_state(indoc! {"
10070 fn a() {
10071 // «b();
10072 // c();
10073 // ˇ» d();
10074 }
10075 "});
10076
10077 // If a selection ends at the beginning of a line, that line is not toggled.
10078 cx.set_selections_state(indoc! {"
10079 fn a() {
10080 // b();
10081 // «c();
10082 ˇ»// d();
10083 }
10084 "});
10085
10086 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10087
10088 cx.assert_editor_state(indoc! {"
10089 fn a() {
10090 // b();
10091 «c();
10092 ˇ»// d();
10093 }
10094 "});
10095
10096 // If a selection span a single line and is empty, the line is toggled.
10097 cx.set_state(indoc! {"
10098 fn a() {
10099 a();
10100 b();
10101 ˇ
10102 }
10103 "});
10104
10105 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10106
10107 cx.assert_editor_state(indoc! {"
10108 fn a() {
10109 a();
10110 b();
10111 //ˇ
10112 }
10113 "});
10114
10115 // If a selection span multiple lines, empty lines are not toggled.
10116 cx.set_state(indoc! {"
10117 fn a() {
10118 «a();
10119
10120 c();ˇ»
10121 }
10122 "});
10123
10124 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10125
10126 cx.assert_editor_state(indoc! {"
10127 fn a() {
10128 // «a();
10129
10130 // c();ˇ»
10131 }
10132 "});
10133
10134 // If a selection includes multiple comment prefixes, all lines are uncommented.
10135 cx.set_state(indoc! {"
10136 fn a() {
10137 // «a();
10138 /// b();
10139 //! c();ˇ»
10140 }
10141 "});
10142
10143 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10144
10145 cx.assert_editor_state(indoc! {"
10146 fn a() {
10147 «a();
10148 b();
10149 c();ˇ»
10150 }
10151 "});
10152}
10153
10154#[gpui::test]
10155async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10156 init_test(cx, |_| {});
10157
10158 let language = Arc::new(Language::new(
10159 LanguageConfig {
10160 line_comments: vec!["// ".into()],
10161 ..Default::default()
10162 },
10163 Some(tree_sitter_rust::LANGUAGE.into()),
10164 ));
10165
10166 let mut cx = EditorTestContext::new(cx).await;
10167
10168 cx.language_registry().add(language.clone());
10169 cx.update_buffer(|buffer, cx| {
10170 buffer.set_language(Some(language), cx);
10171 });
10172
10173 let toggle_comments = &ToggleComments {
10174 advance_downwards: true,
10175 ignore_indent: false,
10176 };
10177
10178 // Single cursor on one line -> advance
10179 // Cursor moves horizontally 3 characters as well on non-blank line
10180 cx.set_state(indoc!(
10181 "fn a() {
10182 ˇdog();
10183 cat();
10184 }"
10185 ));
10186 cx.update_editor(|editor, window, cx| {
10187 editor.toggle_comments(toggle_comments, window, cx);
10188 });
10189 cx.assert_editor_state(indoc!(
10190 "fn a() {
10191 // dog();
10192 catˇ();
10193 }"
10194 ));
10195
10196 // Single selection on one line -> don't advance
10197 cx.set_state(indoc!(
10198 "fn a() {
10199 «dog()ˇ»;
10200 cat();
10201 }"
10202 ));
10203 cx.update_editor(|editor, window, cx| {
10204 editor.toggle_comments(toggle_comments, window, cx);
10205 });
10206 cx.assert_editor_state(indoc!(
10207 "fn a() {
10208 // «dog()ˇ»;
10209 cat();
10210 }"
10211 ));
10212
10213 // Multiple cursors on one line -> advance
10214 cx.set_state(indoc!(
10215 "fn a() {
10216 ˇdˇog();
10217 cat();
10218 }"
10219 ));
10220 cx.update_editor(|editor, window, cx| {
10221 editor.toggle_comments(toggle_comments, window, cx);
10222 });
10223 cx.assert_editor_state(indoc!(
10224 "fn a() {
10225 // dog();
10226 catˇ(ˇ);
10227 }"
10228 ));
10229
10230 // Multiple cursors on one line, with selection -> don't advance
10231 cx.set_state(indoc!(
10232 "fn a() {
10233 ˇdˇog«()ˇ»;
10234 cat();
10235 }"
10236 ));
10237 cx.update_editor(|editor, window, cx| {
10238 editor.toggle_comments(toggle_comments, window, cx);
10239 });
10240 cx.assert_editor_state(indoc!(
10241 "fn a() {
10242 // ˇdˇog«()ˇ»;
10243 cat();
10244 }"
10245 ));
10246
10247 // Single cursor on one line -> advance
10248 // Cursor moves to column 0 on blank line
10249 cx.set_state(indoc!(
10250 "fn a() {
10251 ˇdog();
10252
10253 cat();
10254 }"
10255 ));
10256 cx.update_editor(|editor, window, cx| {
10257 editor.toggle_comments(toggle_comments, window, cx);
10258 });
10259 cx.assert_editor_state(indoc!(
10260 "fn a() {
10261 // dog();
10262 ˇ
10263 cat();
10264 }"
10265 ));
10266
10267 // Single cursor on one line -> advance
10268 // Cursor starts and ends at column 0
10269 cx.set_state(indoc!(
10270 "fn a() {
10271 ˇ dog();
10272 cat();
10273 }"
10274 ));
10275 cx.update_editor(|editor, window, cx| {
10276 editor.toggle_comments(toggle_comments, window, cx);
10277 });
10278 cx.assert_editor_state(indoc!(
10279 "fn a() {
10280 // dog();
10281 ˇ cat();
10282 }"
10283 ));
10284}
10285
10286#[gpui::test]
10287async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10288 init_test(cx, |_| {});
10289
10290 let mut cx = EditorTestContext::new(cx).await;
10291
10292 let html_language = Arc::new(
10293 Language::new(
10294 LanguageConfig {
10295 name: "HTML".into(),
10296 block_comment: Some(("<!-- ".into(), " -->".into())),
10297 ..Default::default()
10298 },
10299 Some(tree_sitter_html::LANGUAGE.into()),
10300 )
10301 .with_injection_query(
10302 r#"
10303 (script_element
10304 (raw_text) @injection.content
10305 (#set! injection.language "javascript"))
10306 "#,
10307 )
10308 .unwrap(),
10309 );
10310
10311 let javascript_language = Arc::new(Language::new(
10312 LanguageConfig {
10313 name: "JavaScript".into(),
10314 line_comments: vec!["// ".into()],
10315 ..Default::default()
10316 },
10317 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10318 ));
10319
10320 cx.language_registry().add(html_language.clone());
10321 cx.language_registry().add(javascript_language.clone());
10322 cx.update_buffer(|buffer, cx| {
10323 buffer.set_language(Some(html_language), cx);
10324 });
10325
10326 // Toggle comments for empty selections
10327 cx.set_state(
10328 &r#"
10329 <p>A</p>ˇ
10330 <p>B</p>ˇ
10331 <p>C</p>ˇ
10332 "#
10333 .unindent(),
10334 );
10335 cx.update_editor(|editor, window, cx| {
10336 editor.toggle_comments(&ToggleComments::default(), window, cx)
10337 });
10338 cx.assert_editor_state(
10339 &r#"
10340 <!-- <p>A</p>ˇ -->
10341 <!-- <p>B</p>ˇ -->
10342 <!-- <p>C</p>ˇ -->
10343 "#
10344 .unindent(),
10345 );
10346 cx.update_editor(|editor, window, cx| {
10347 editor.toggle_comments(&ToggleComments::default(), window, cx)
10348 });
10349 cx.assert_editor_state(
10350 &r#"
10351 <p>A</p>ˇ
10352 <p>B</p>ˇ
10353 <p>C</p>ˇ
10354 "#
10355 .unindent(),
10356 );
10357
10358 // Toggle comments for mixture of empty and non-empty selections, where
10359 // multiple selections occupy a given line.
10360 cx.set_state(
10361 &r#"
10362 <p>A«</p>
10363 <p>ˇ»B</p>ˇ
10364 <p>C«</p>
10365 <p>ˇ»D</p>ˇ
10366 "#
10367 .unindent(),
10368 );
10369
10370 cx.update_editor(|editor, window, cx| {
10371 editor.toggle_comments(&ToggleComments::default(), window, cx)
10372 });
10373 cx.assert_editor_state(
10374 &r#"
10375 <!-- <p>A«</p>
10376 <p>ˇ»B</p>ˇ -->
10377 <!-- <p>C«</p>
10378 <p>ˇ»D</p>ˇ -->
10379 "#
10380 .unindent(),
10381 );
10382 cx.update_editor(|editor, window, cx| {
10383 editor.toggle_comments(&ToggleComments::default(), window, cx)
10384 });
10385 cx.assert_editor_state(
10386 &r#"
10387 <p>A«</p>
10388 <p>ˇ»B</p>ˇ
10389 <p>C«</p>
10390 <p>ˇ»D</p>ˇ
10391 "#
10392 .unindent(),
10393 );
10394
10395 // Toggle comments when different languages are active for different
10396 // selections.
10397 cx.set_state(
10398 &r#"
10399 ˇ<script>
10400 ˇvar x = new Y();
10401 ˇ</script>
10402 "#
10403 .unindent(),
10404 );
10405 cx.executor().run_until_parked();
10406 cx.update_editor(|editor, window, cx| {
10407 editor.toggle_comments(&ToggleComments::default(), window, cx)
10408 });
10409 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10410 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10411 cx.assert_editor_state(
10412 &r#"
10413 <!-- ˇ<script> -->
10414 // ˇvar x = new Y();
10415 <!-- ˇ</script> -->
10416 "#
10417 .unindent(),
10418 );
10419}
10420
10421#[gpui::test]
10422fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10423 init_test(cx, |_| {});
10424
10425 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10426 let multibuffer = cx.new(|cx| {
10427 let mut multibuffer = MultiBuffer::new(ReadWrite);
10428 multibuffer.push_excerpts(
10429 buffer.clone(),
10430 [
10431 ExcerptRange {
10432 context: Point::new(0, 0)..Point::new(0, 4),
10433 primary: None,
10434 },
10435 ExcerptRange {
10436 context: Point::new(1, 0)..Point::new(1, 4),
10437 primary: None,
10438 },
10439 ],
10440 cx,
10441 );
10442 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10443 multibuffer
10444 });
10445
10446 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10447 editor.update_in(cx, |editor, window, cx| {
10448 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10449 editor.change_selections(None, window, cx, |s| {
10450 s.select_ranges([
10451 Point::new(0, 0)..Point::new(0, 0),
10452 Point::new(1, 0)..Point::new(1, 0),
10453 ])
10454 });
10455
10456 editor.handle_input("X", window, cx);
10457 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10458 assert_eq!(
10459 editor.selections.ranges(cx),
10460 [
10461 Point::new(0, 1)..Point::new(0, 1),
10462 Point::new(1, 1)..Point::new(1, 1),
10463 ]
10464 );
10465
10466 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10467 editor.change_selections(None, window, cx, |s| {
10468 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10469 });
10470 editor.backspace(&Default::default(), window, cx);
10471 assert_eq!(editor.text(cx), "Xa\nbbb");
10472 assert_eq!(
10473 editor.selections.ranges(cx),
10474 [Point::new(1, 0)..Point::new(1, 0)]
10475 );
10476
10477 editor.change_selections(None, window, cx, |s| {
10478 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10479 });
10480 editor.backspace(&Default::default(), window, cx);
10481 assert_eq!(editor.text(cx), "X\nbb");
10482 assert_eq!(
10483 editor.selections.ranges(cx),
10484 [Point::new(0, 1)..Point::new(0, 1)]
10485 );
10486 });
10487}
10488
10489#[gpui::test]
10490fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10491 init_test(cx, |_| {});
10492
10493 let markers = vec![('[', ']').into(), ('(', ')').into()];
10494 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10495 indoc! {"
10496 [aaaa
10497 (bbbb]
10498 cccc)",
10499 },
10500 markers.clone(),
10501 );
10502 let excerpt_ranges = markers.into_iter().map(|marker| {
10503 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10504 ExcerptRange {
10505 context,
10506 primary: None,
10507 }
10508 });
10509 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10510 let multibuffer = cx.new(|cx| {
10511 let mut multibuffer = MultiBuffer::new(ReadWrite);
10512 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10513 multibuffer
10514 });
10515
10516 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10517 editor.update_in(cx, |editor, window, cx| {
10518 let (expected_text, selection_ranges) = marked_text_ranges(
10519 indoc! {"
10520 aaaa
10521 bˇbbb
10522 bˇbbˇb
10523 cccc"
10524 },
10525 true,
10526 );
10527 assert_eq!(editor.text(cx), expected_text);
10528 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10529
10530 editor.handle_input("X", window, cx);
10531
10532 let (expected_text, expected_selections) = marked_text_ranges(
10533 indoc! {"
10534 aaaa
10535 bXˇbbXb
10536 bXˇbbXˇb
10537 cccc"
10538 },
10539 false,
10540 );
10541 assert_eq!(editor.text(cx), expected_text);
10542 assert_eq!(editor.selections.ranges(cx), expected_selections);
10543
10544 editor.newline(&Newline, window, cx);
10545 let (expected_text, expected_selections) = marked_text_ranges(
10546 indoc! {"
10547 aaaa
10548 bX
10549 ˇbbX
10550 b
10551 bX
10552 ˇbbX
10553 ˇb
10554 cccc"
10555 },
10556 false,
10557 );
10558 assert_eq!(editor.text(cx), expected_text);
10559 assert_eq!(editor.selections.ranges(cx), expected_selections);
10560 });
10561}
10562
10563#[gpui::test]
10564fn test_refresh_selections(cx: &mut TestAppContext) {
10565 init_test(cx, |_| {});
10566
10567 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10568 let mut excerpt1_id = None;
10569 let multibuffer = cx.new(|cx| {
10570 let mut multibuffer = MultiBuffer::new(ReadWrite);
10571 excerpt1_id = multibuffer
10572 .push_excerpts(
10573 buffer.clone(),
10574 [
10575 ExcerptRange {
10576 context: Point::new(0, 0)..Point::new(1, 4),
10577 primary: None,
10578 },
10579 ExcerptRange {
10580 context: Point::new(1, 0)..Point::new(2, 4),
10581 primary: None,
10582 },
10583 ],
10584 cx,
10585 )
10586 .into_iter()
10587 .next();
10588 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10589 multibuffer
10590 });
10591
10592 let editor = cx.add_window(|window, cx| {
10593 let mut editor = build_editor(multibuffer.clone(), window, cx);
10594 let snapshot = editor.snapshot(window, cx);
10595 editor.change_selections(None, window, cx, |s| {
10596 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10597 });
10598 editor.begin_selection(
10599 Point::new(2, 1).to_display_point(&snapshot),
10600 true,
10601 1,
10602 window,
10603 cx,
10604 );
10605 assert_eq!(
10606 editor.selections.ranges(cx),
10607 [
10608 Point::new(1, 3)..Point::new(1, 3),
10609 Point::new(2, 1)..Point::new(2, 1),
10610 ]
10611 );
10612 editor
10613 });
10614
10615 // Refreshing selections is a no-op when excerpts haven't changed.
10616 _ = editor.update(cx, |editor, window, cx| {
10617 editor.change_selections(None, window, cx, |s| s.refresh());
10618 assert_eq!(
10619 editor.selections.ranges(cx),
10620 [
10621 Point::new(1, 3)..Point::new(1, 3),
10622 Point::new(2, 1)..Point::new(2, 1),
10623 ]
10624 );
10625 });
10626
10627 multibuffer.update(cx, |multibuffer, cx| {
10628 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10629 });
10630 _ = editor.update(cx, |editor, window, cx| {
10631 // Removing an excerpt causes the first selection to become degenerate.
10632 assert_eq!(
10633 editor.selections.ranges(cx),
10634 [
10635 Point::new(0, 0)..Point::new(0, 0),
10636 Point::new(0, 1)..Point::new(0, 1)
10637 ]
10638 );
10639
10640 // Refreshing selections will relocate the first selection to the original buffer
10641 // location.
10642 editor.change_selections(None, window, cx, |s| s.refresh());
10643 assert_eq!(
10644 editor.selections.ranges(cx),
10645 [
10646 Point::new(0, 1)..Point::new(0, 1),
10647 Point::new(0, 3)..Point::new(0, 3)
10648 ]
10649 );
10650 assert!(editor.selections.pending_anchor().is_some());
10651 });
10652}
10653
10654#[gpui::test]
10655fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10656 init_test(cx, |_| {});
10657
10658 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10659 let mut excerpt1_id = None;
10660 let multibuffer = cx.new(|cx| {
10661 let mut multibuffer = MultiBuffer::new(ReadWrite);
10662 excerpt1_id = multibuffer
10663 .push_excerpts(
10664 buffer.clone(),
10665 [
10666 ExcerptRange {
10667 context: Point::new(0, 0)..Point::new(1, 4),
10668 primary: None,
10669 },
10670 ExcerptRange {
10671 context: Point::new(1, 0)..Point::new(2, 4),
10672 primary: None,
10673 },
10674 ],
10675 cx,
10676 )
10677 .into_iter()
10678 .next();
10679 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10680 multibuffer
10681 });
10682
10683 let editor = cx.add_window(|window, cx| {
10684 let mut editor = build_editor(multibuffer.clone(), window, cx);
10685 let snapshot = editor.snapshot(window, cx);
10686 editor.begin_selection(
10687 Point::new(1, 3).to_display_point(&snapshot),
10688 false,
10689 1,
10690 window,
10691 cx,
10692 );
10693 assert_eq!(
10694 editor.selections.ranges(cx),
10695 [Point::new(1, 3)..Point::new(1, 3)]
10696 );
10697 editor
10698 });
10699
10700 multibuffer.update(cx, |multibuffer, cx| {
10701 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10702 });
10703 _ = editor.update(cx, |editor, window, cx| {
10704 assert_eq!(
10705 editor.selections.ranges(cx),
10706 [Point::new(0, 0)..Point::new(0, 0)]
10707 );
10708
10709 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10710 editor.change_selections(None, window, cx, |s| s.refresh());
10711 assert_eq!(
10712 editor.selections.ranges(cx),
10713 [Point::new(0, 3)..Point::new(0, 3)]
10714 );
10715 assert!(editor.selections.pending_anchor().is_some());
10716 });
10717}
10718
10719#[gpui::test]
10720async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10721 init_test(cx, |_| {});
10722
10723 let language = Arc::new(
10724 Language::new(
10725 LanguageConfig {
10726 brackets: BracketPairConfig {
10727 pairs: vec![
10728 BracketPair {
10729 start: "{".to_string(),
10730 end: "}".to_string(),
10731 close: true,
10732 surround: true,
10733 newline: true,
10734 },
10735 BracketPair {
10736 start: "/* ".to_string(),
10737 end: " */".to_string(),
10738 close: true,
10739 surround: true,
10740 newline: true,
10741 },
10742 ],
10743 ..Default::default()
10744 },
10745 ..Default::default()
10746 },
10747 Some(tree_sitter_rust::LANGUAGE.into()),
10748 )
10749 .with_indents_query("")
10750 .unwrap(),
10751 );
10752
10753 let text = concat!(
10754 "{ }\n", //
10755 " x\n", //
10756 " /* */\n", //
10757 "x\n", //
10758 "{{} }\n", //
10759 );
10760
10761 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10762 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10763 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10764 editor
10765 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10766 .await;
10767
10768 editor.update_in(cx, |editor, window, cx| {
10769 editor.change_selections(None, window, cx, |s| {
10770 s.select_display_ranges([
10771 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10772 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10773 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10774 ])
10775 });
10776 editor.newline(&Newline, window, cx);
10777
10778 assert_eq!(
10779 editor.buffer().read(cx).read(cx).text(),
10780 concat!(
10781 "{ \n", // Suppress rustfmt
10782 "\n", //
10783 "}\n", //
10784 " x\n", //
10785 " /* \n", //
10786 " \n", //
10787 " */\n", //
10788 "x\n", //
10789 "{{} \n", //
10790 "}\n", //
10791 )
10792 );
10793 });
10794}
10795
10796#[gpui::test]
10797fn test_highlighted_ranges(cx: &mut TestAppContext) {
10798 init_test(cx, |_| {});
10799
10800 let editor = cx.add_window(|window, cx| {
10801 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10802 build_editor(buffer.clone(), window, cx)
10803 });
10804
10805 _ = editor.update(cx, |editor, window, cx| {
10806 struct Type1;
10807 struct Type2;
10808
10809 let buffer = editor.buffer.read(cx).snapshot(cx);
10810
10811 let anchor_range =
10812 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10813
10814 editor.highlight_background::<Type1>(
10815 &[
10816 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10817 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10818 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10819 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10820 ],
10821 |_| Hsla::red(),
10822 cx,
10823 );
10824 editor.highlight_background::<Type2>(
10825 &[
10826 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10827 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10828 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10829 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10830 ],
10831 |_| Hsla::green(),
10832 cx,
10833 );
10834
10835 let snapshot = editor.snapshot(window, cx);
10836 let mut highlighted_ranges = editor.background_highlights_in_range(
10837 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10838 &snapshot,
10839 cx.theme().colors(),
10840 );
10841 // Enforce a consistent ordering based on color without relying on the ordering of the
10842 // highlight's `TypeId` which is non-executor.
10843 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10844 assert_eq!(
10845 highlighted_ranges,
10846 &[
10847 (
10848 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10849 Hsla::red(),
10850 ),
10851 (
10852 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10853 Hsla::red(),
10854 ),
10855 (
10856 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10857 Hsla::green(),
10858 ),
10859 (
10860 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10861 Hsla::green(),
10862 ),
10863 ]
10864 );
10865 assert_eq!(
10866 editor.background_highlights_in_range(
10867 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10868 &snapshot,
10869 cx.theme().colors(),
10870 ),
10871 &[(
10872 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10873 Hsla::red(),
10874 )]
10875 );
10876 });
10877}
10878
10879#[gpui::test]
10880async fn test_following(cx: &mut TestAppContext) {
10881 init_test(cx, |_| {});
10882
10883 let fs = FakeFs::new(cx.executor());
10884 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10885
10886 let buffer = project.update(cx, |project, cx| {
10887 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10888 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10889 });
10890 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10891 let follower = cx.update(|cx| {
10892 cx.open_window(
10893 WindowOptions {
10894 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10895 gpui::Point::new(px(0.), px(0.)),
10896 gpui::Point::new(px(10.), px(80.)),
10897 ))),
10898 ..Default::default()
10899 },
10900 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10901 )
10902 .unwrap()
10903 });
10904
10905 let is_still_following = Rc::new(RefCell::new(true));
10906 let follower_edit_event_count = Rc::new(RefCell::new(0));
10907 let pending_update = Rc::new(RefCell::new(None));
10908 let leader_entity = leader.root(cx).unwrap();
10909 let follower_entity = follower.root(cx).unwrap();
10910 _ = follower.update(cx, {
10911 let update = pending_update.clone();
10912 let is_still_following = is_still_following.clone();
10913 let follower_edit_event_count = follower_edit_event_count.clone();
10914 |_, window, cx| {
10915 cx.subscribe_in(
10916 &leader_entity,
10917 window,
10918 move |_, leader, event, window, cx| {
10919 leader.read(cx).add_event_to_update_proto(
10920 event,
10921 &mut update.borrow_mut(),
10922 window,
10923 cx,
10924 );
10925 },
10926 )
10927 .detach();
10928
10929 cx.subscribe_in(
10930 &follower_entity,
10931 window,
10932 move |_, _, event: &EditorEvent, _window, _cx| {
10933 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10934 *is_still_following.borrow_mut() = false;
10935 }
10936
10937 if let EditorEvent::BufferEdited = event {
10938 *follower_edit_event_count.borrow_mut() += 1;
10939 }
10940 },
10941 )
10942 .detach();
10943 }
10944 });
10945
10946 // Update the selections only
10947 _ = leader.update(cx, |leader, window, cx| {
10948 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10949 });
10950 follower
10951 .update(cx, |follower, window, cx| {
10952 follower.apply_update_proto(
10953 &project,
10954 pending_update.borrow_mut().take().unwrap(),
10955 window,
10956 cx,
10957 )
10958 })
10959 .unwrap()
10960 .await
10961 .unwrap();
10962 _ = follower.update(cx, |follower, _, cx| {
10963 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10964 });
10965 assert!(*is_still_following.borrow());
10966 assert_eq!(*follower_edit_event_count.borrow(), 0);
10967
10968 // Update the scroll position only
10969 _ = leader.update(cx, |leader, window, cx| {
10970 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10971 });
10972 follower
10973 .update(cx, |follower, window, cx| {
10974 follower.apply_update_proto(
10975 &project,
10976 pending_update.borrow_mut().take().unwrap(),
10977 window,
10978 cx,
10979 )
10980 })
10981 .unwrap()
10982 .await
10983 .unwrap();
10984 assert_eq!(
10985 follower
10986 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10987 .unwrap(),
10988 gpui::Point::new(1.5, 3.5)
10989 );
10990 assert!(*is_still_following.borrow());
10991 assert_eq!(*follower_edit_event_count.borrow(), 0);
10992
10993 // Update the selections and scroll position. The follower's scroll position is updated
10994 // via autoscroll, not via the leader's exact scroll position.
10995 _ = leader.update(cx, |leader, window, cx| {
10996 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10997 leader.request_autoscroll(Autoscroll::newest(), cx);
10998 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10999 });
11000 follower
11001 .update(cx, |follower, window, cx| {
11002 follower.apply_update_proto(
11003 &project,
11004 pending_update.borrow_mut().take().unwrap(),
11005 window,
11006 cx,
11007 )
11008 })
11009 .unwrap()
11010 .await
11011 .unwrap();
11012 _ = follower.update(cx, |follower, _, cx| {
11013 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11014 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11015 });
11016 assert!(*is_still_following.borrow());
11017
11018 // Creating a pending selection that precedes another selection
11019 _ = leader.update(cx, |leader, window, cx| {
11020 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11021 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11022 });
11023 follower
11024 .update(cx, |follower, window, cx| {
11025 follower.apply_update_proto(
11026 &project,
11027 pending_update.borrow_mut().take().unwrap(),
11028 window,
11029 cx,
11030 )
11031 })
11032 .unwrap()
11033 .await
11034 .unwrap();
11035 _ = follower.update(cx, |follower, _, cx| {
11036 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11037 });
11038 assert!(*is_still_following.borrow());
11039
11040 // Extend the pending selection so that it surrounds another selection
11041 _ = leader.update(cx, |leader, window, cx| {
11042 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11043 });
11044 follower
11045 .update(cx, |follower, window, cx| {
11046 follower.apply_update_proto(
11047 &project,
11048 pending_update.borrow_mut().take().unwrap(),
11049 window,
11050 cx,
11051 )
11052 })
11053 .unwrap()
11054 .await
11055 .unwrap();
11056 _ = follower.update(cx, |follower, _, cx| {
11057 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11058 });
11059
11060 // Scrolling locally breaks the follow
11061 _ = follower.update(cx, |follower, window, cx| {
11062 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11063 follower.set_scroll_anchor(
11064 ScrollAnchor {
11065 anchor: top_anchor,
11066 offset: gpui::Point::new(0.0, 0.5),
11067 },
11068 window,
11069 cx,
11070 );
11071 });
11072 assert!(!(*is_still_following.borrow()));
11073}
11074
11075#[gpui::test]
11076async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11077 init_test(cx, |_| {});
11078
11079 let fs = FakeFs::new(cx.executor());
11080 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11081 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11082 let pane = workspace
11083 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11084 .unwrap();
11085
11086 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11087
11088 let leader = pane.update_in(cx, |_, window, cx| {
11089 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11090 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11091 });
11092
11093 // Start following the editor when it has no excerpts.
11094 let mut state_message =
11095 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11096 let workspace_entity = workspace.root(cx).unwrap();
11097 let follower_1 = cx
11098 .update_window(*workspace.deref(), |_, window, cx| {
11099 Editor::from_state_proto(
11100 workspace_entity,
11101 ViewId {
11102 creator: Default::default(),
11103 id: 0,
11104 },
11105 &mut state_message,
11106 window,
11107 cx,
11108 )
11109 })
11110 .unwrap()
11111 .unwrap()
11112 .await
11113 .unwrap();
11114
11115 let update_message = Rc::new(RefCell::new(None));
11116 follower_1.update_in(cx, {
11117 let update = update_message.clone();
11118 |_, window, cx| {
11119 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11120 leader.read(cx).add_event_to_update_proto(
11121 event,
11122 &mut update.borrow_mut(),
11123 window,
11124 cx,
11125 );
11126 })
11127 .detach();
11128 }
11129 });
11130
11131 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11132 (
11133 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11134 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11135 )
11136 });
11137
11138 // Insert some excerpts.
11139 leader.update(cx, |leader, cx| {
11140 leader.buffer.update(cx, |multibuffer, cx| {
11141 let excerpt_ids = multibuffer.push_excerpts(
11142 buffer_1.clone(),
11143 [
11144 ExcerptRange {
11145 context: 1..6,
11146 primary: None,
11147 },
11148 ExcerptRange {
11149 context: 12..15,
11150 primary: None,
11151 },
11152 ExcerptRange {
11153 context: 0..3,
11154 primary: None,
11155 },
11156 ],
11157 cx,
11158 );
11159 multibuffer.insert_excerpts_after(
11160 excerpt_ids[0],
11161 buffer_2.clone(),
11162 [
11163 ExcerptRange {
11164 context: 8..12,
11165 primary: None,
11166 },
11167 ExcerptRange {
11168 context: 0..6,
11169 primary: None,
11170 },
11171 ],
11172 cx,
11173 );
11174 });
11175 });
11176
11177 // Apply the update of adding the excerpts.
11178 follower_1
11179 .update_in(cx, |follower, window, cx| {
11180 follower.apply_update_proto(
11181 &project,
11182 update_message.borrow().clone().unwrap(),
11183 window,
11184 cx,
11185 )
11186 })
11187 .await
11188 .unwrap();
11189 assert_eq!(
11190 follower_1.update(cx, |editor, cx| editor.text(cx)),
11191 leader.update(cx, |editor, cx| editor.text(cx))
11192 );
11193 update_message.borrow_mut().take();
11194
11195 // Start following separately after it already has excerpts.
11196 let mut state_message =
11197 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11198 let workspace_entity = workspace.root(cx).unwrap();
11199 let follower_2 = cx
11200 .update_window(*workspace.deref(), |_, window, cx| {
11201 Editor::from_state_proto(
11202 workspace_entity,
11203 ViewId {
11204 creator: Default::default(),
11205 id: 0,
11206 },
11207 &mut state_message,
11208 window,
11209 cx,
11210 )
11211 })
11212 .unwrap()
11213 .unwrap()
11214 .await
11215 .unwrap();
11216 assert_eq!(
11217 follower_2.update(cx, |editor, cx| editor.text(cx)),
11218 leader.update(cx, |editor, cx| editor.text(cx))
11219 );
11220
11221 // Remove some excerpts.
11222 leader.update(cx, |leader, cx| {
11223 leader.buffer.update(cx, |multibuffer, cx| {
11224 let excerpt_ids = multibuffer.excerpt_ids();
11225 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11226 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11227 });
11228 });
11229
11230 // Apply the update of removing the excerpts.
11231 follower_1
11232 .update_in(cx, |follower, window, cx| {
11233 follower.apply_update_proto(
11234 &project,
11235 update_message.borrow().clone().unwrap(),
11236 window,
11237 cx,
11238 )
11239 })
11240 .await
11241 .unwrap();
11242 follower_2
11243 .update_in(cx, |follower, window, cx| {
11244 follower.apply_update_proto(
11245 &project,
11246 update_message.borrow().clone().unwrap(),
11247 window,
11248 cx,
11249 )
11250 })
11251 .await
11252 .unwrap();
11253 update_message.borrow_mut().take();
11254 assert_eq!(
11255 follower_1.update(cx, |editor, cx| editor.text(cx)),
11256 leader.update(cx, |editor, cx| editor.text(cx))
11257 );
11258}
11259
11260#[gpui::test]
11261async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11262 init_test(cx, |_| {});
11263
11264 let mut cx = EditorTestContext::new(cx).await;
11265 let lsp_store =
11266 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11267
11268 cx.set_state(indoc! {"
11269 ˇfn func(abc def: i32) -> u32 {
11270 }
11271 "});
11272
11273 cx.update(|_, cx| {
11274 lsp_store.update(cx, |lsp_store, cx| {
11275 lsp_store
11276 .update_diagnostics(
11277 LanguageServerId(0),
11278 lsp::PublishDiagnosticsParams {
11279 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11280 version: None,
11281 diagnostics: vec![
11282 lsp::Diagnostic {
11283 range: lsp::Range::new(
11284 lsp::Position::new(0, 11),
11285 lsp::Position::new(0, 12),
11286 ),
11287 severity: Some(lsp::DiagnosticSeverity::ERROR),
11288 ..Default::default()
11289 },
11290 lsp::Diagnostic {
11291 range: lsp::Range::new(
11292 lsp::Position::new(0, 12),
11293 lsp::Position::new(0, 15),
11294 ),
11295 severity: Some(lsp::DiagnosticSeverity::ERROR),
11296 ..Default::default()
11297 },
11298 lsp::Diagnostic {
11299 range: lsp::Range::new(
11300 lsp::Position::new(0, 25),
11301 lsp::Position::new(0, 28),
11302 ),
11303 severity: Some(lsp::DiagnosticSeverity::ERROR),
11304 ..Default::default()
11305 },
11306 ],
11307 },
11308 &[],
11309 cx,
11310 )
11311 .unwrap()
11312 });
11313 });
11314
11315 executor.run_until_parked();
11316
11317 cx.update_editor(|editor, window, cx| {
11318 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11319 });
11320
11321 cx.assert_editor_state(indoc! {"
11322 fn func(abc def: i32) -> ˇu32 {
11323 }
11324 "});
11325
11326 cx.update_editor(|editor, window, cx| {
11327 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11328 });
11329
11330 cx.assert_editor_state(indoc! {"
11331 fn func(abc ˇdef: i32) -> u32 {
11332 }
11333 "});
11334
11335 cx.update_editor(|editor, window, cx| {
11336 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11337 });
11338
11339 cx.assert_editor_state(indoc! {"
11340 fn func(abcˇ def: i32) -> u32 {
11341 }
11342 "});
11343
11344 cx.update_editor(|editor, window, cx| {
11345 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11346 });
11347
11348 cx.assert_editor_state(indoc! {"
11349 fn func(abc def: i32) -> ˇu32 {
11350 }
11351 "});
11352}
11353
11354#[gpui::test]
11355async fn cycle_through_same_place_diagnostics(
11356 executor: BackgroundExecutor,
11357 cx: &mut TestAppContext,
11358) {
11359 init_test(cx, |_| {});
11360
11361 let mut cx = EditorTestContext::new(cx).await;
11362 let lsp_store =
11363 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11364
11365 cx.set_state(indoc! {"
11366 ˇfn func(abc def: i32) -> u32 {
11367 }
11368 "});
11369
11370 cx.update(|_, cx| {
11371 lsp_store.update(cx, |lsp_store, cx| {
11372 lsp_store
11373 .update_diagnostics(
11374 LanguageServerId(0),
11375 lsp::PublishDiagnosticsParams {
11376 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11377 version: None,
11378 diagnostics: vec![
11379 lsp::Diagnostic {
11380 range: lsp::Range::new(
11381 lsp::Position::new(0, 11),
11382 lsp::Position::new(0, 12),
11383 ),
11384 severity: Some(lsp::DiagnosticSeverity::ERROR),
11385 ..Default::default()
11386 },
11387 lsp::Diagnostic {
11388 range: lsp::Range::new(
11389 lsp::Position::new(0, 12),
11390 lsp::Position::new(0, 15),
11391 ),
11392 severity: Some(lsp::DiagnosticSeverity::ERROR),
11393 ..Default::default()
11394 },
11395 lsp::Diagnostic {
11396 range: lsp::Range::new(
11397 lsp::Position::new(0, 12),
11398 lsp::Position::new(0, 15),
11399 ),
11400 severity: Some(lsp::DiagnosticSeverity::ERROR),
11401 ..Default::default()
11402 },
11403 lsp::Diagnostic {
11404 range: lsp::Range::new(
11405 lsp::Position::new(0, 25),
11406 lsp::Position::new(0, 28),
11407 ),
11408 severity: Some(lsp::DiagnosticSeverity::ERROR),
11409 ..Default::default()
11410 },
11411 ],
11412 },
11413 &[],
11414 cx,
11415 )
11416 .unwrap()
11417 });
11418 });
11419 executor.run_until_parked();
11420
11421 //// Backward
11422
11423 // Fourth diagnostic
11424 cx.update_editor(|editor, window, cx| {
11425 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11426 });
11427 cx.assert_editor_state(indoc! {"
11428 fn func(abc def: i32) -> ˇu32 {
11429 }
11430 "});
11431
11432 // Third diagnostic
11433 cx.update_editor(|editor, window, cx| {
11434 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11435 });
11436 cx.assert_editor_state(indoc! {"
11437 fn func(abc ˇdef: i32) -> u32 {
11438 }
11439 "});
11440
11441 // Second diagnostic, same place
11442 cx.update_editor(|editor, window, cx| {
11443 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11444 });
11445 cx.assert_editor_state(indoc! {"
11446 fn func(abc ˇdef: i32) -> u32 {
11447 }
11448 "});
11449
11450 // First diagnostic
11451 cx.update_editor(|editor, window, cx| {
11452 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11453 });
11454 cx.assert_editor_state(indoc! {"
11455 fn func(abcˇ def: i32) -> u32 {
11456 }
11457 "});
11458
11459 // Wrapped over, fourth diagnostic
11460 cx.update_editor(|editor, window, cx| {
11461 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11462 });
11463 cx.assert_editor_state(indoc! {"
11464 fn func(abc def: i32) -> ˇu32 {
11465 }
11466 "});
11467
11468 cx.update_editor(|editor, window, cx| {
11469 editor.move_to_beginning(&MoveToBeginning, window, cx);
11470 });
11471 cx.assert_editor_state(indoc! {"
11472 ˇfn func(abc def: i32) -> u32 {
11473 }
11474 "});
11475
11476 //// Forward
11477
11478 // First diagnostic
11479 cx.update_editor(|editor, window, cx| {
11480 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11481 });
11482 cx.assert_editor_state(indoc! {"
11483 fn func(abcˇ def: i32) -> u32 {
11484 }
11485 "});
11486
11487 // Second diagnostic
11488 cx.update_editor(|editor, window, cx| {
11489 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11490 });
11491 cx.assert_editor_state(indoc! {"
11492 fn func(abc ˇdef: i32) -> u32 {
11493 }
11494 "});
11495
11496 // Third diagnostic, same place
11497 cx.update_editor(|editor, window, cx| {
11498 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11499 });
11500 cx.assert_editor_state(indoc! {"
11501 fn func(abc ˇdef: i32) -> u32 {
11502 }
11503 "});
11504
11505 // Fourth diagnostic
11506 cx.update_editor(|editor, window, cx| {
11507 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11508 });
11509 cx.assert_editor_state(indoc! {"
11510 fn func(abc def: i32) -> ˇu32 {
11511 }
11512 "});
11513
11514 // Wrapped around, first diagnostic
11515 cx.update_editor(|editor, window, cx| {
11516 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11517 });
11518 cx.assert_editor_state(indoc! {"
11519 fn func(abcˇ def: i32) -> u32 {
11520 }
11521 "});
11522}
11523
11524#[gpui::test]
11525async fn active_diagnostics_dismiss_after_invalidation(
11526 executor: BackgroundExecutor,
11527 cx: &mut TestAppContext,
11528) {
11529 init_test(cx, |_| {});
11530
11531 let mut cx = EditorTestContext::new(cx).await;
11532 let lsp_store =
11533 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11534
11535 cx.set_state(indoc! {"
11536 ˇfn func(abc def: i32) -> u32 {
11537 }
11538 "});
11539
11540 let message = "Something's wrong!";
11541 cx.update(|_, cx| {
11542 lsp_store.update(cx, |lsp_store, cx| {
11543 lsp_store
11544 .update_diagnostics(
11545 LanguageServerId(0),
11546 lsp::PublishDiagnosticsParams {
11547 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11548 version: None,
11549 diagnostics: vec![lsp::Diagnostic {
11550 range: lsp::Range::new(
11551 lsp::Position::new(0, 11),
11552 lsp::Position::new(0, 12),
11553 ),
11554 severity: Some(lsp::DiagnosticSeverity::ERROR),
11555 message: message.to_string(),
11556 ..Default::default()
11557 }],
11558 },
11559 &[],
11560 cx,
11561 )
11562 .unwrap()
11563 });
11564 });
11565 executor.run_until_parked();
11566
11567 cx.update_editor(|editor, window, cx| {
11568 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11569 assert_eq!(
11570 editor
11571 .active_diagnostics
11572 .as_ref()
11573 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11574 Some(message),
11575 "Should have a diagnostics group activated"
11576 );
11577 });
11578 cx.assert_editor_state(indoc! {"
11579 fn func(abcˇ def: i32) -> u32 {
11580 }
11581 "});
11582
11583 cx.update(|_, cx| {
11584 lsp_store.update(cx, |lsp_store, cx| {
11585 lsp_store
11586 .update_diagnostics(
11587 LanguageServerId(0),
11588 lsp::PublishDiagnosticsParams {
11589 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11590 version: None,
11591 diagnostics: Vec::new(),
11592 },
11593 &[],
11594 cx,
11595 )
11596 .unwrap()
11597 });
11598 });
11599 executor.run_until_parked();
11600 cx.update_editor(|editor, _, _| {
11601 assert_eq!(
11602 editor.active_diagnostics, None,
11603 "After no diagnostics set to the editor, no diagnostics should be active"
11604 );
11605 });
11606 cx.assert_editor_state(indoc! {"
11607 fn func(abcˇ def: i32) -> u32 {
11608 }
11609 "});
11610
11611 cx.update_editor(|editor, window, cx| {
11612 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11613 assert_eq!(
11614 editor.active_diagnostics, None,
11615 "Should be no diagnostics to go to and activate"
11616 );
11617 });
11618 cx.assert_editor_state(indoc! {"
11619 fn func(abcˇ def: i32) -> u32 {
11620 }
11621 "});
11622}
11623
11624#[gpui::test]
11625async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11626 init_test(cx, |_| {});
11627
11628 let mut cx = EditorTestContext::new(cx).await;
11629
11630 cx.set_state(indoc! {"
11631 fn func(abˇc def: i32) -> u32 {
11632 }
11633 "});
11634 let lsp_store =
11635 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11636
11637 cx.update(|_, cx| {
11638 lsp_store.update(cx, |lsp_store, cx| {
11639 lsp_store.update_diagnostics(
11640 LanguageServerId(0),
11641 lsp::PublishDiagnosticsParams {
11642 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11643 version: None,
11644 diagnostics: vec![lsp::Diagnostic {
11645 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11646 severity: Some(lsp::DiagnosticSeverity::ERROR),
11647 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11648 ..Default::default()
11649 }],
11650 },
11651 &[],
11652 cx,
11653 )
11654 })
11655 }).unwrap();
11656 cx.run_until_parked();
11657 cx.update_editor(|editor, window, cx| {
11658 hover_popover::hover(editor, &Default::default(), window, cx)
11659 });
11660 cx.run_until_parked();
11661 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11662}
11663
11664#[gpui::test]
11665async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11666 init_test(cx, |_| {});
11667
11668 let mut cx = EditorTestContext::new(cx).await;
11669
11670 let diff_base = r#"
11671 use some::mod;
11672
11673 const A: u32 = 42;
11674
11675 fn main() {
11676 println!("hello");
11677
11678 println!("world");
11679 }
11680 "#
11681 .unindent();
11682
11683 // Edits are modified, removed, modified, added
11684 cx.set_state(
11685 &r#"
11686 use some::modified;
11687
11688 ˇ
11689 fn main() {
11690 println!("hello there");
11691
11692 println!("around the");
11693 println!("world");
11694 }
11695 "#
11696 .unindent(),
11697 );
11698
11699 cx.set_head_text(&diff_base);
11700 executor.run_until_parked();
11701
11702 cx.update_editor(|editor, window, cx| {
11703 //Wrap around the bottom of the buffer
11704 for _ in 0..3 {
11705 editor.go_to_next_hunk(&GoToHunk, window, cx);
11706 }
11707 });
11708
11709 cx.assert_editor_state(
11710 &r#"
11711 ˇuse some::modified;
11712
11713
11714 fn main() {
11715 println!("hello there");
11716
11717 println!("around the");
11718 println!("world");
11719 }
11720 "#
11721 .unindent(),
11722 );
11723
11724 cx.update_editor(|editor, window, cx| {
11725 //Wrap around the top of the buffer
11726 for _ in 0..2 {
11727 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11728 }
11729 });
11730
11731 cx.assert_editor_state(
11732 &r#"
11733 use some::modified;
11734
11735
11736 fn main() {
11737 ˇ println!("hello there");
11738
11739 println!("around the");
11740 println!("world");
11741 }
11742 "#
11743 .unindent(),
11744 );
11745
11746 cx.update_editor(|editor, window, cx| {
11747 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11748 });
11749
11750 cx.assert_editor_state(
11751 &r#"
11752 use some::modified;
11753
11754 ˇ
11755 fn main() {
11756 println!("hello there");
11757
11758 println!("around the");
11759 println!("world");
11760 }
11761 "#
11762 .unindent(),
11763 );
11764
11765 cx.update_editor(|editor, window, cx| {
11766 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11767 });
11768
11769 cx.assert_editor_state(
11770 &r#"
11771 ˇuse some::modified;
11772
11773
11774 fn main() {
11775 println!("hello there");
11776
11777 println!("around the");
11778 println!("world");
11779 }
11780 "#
11781 .unindent(),
11782 );
11783
11784 cx.update_editor(|editor, window, cx| {
11785 for _ in 0..2 {
11786 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11787 }
11788 });
11789
11790 cx.assert_editor_state(
11791 &r#"
11792 use some::modified;
11793
11794
11795 fn main() {
11796 ˇ println!("hello there");
11797
11798 println!("around the");
11799 println!("world");
11800 }
11801 "#
11802 .unindent(),
11803 );
11804
11805 cx.update_editor(|editor, window, cx| {
11806 editor.fold(&Fold, window, cx);
11807 });
11808
11809 cx.update_editor(|editor, window, cx| {
11810 editor.go_to_next_hunk(&GoToHunk, window, cx);
11811 });
11812
11813 cx.assert_editor_state(
11814 &r#"
11815 ˇuse some::modified;
11816
11817
11818 fn main() {
11819 println!("hello there");
11820
11821 println!("around the");
11822 println!("world");
11823 }
11824 "#
11825 .unindent(),
11826 );
11827}
11828
11829#[test]
11830fn test_split_words() {
11831 fn split(text: &str) -> Vec<&str> {
11832 split_words(text).collect()
11833 }
11834
11835 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11836 assert_eq!(split("hello_world"), &["hello_", "world"]);
11837 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11838 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11839 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11840 assert_eq!(split("helloworld"), &["helloworld"]);
11841
11842 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11843}
11844
11845#[gpui::test]
11846async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11847 init_test(cx, |_| {});
11848
11849 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11850 let mut assert = |before, after| {
11851 let _state_context = cx.set_state(before);
11852 cx.run_until_parked();
11853 cx.update_editor(|editor, window, cx| {
11854 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11855 });
11856 cx.assert_editor_state(after);
11857 };
11858
11859 // Outside bracket jumps to outside of matching bracket
11860 assert("console.logˇ(var);", "console.log(var)ˇ;");
11861 assert("console.log(var)ˇ;", "console.logˇ(var);");
11862
11863 // Inside bracket jumps to inside of matching bracket
11864 assert("console.log(ˇvar);", "console.log(varˇ);");
11865 assert("console.log(varˇ);", "console.log(ˇvar);");
11866
11867 // When outside a bracket and inside, favor jumping to the inside bracket
11868 assert(
11869 "console.log('foo', [1, 2, 3]ˇ);",
11870 "console.log(ˇ'foo', [1, 2, 3]);",
11871 );
11872 assert(
11873 "console.log(ˇ'foo', [1, 2, 3]);",
11874 "console.log('foo', [1, 2, 3]ˇ);",
11875 );
11876
11877 // Bias forward if two options are equally likely
11878 assert(
11879 "let result = curried_fun()ˇ();",
11880 "let result = curried_fun()()ˇ;",
11881 );
11882
11883 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11884 assert(
11885 indoc! {"
11886 function test() {
11887 console.log('test')ˇ
11888 }"},
11889 indoc! {"
11890 function test() {
11891 console.logˇ('test')
11892 }"},
11893 );
11894}
11895
11896#[gpui::test]
11897async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11898 init_test(cx, |_| {});
11899
11900 let fs = FakeFs::new(cx.executor());
11901 fs.insert_tree(
11902 path!("/a"),
11903 json!({
11904 "main.rs": "fn main() { let a = 5; }",
11905 "other.rs": "// Test file",
11906 }),
11907 )
11908 .await;
11909 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11910
11911 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11912 language_registry.add(Arc::new(Language::new(
11913 LanguageConfig {
11914 name: "Rust".into(),
11915 matcher: LanguageMatcher {
11916 path_suffixes: vec!["rs".to_string()],
11917 ..Default::default()
11918 },
11919 brackets: BracketPairConfig {
11920 pairs: vec![BracketPair {
11921 start: "{".to_string(),
11922 end: "}".to_string(),
11923 close: true,
11924 surround: true,
11925 newline: true,
11926 }],
11927 disabled_scopes_by_bracket_ix: Vec::new(),
11928 },
11929 ..Default::default()
11930 },
11931 Some(tree_sitter_rust::LANGUAGE.into()),
11932 )));
11933 let mut fake_servers = language_registry.register_fake_lsp(
11934 "Rust",
11935 FakeLspAdapter {
11936 capabilities: lsp::ServerCapabilities {
11937 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11938 first_trigger_character: "{".to_string(),
11939 more_trigger_character: None,
11940 }),
11941 ..Default::default()
11942 },
11943 ..Default::default()
11944 },
11945 );
11946
11947 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11948
11949 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11950
11951 let worktree_id = workspace
11952 .update(cx, |workspace, _, cx| {
11953 workspace.project().update(cx, |project, cx| {
11954 project.worktrees(cx).next().unwrap().read(cx).id()
11955 })
11956 })
11957 .unwrap();
11958
11959 let buffer = project
11960 .update(cx, |project, cx| {
11961 project.open_local_buffer(path!("/a/main.rs"), cx)
11962 })
11963 .await
11964 .unwrap();
11965 let editor_handle = workspace
11966 .update(cx, |workspace, window, cx| {
11967 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11968 })
11969 .unwrap()
11970 .await
11971 .unwrap()
11972 .downcast::<Editor>()
11973 .unwrap();
11974
11975 cx.executor().start_waiting();
11976 let fake_server = fake_servers.next().await.unwrap();
11977
11978 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11979 assert_eq!(
11980 params.text_document_position.text_document.uri,
11981 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11982 );
11983 assert_eq!(
11984 params.text_document_position.position,
11985 lsp::Position::new(0, 21),
11986 );
11987
11988 Ok(Some(vec![lsp::TextEdit {
11989 new_text: "]".to_string(),
11990 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11991 }]))
11992 });
11993
11994 editor_handle.update_in(cx, |editor, window, cx| {
11995 window.focus(&editor.focus_handle(cx));
11996 editor.change_selections(None, window, cx, |s| {
11997 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11998 });
11999 editor.handle_input("{", window, cx);
12000 });
12001
12002 cx.executor().run_until_parked();
12003
12004 buffer.update(cx, |buffer, _| {
12005 assert_eq!(
12006 buffer.text(),
12007 "fn main() { let a = {5}; }",
12008 "No extra braces from on type formatting should appear in the buffer"
12009 )
12010 });
12011}
12012
12013#[gpui::test]
12014async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12015 init_test(cx, |_| {});
12016
12017 let fs = FakeFs::new(cx.executor());
12018 fs.insert_tree(
12019 path!("/a"),
12020 json!({
12021 "main.rs": "fn main() { let a = 5; }",
12022 "other.rs": "// Test file",
12023 }),
12024 )
12025 .await;
12026
12027 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12028
12029 let server_restarts = Arc::new(AtomicUsize::new(0));
12030 let closure_restarts = Arc::clone(&server_restarts);
12031 let language_server_name = "test language server";
12032 let language_name: LanguageName = "Rust".into();
12033
12034 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12035 language_registry.add(Arc::new(Language::new(
12036 LanguageConfig {
12037 name: language_name.clone(),
12038 matcher: LanguageMatcher {
12039 path_suffixes: vec!["rs".to_string()],
12040 ..Default::default()
12041 },
12042 ..Default::default()
12043 },
12044 Some(tree_sitter_rust::LANGUAGE.into()),
12045 )));
12046 let mut fake_servers = language_registry.register_fake_lsp(
12047 "Rust",
12048 FakeLspAdapter {
12049 name: language_server_name,
12050 initialization_options: Some(json!({
12051 "testOptionValue": true
12052 })),
12053 initializer: Some(Box::new(move |fake_server| {
12054 let task_restarts = Arc::clone(&closure_restarts);
12055 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
12056 task_restarts.fetch_add(1, atomic::Ordering::Release);
12057 futures::future::ready(Ok(()))
12058 });
12059 })),
12060 ..Default::default()
12061 },
12062 );
12063
12064 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12065 let _buffer = project
12066 .update(cx, |project, cx| {
12067 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12068 })
12069 .await
12070 .unwrap();
12071 let _fake_server = fake_servers.next().await.unwrap();
12072 update_test_language_settings(cx, |language_settings| {
12073 language_settings.languages.insert(
12074 language_name.clone(),
12075 LanguageSettingsContent {
12076 tab_size: NonZeroU32::new(8),
12077 ..Default::default()
12078 },
12079 );
12080 });
12081 cx.executor().run_until_parked();
12082 assert_eq!(
12083 server_restarts.load(atomic::Ordering::Acquire),
12084 0,
12085 "Should not restart LSP server on an unrelated change"
12086 );
12087
12088 update_test_project_settings(cx, |project_settings| {
12089 project_settings.lsp.insert(
12090 "Some other server name".into(),
12091 LspSettings {
12092 binary: None,
12093 settings: None,
12094 initialization_options: Some(json!({
12095 "some other init value": false
12096 })),
12097 },
12098 );
12099 });
12100 cx.executor().run_until_parked();
12101 assert_eq!(
12102 server_restarts.load(atomic::Ordering::Acquire),
12103 0,
12104 "Should not restart LSP server on an unrelated LSP settings change"
12105 );
12106
12107 update_test_project_settings(cx, |project_settings| {
12108 project_settings.lsp.insert(
12109 language_server_name.into(),
12110 LspSettings {
12111 binary: None,
12112 settings: None,
12113 initialization_options: Some(json!({
12114 "anotherInitValue": false
12115 })),
12116 },
12117 );
12118 });
12119 cx.executor().run_until_parked();
12120 assert_eq!(
12121 server_restarts.load(atomic::Ordering::Acquire),
12122 1,
12123 "Should restart LSP server on a related LSP settings change"
12124 );
12125
12126 update_test_project_settings(cx, |project_settings| {
12127 project_settings.lsp.insert(
12128 language_server_name.into(),
12129 LspSettings {
12130 binary: None,
12131 settings: None,
12132 initialization_options: Some(json!({
12133 "anotherInitValue": false
12134 })),
12135 },
12136 );
12137 });
12138 cx.executor().run_until_parked();
12139 assert_eq!(
12140 server_restarts.load(atomic::Ordering::Acquire),
12141 1,
12142 "Should not restart LSP server on a related LSP settings change that is the same"
12143 );
12144
12145 update_test_project_settings(cx, |project_settings| {
12146 project_settings.lsp.insert(
12147 language_server_name.into(),
12148 LspSettings {
12149 binary: None,
12150 settings: None,
12151 initialization_options: None,
12152 },
12153 );
12154 });
12155 cx.executor().run_until_parked();
12156 assert_eq!(
12157 server_restarts.load(atomic::Ordering::Acquire),
12158 2,
12159 "Should restart LSP server on another related LSP settings change"
12160 );
12161}
12162
12163#[gpui::test]
12164async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12165 init_test(cx, |_| {});
12166
12167 let mut cx = EditorLspTestContext::new_rust(
12168 lsp::ServerCapabilities {
12169 completion_provider: Some(lsp::CompletionOptions {
12170 trigger_characters: Some(vec![".".to_string()]),
12171 resolve_provider: Some(true),
12172 ..Default::default()
12173 }),
12174 ..Default::default()
12175 },
12176 cx,
12177 )
12178 .await;
12179
12180 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12181 cx.simulate_keystroke(".");
12182 let completion_item = lsp::CompletionItem {
12183 label: "some".into(),
12184 kind: Some(lsp::CompletionItemKind::SNIPPET),
12185 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12186 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12187 kind: lsp::MarkupKind::Markdown,
12188 value: "```rust\nSome(2)\n```".to_string(),
12189 })),
12190 deprecated: Some(false),
12191 sort_text: Some("fffffff2".to_string()),
12192 filter_text: Some("some".to_string()),
12193 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12194 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12195 range: lsp::Range {
12196 start: lsp::Position {
12197 line: 0,
12198 character: 22,
12199 },
12200 end: lsp::Position {
12201 line: 0,
12202 character: 22,
12203 },
12204 },
12205 new_text: "Some(2)".to_string(),
12206 })),
12207 additional_text_edits: Some(vec![lsp::TextEdit {
12208 range: lsp::Range {
12209 start: lsp::Position {
12210 line: 0,
12211 character: 20,
12212 },
12213 end: lsp::Position {
12214 line: 0,
12215 character: 22,
12216 },
12217 },
12218 new_text: "".to_string(),
12219 }]),
12220 ..Default::default()
12221 };
12222
12223 let closure_completion_item = completion_item.clone();
12224 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12225 let task_completion_item = closure_completion_item.clone();
12226 async move {
12227 Ok(Some(lsp::CompletionResponse::Array(vec![
12228 task_completion_item,
12229 ])))
12230 }
12231 });
12232
12233 request.next().await;
12234
12235 cx.condition(|editor, _| editor.context_menu_visible())
12236 .await;
12237 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12238 editor
12239 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12240 .unwrap()
12241 });
12242 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12243
12244 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12245 let task_completion_item = completion_item.clone();
12246 async move { Ok(task_completion_item) }
12247 })
12248 .next()
12249 .await
12250 .unwrap();
12251 apply_additional_edits.await.unwrap();
12252 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12253}
12254
12255#[gpui::test]
12256async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12257 init_test(cx, |_| {});
12258
12259 let mut cx = EditorLspTestContext::new_rust(
12260 lsp::ServerCapabilities {
12261 completion_provider: Some(lsp::CompletionOptions {
12262 trigger_characters: Some(vec![".".to_string()]),
12263 resolve_provider: Some(true),
12264 ..Default::default()
12265 }),
12266 ..Default::default()
12267 },
12268 cx,
12269 )
12270 .await;
12271
12272 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12273 cx.simulate_keystroke(".");
12274
12275 let item1 = lsp::CompletionItem {
12276 label: "method id()".to_string(),
12277 filter_text: Some("id".to_string()),
12278 detail: None,
12279 documentation: None,
12280 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12281 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12282 new_text: ".id".to_string(),
12283 })),
12284 ..lsp::CompletionItem::default()
12285 };
12286
12287 let item2 = lsp::CompletionItem {
12288 label: "other".to_string(),
12289 filter_text: Some("other".to_string()),
12290 detail: None,
12291 documentation: None,
12292 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12293 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12294 new_text: ".other".to_string(),
12295 })),
12296 ..lsp::CompletionItem::default()
12297 };
12298
12299 let item1 = item1.clone();
12300 cx.handle_request::<lsp::request::Completion, _, _>({
12301 let item1 = item1.clone();
12302 move |_, _, _| {
12303 let item1 = item1.clone();
12304 let item2 = item2.clone();
12305 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12306 }
12307 })
12308 .next()
12309 .await;
12310
12311 cx.condition(|editor, _| editor.context_menu_visible())
12312 .await;
12313 cx.update_editor(|editor, _, _| {
12314 let context_menu = editor.context_menu.borrow_mut();
12315 let context_menu = context_menu
12316 .as_ref()
12317 .expect("Should have the context menu deployed");
12318 match context_menu {
12319 CodeContextMenu::Completions(completions_menu) => {
12320 let completions = completions_menu.completions.borrow_mut();
12321 assert_eq!(
12322 completions
12323 .iter()
12324 .map(|completion| &completion.label.text)
12325 .collect::<Vec<_>>(),
12326 vec!["method id()", "other"]
12327 )
12328 }
12329 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12330 }
12331 });
12332
12333 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12334 let item1 = item1.clone();
12335 move |_, item_to_resolve, _| {
12336 let item1 = item1.clone();
12337 async move {
12338 if item1 == item_to_resolve {
12339 Ok(lsp::CompletionItem {
12340 label: "method id()".to_string(),
12341 filter_text: Some("id".to_string()),
12342 detail: Some("Now resolved!".to_string()),
12343 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12344 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12345 range: lsp::Range::new(
12346 lsp::Position::new(0, 22),
12347 lsp::Position::new(0, 22),
12348 ),
12349 new_text: ".id".to_string(),
12350 })),
12351 ..lsp::CompletionItem::default()
12352 })
12353 } else {
12354 Ok(item_to_resolve)
12355 }
12356 }
12357 }
12358 })
12359 .next()
12360 .await
12361 .unwrap();
12362 cx.run_until_parked();
12363
12364 cx.update_editor(|editor, window, cx| {
12365 editor.context_menu_next(&Default::default(), window, cx);
12366 });
12367
12368 cx.update_editor(|editor, _, _| {
12369 let context_menu = editor.context_menu.borrow_mut();
12370 let context_menu = context_menu
12371 .as_ref()
12372 .expect("Should have the context menu deployed");
12373 match context_menu {
12374 CodeContextMenu::Completions(completions_menu) => {
12375 let completions = completions_menu.completions.borrow_mut();
12376 assert_eq!(
12377 completions
12378 .iter()
12379 .map(|completion| &completion.label.text)
12380 .collect::<Vec<_>>(),
12381 vec!["method id() Now resolved!", "other"],
12382 "Should update first completion label, but not second as the filter text did not match."
12383 );
12384 }
12385 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12386 }
12387 });
12388}
12389
12390#[gpui::test]
12391async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12392 init_test(cx, |_| {});
12393
12394 let mut cx = EditorLspTestContext::new_rust(
12395 lsp::ServerCapabilities {
12396 completion_provider: Some(lsp::CompletionOptions {
12397 trigger_characters: Some(vec![".".to_string()]),
12398 resolve_provider: Some(true),
12399 ..Default::default()
12400 }),
12401 ..Default::default()
12402 },
12403 cx,
12404 )
12405 .await;
12406
12407 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12408 cx.simulate_keystroke(".");
12409
12410 let unresolved_item_1 = lsp::CompletionItem {
12411 label: "id".to_string(),
12412 filter_text: Some("id".to_string()),
12413 detail: None,
12414 documentation: None,
12415 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12416 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12417 new_text: ".id".to_string(),
12418 })),
12419 ..lsp::CompletionItem::default()
12420 };
12421 let resolved_item_1 = lsp::CompletionItem {
12422 additional_text_edits: Some(vec![lsp::TextEdit {
12423 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12424 new_text: "!!".to_string(),
12425 }]),
12426 ..unresolved_item_1.clone()
12427 };
12428 let unresolved_item_2 = lsp::CompletionItem {
12429 label: "other".to_string(),
12430 filter_text: Some("other".to_string()),
12431 detail: None,
12432 documentation: None,
12433 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12434 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12435 new_text: ".other".to_string(),
12436 })),
12437 ..lsp::CompletionItem::default()
12438 };
12439 let resolved_item_2 = lsp::CompletionItem {
12440 additional_text_edits: Some(vec![lsp::TextEdit {
12441 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12442 new_text: "??".to_string(),
12443 }]),
12444 ..unresolved_item_2.clone()
12445 };
12446
12447 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12448 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12449 cx.lsp
12450 .server
12451 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12452 let unresolved_item_1 = unresolved_item_1.clone();
12453 let resolved_item_1 = resolved_item_1.clone();
12454 let unresolved_item_2 = unresolved_item_2.clone();
12455 let resolved_item_2 = resolved_item_2.clone();
12456 let resolve_requests_1 = resolve_requests_1.clone();
12457 let resolve_requests_2 = resolve_requests_2.clone();
12458 move |unresolved_request, _| {
12459 let unresolved_item_1 = unresolved_item_1.clone();
12460 let resolved_item_1 = resolved_item_1.clone();
12461 let unresolved_item_2 = unresolved_item_2.clone();
12462 let resolved_item_2 = resolved_item_2.clone();
12463 let resolve_requests_1 = resolve_requests_1.clone();
12464 let resolve_requests_2 = resolve_requests_2.clone();
12465 async move {
12466 if unresolved_request == unresolved_item_1 {
12467 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12468 Ok(resolved_item_1.clone())
12469 } else if unresolved_request == unresolved_item_2 {
12470 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12471 Ok(resolved_item_2.clone())
12472 } else {
12473 panic!("Unexpected completion item {unresolved_request:?}")
12474 }
12475 }
12476 }
12477 })
12478 .detach();
12479
12480 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12481 let unresolved_item_1 = unresolved_item_1.clone();
12482 let unresolved_item_2 = unresolved_item_2.clone();
12483 async move {
12484 Ok(Some(lsp::CompletionResponse::Array(vec![
12485 unresolved_item_1,
12486 unresolved_item_2,
12487 ])))
12488 }
12489 })
12490 .next()
12491 .await;
12492
12493 cx.condition(|editor, _| editor.context_menu_visible())
12494 .await;
12495 cx.update_editor(|editor, _, _| {
12496 let context_menu = editor.context_menu.borrow_mut();
12497 let context_menu = context_menu
12498 .as_ref()
12499 .expect("Should have the context menu deployed");
12500 match context_menu {
12501 CodeContextMenu::Completions(completions_menu) => {
12502 let completions = completions_menu.completions.borrow_mut();
12503 assert_eq!(
12504 completions
12505 .iter()
12506 .map(|completion| &completion.label.text)
12507 .collect::<Vec<_>>(),
12508 vec!["id", "other"]
12509 )
12510 }
12511 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12512 }
12513 });
12514 cx.run_until_parked();
12515
12516 cx.update_editor(|editor, window, cx| {
12517 editor.context_menu_next(&ContextMenuNext, window, cx);
12518 });
12519 cx.run_until_parked();
12520 cx.update_editor(|editor, window, cx| {
12521 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12522 });
12523 cx.run_until_parked();
12524 cx.update_editor(|editor, window, cx| {
12525 editor.context_menu_next(&ContextMenuNext, window, cx);
12526 });
12527 cx.run_until_parked();
12528 cx.update_editor(|editor, window, cx| {
12529 editor
12530 .compose_completion(&ComposeCompletion::default(), window, cx)
12531 .expect("No task returned")
12532 })
12533 .await
12534 .expect("Completion failed");
12535 cx.run_until_parked();
12536
12537 cx.update_editor(|editor, _, cx| {
12538 assert_eq!(
12539 resolve_requests_1.load(atomic::Ordering::Acquire),
12540 1,
12541 "Should always resolve once despite multiple selections"
12542 );
12543 assert_eq!(
12544 resolve_requests_2.load(atomic::Ordering::Acquire),
12545 1,
12546 "Should always resolve once after multiple selections and applying the completion"
12547 );
12548 assert_eq!(
12549 editor.text(cx),
12550 "fn main() { let a = ??.other; }",
12551 "Should use resolved data when applying the completion"
12552 );
12553 });
12554}
12555
12556#[gpui::test]
12557async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12558 init_test(cx, |_| {});
12559
12560 let item_0 = lsp::CompletionItem {
12561 label: "abs".into(),
12562 insert_text: Some("abs".into()),
12563 data: Some(json!({ "very": "special"})),
12564 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12565 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12566 lsp::InsertReplaceEdit {
12567 new_text: "abs".to_string(),
12568 insert: lsp::Range::default(),
12569 replace: lsp::Range::default(),
12570 },
12571 )),
12572 ..lsp::CompletionItem::default()
12573 };
12574 let items = iter::once(item_0.clone())
12575 .chain((11..51).map(|i| lsp::CompletionItem {
12576 label: format!("item_{}", i),
12577 insert_text: Some(format!("item_{}", i)),
12578 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12579 ..lsp::CompletionItem::default()
12580 }))
12581 .collect::<Vec<_>>();
12582
12583 let default_commit_characters = vec!["?".to_string()];
12584 let default_data = json!({ "default": "data"});
12585 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12586 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12587 let default_edit_range = lsp::Range {
12588 start: lsp::Position {
12589 line: 0,
12590 character: 5,
12591 },
12592 end: lsp::Position {
12593 line: 0,
12594 character: 5,
12595 },
12596 };
12597
12598 let mut cx = EditorLspTestContext::new_rust(
12599 lsp::ServerCapabilities {
12600 completion_provider: Some(lsp::CompletionOptions {
12601 trigger_characters: Some(vec![".".to_string()]),
12602 resolve_provider: Some(true),
12603 ..Default::default()
12604 }),
12605 ..Default::default()
12606 },
12607 cx,
12608 )
12609 .await;
12610
12611 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12612 cx.simulate_keystroke(".");
12613
12614 let completion_data = default_data.clone();
12615 let completion_characters = default_commit_characters.clone();
12616 let completion_items = items.clone();
12617 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12618 let default_data = completion_data.clone();
12619 let default_commit_characters = completion_characters.clone();
12620 let items = completion_items.clone();
12621 async move {
12622 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12623 items,
12624 item_defaults: Some(lsp::CompletionListItemDefaults {
12625 data: Some(default_data.clone()),
12626 commit_characters: Some(default_commit_characters.clone()),
12627 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12628 default_edit_range,
12629 )),
12630 insert_text_format: Some(default_insert_text_format),
12631 insert_text_mode: Some(default_insert_text_mode),
12632 }),
12633 ..lsp::CompletionList::default()
12634 })))
12635 }
12636 })
12637 .next()
12638 .await;
12639
12640 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12641 cx.lsp
12642 .server
12643 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12644 let closure_resolved_items = resolved_items.clone();
12645 move |item_to_resolve, _| {
12646 let closure_resolved_items = closure_resolved_items.clone();
12647 async move {
12648 closure_resolved_items.lock().push(item_to_resolve.clone());
12649 Ok(item_to_resolve)
12650 }
12651 }
12652 })
12653 .detach();
12654
12655 cx.condition(|editor, _| editor.context_menu_visible())
12656 .await;
12657 cx.run_until_parked();
12658 cx.update_editor(|editor, _, _| {
12659 let menu = editor.context_menu.borrow_mut();
12660 match menu.as_ref().expect("should have the completions menu") {
12661 CodeContextMenu::Completions(completions_menu) => {
12662 assert_eq!(
12663 completions_menu
12664 .entries
12665 .borrow()
12666 .iter()
12667 .map(|mat| mat.string.clone())
12668 .collect::<Vec<String>>(),
12669 items
12670 .iter()
12671 .map(|completion| completion.label.clone())
12672 .collect::<Vec<String>>()
12673 );
12674 }
12675 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12676 }
12677 });
12678 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12679 // with 4 from the end.
12680 assert_eq!(
12681 *resolved_items.lock(),
12682 [&items[0..16], &items[items.len() - 4..items.len()]]
12683 .concat()
12684 .iter()
12685 .cloned()
12686 .map(|mut item| {
12687 if item.data.is_none() {
12688 item.data = Some(default_data.clone());
12689 }
12690 item
12691 })
12692 .collect::<Vec<lsp::CompletionItem>>(),
12693 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12694 );
12695 resolved_items.lock().clear();
12696
12697 cx.update_editor(|editor, window, cx| {
12698 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12699 });
12700 cx.run_until_parked();
12701 // Completions that have already been resolved are skipped.
12702 assert_eq!(
12703 *resolved_items.lock(),
12704 items[items.len() - 16..items.len() - 4]
12705 .iter()
12706 .cloned()
12707 .map(|mut item| {
12708 if item.data.is_none() {
12709 item.data = Some(default_data.clone());
12710 }
12711 item
12712 })
12713 .collect::<Vec<lsp::CompletionItem>>()
12714 );
12715 resolved_items.lock().clear();
12716}
12717
12718#[gpui::test]
12719async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12720 init_test(cx, |_| {});
12721
12722 let mut cx = EditorLspTestContext::new(
12723 Language::new(
12724 LanguageConfig {
12725 matcher: LanguageMatcher {
12726 path_suffixes: vec!["jsx".into()],
12727 ..Default::default()
12728 },
12729 overrides: [(
12730 "element".into(),
12731 LanguageConfigOverride {
12732 word_characters: Override::Set(['-'].into_iter().collect()),
12733 ..Default::default()
12734 },
12735 )]
12736 .into_iter()
12737 .collect(),
12738 ..Default::default()
12739 },
12740 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12741 )
12742 .with_override_query("(jsx_self_closing_element) @element")
12743 .unwrap(),
12744 lsp::ServerCapabilities {
12745 completion_provider: Some(lsp::CompletionOptions {
12746 trigger_characters: Some(vec![":".to_string()]),
12747 ..Default::default()
12748 }),
12749 ..Default::default()
12750 },
12751 cx,
12752 )
12753 .await;
12754
12755 cx.lsp
12756 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12757 Ok(Some(lsp::CompletionResponse::Array(vec![
12758 lsp::CompletionItem {
12759 label: "bg-blue".into(),
12760 ..Default::default()
12761 },
12762 lsp::CompletionItem {
12763 label: "bg-red".into(),
12764 ..Default::default()
12765 },
12766 lsp::CompletionItem {
12767 label: "bg-yellow".into(),
12768 ..Default::default()
12769 },
12770 ])))
12771 });
12772
12773 cx.set_state(r#"<p class="bgˇ" />"#);
12774
12775 // Trigger completion when typing a dash, because the dash is an extra
12776 // word character in the 'element' scope, which contains the cursor.
12777 cx.simulate_keystroke("-");
12778 cx.executor().run_until_parked();
12779 cx.update_editor(|editor, _, _| {
12780 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12781 {
12782 assert_eq!(
12783 completion_menu_entries(&menu),
12784 &["bg-red", "bg-blue", "bg-yellow"]
12785 );
12786 } else {
12787 panic!("expected completion menu to be open");
12788 }
12789 });
12790
12791 cx.simulate_keystroke("l");
12792 cx.executor().run_until_parked();
12793 cx.update_editor(|editor, _, _| {
12794 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12795 {
12796 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12797 } else {
12798 panic!("expected completion menu to be open");
12799 }
12800 });
12801
12802 // When filtering completions, consider the character after the '-' to
12803 // be the start of a subword.
12804 cx.set_state(r#"<p class="yelˇ" />"#);
12805 cx.simulate_keystroke("l");
12806 cx.executor().run_until_parked();
12807 cx.update_editor(|editor, _, _| {
12808 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12809 {
12810 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12811 } else {
12812 panic!("expected completion menu to be open");
12813 }
12814 });
12815}
12816
12817fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12818 let entries = menu.entries.borrow();
12819 entries.iter().map(|mat| mat.string.clone()).collect()
12820}
12821
12822#[gpui::test]
12823async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12824 init_test(cx, |settings| {
12825 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12826 FormatterList(vec![Formatter::Prettier].into()),
12827 ))
12828 });
12829
12830 let fs = FakeFs::new(cx.executor());
12831 fs.insert_file(path!("/file.ts"), Default::default()).await;
12832
12833 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12834 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12835
12836 language_registry.add(Arc::new(Language::new(
12837 LanguageConfig {
12838 name: "TypeScript".into(),
12839 matcher: LanguageMatcher {
12840 path_suffixes: vec!["ts".to_string()],
12841 ..Default::default()
12842 },
12843 ..Default::default()
12844 },
12845 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12846 )));
12847 update_test_language_settings(cx, |settings| {
12848 settings.defaults.prettier = Some(PrettierSettings {
12849 allowed: true,
12850 ..PrettierSettings::default()
12851 });
12852 });
12853
12854 let test_plugin = "test_plugin";
12855 let _ = language_registry.register_fake_lsp(
12856 "TypeScript",
12857 FakeLspAdapter {
12858 prettier_plugins: vec![test_plugin],
12859 ..Default::default()
12860 },
12861 );
12862
12863 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12864 let buffer = project
12865 .update(cx, |project, cx| {
12866 project.open_local_buffer(path!("/file.ts"), cx)
12867 })
12868 .await
12869 .unwrap();
12870
12871 let buffer_text = "one\ntwo\nthree\n";
12872 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12873 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12874 editor.update_in(cx, |editor, window, cx| {
12875 editor.set_text(buffer_text, window, cx)
12876 });
12877
12878 editor
12879 .update_in(cx, |editor, window, cx| {
12880 editor.perform_format(
12881 project.clone(),
12882 FormatTrigger::Manual,
12883 FormatTarget::Buffers,
12884 window,
12885 cx,
12886 )
12887 })
12888 .unwrap()
12889 .await;
12890 assert_eq!(
12891 editor.update(cx, |editor, cx| editor.text(cx)),
12892 buffer_text.to_string() + prettier_format_suffix,
12893 "Test prettier formatting was not applied to the original buffer text",
12894 );
12895
12896 update_test_language_settings(cx, |settings| {
12897 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12898 });
12899 let format = editor.update_in(cx, |editor, window, cx| {
12900 editor.perform_format(
12901 project.clone(),
12902 FormatTrigger::Manual,
12903 FormatTarget::Buffers,
12904 window,
12905 cx,
12906 )
12907 });
12908 format.await.unwrap();
12909 assert_eq!(
12910 editor.update(cx, |editor, cx| editor.text(cx)),
12911 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12912 "Autoformatting (via test prettier) was not applied to the original buffer text",
12913 );
12914}
12915
12916#[gpui::test]
12917async fn test_addition_reverts(cx: &mut TestAppContext) {
12918 init_test(cx, |_| {});
12919 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12920 let base_text = indoc! {r#"
12921 struct Row;
12922 struct Row1;
12923 struct Row2;
12924
12925 struct Row4;
12926 struct Row5;
12927 struct Row6;
12928
12929 struct Row8;
12930 struct Row9;
12931 struct Row10;"#};
12932
12933 // When addition hunks are not adjacent to carets, no hunk revert is performed
12934 assert_hunk_revert(
12935 indoc! {r#"struct Row;
12936 struct Row1;
12937 struct Row1.1;
12938 struct Row1.2;
12939 struct Row2;ˇ
12940
12941 struct Row4;
12942 struct Row5;
12943 struct Row6;
12944
12945 struct Row8;
12946 ˇstruct Row9;
12947 struct Row9.1;
12948 struct Row9.2;
12949 struct Row9.3;
12950 struct Row10;"#},
12951 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12952 indoc! {r#"struct Row;
12953 struct Row1;
12954 struct Row1.1;
12955 struct Row1.2;
12956 struct Row2;ˇ
12957
12958 struct Row4;
12959 struct Row5;
12960 struct Row6;
12961
12962 struct Row8;
12963 ˇstruct Row9;
12964 struct Row9.1;
12965 struct Row9.2;
12966 struct Row9.3;
12967 struct Row10;"#},
12968 base_text,
12969 &mut cx,
12970 );
12971 // Same for selections
12972 assert_hunk_revert(
12973 indoc! {r#"struct Row;
12974 struct Row1;
12975 struct Row2;
12976 struct Row2.1;
12977 struct Row2.2;
12978 «ˇ
12979 struct Row4;
12980 struct» Row5;
12981 «struct Row6;
12982 ˇ»
12983 struct Row9.1;
12984 struct Row9.2;
12985 struct Row9.3;
12986 struct Row8;
12987 struct Row9;
12988 struct Row10;"#},
12989 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12990 indoc! {r#"struct Row;
12991 struct Row1;
12992 struct Row2;
12993 struct Row2.1;
12994 struct Row2.2;
12995 «ˇ
12996 struct Row4;
12997 struct» Row5;
12998 «struct Row6;
12999 ˇ»
13000 struct Row9.1;
13001 struct Row9.2;
13002 struct Row9.3;
13003 struct Row8;
13004 struct Row9;
13005 struct Row10;"#},
13006 base_text,
13007 &mut cx,
13008 );
13009
13010 // When carets and selections intersect the addition hunks, those are reverted.
13011 // Adjacent carets got merged.
13012 assert_hunk_revert(
13013 indoc! {r#"struct Row;
13014 ˇ// something on the top
13015 struct Row1;
13016 struct Row2;
13017 struct Roˇw3.1;
13018 struct Row2.2;
13019 struct Row2.3;ˇ
13020
13021 struct Row4;
13022 struct ˇRow5.1;
13023 struct Row5.2;
13024 struct «Rowˇ»5.3;
13025 struct Row5;
13026 struct Row6;
13027 ˇ
13028 struct Row9.1;
13029 struct «Rowˇ»9.2;
13030 struct «ˇRow»9.3;
13031 struct Row8;
13032 struct Row9;
13033 «ˇ// something on bottom»
13034 struct Row10;"#},
13035 vec![
13036 DiffHunkStatusKind::Added,
13037 DiffHunkStatusKind::Added,
13038 DiffHunkStatusKind::Added,
13039 DiffHunkStatusKind::Added,
13040 DiffHunkStatusKind::Added,
13041 ],
13042 indoc! {r#"struct Row;
13043 ˇstruct Row1;
13044 struct Row2;
13045 ˇ
13046 struct Row4;
13047 ˇstruct Row5;
13048 struct Row6;
13049 ˇ
13050 ˇstruct Row8;
13051 struct Row9;
13052 ˇstruct Row10;"#},
13053 base_text,
13054 &mut cx,
13055 );
13056}
13057
13058#[gpui::test]
13059async fn test_modification_reverts(cx: &mut TestAppContext) {
13060 init_test(cx, |_| {});
13061 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13062 let base_text = indoc! {r#"
13063 struct Row;
13064 struct Row1;
13065 struct Row2;
13066
13067 struct Row4;
13068 struct Row5;
13069 struct Row6;
13070
13071 struct Row8;
13072 struct Row9;
13073 struct Row10;"#};
13074
13075 // Modification hunks behave the same as the addition ones.
13076 assert_hunk_revert(
13077 indoc! {r#"struct Row;
13078 struct Row1;
13079 struct Row33;
13080 ˇ
13081 struct Row4;
13082 struct Row5;
13083 struct Row6;
13084 ˇ
13085 struct Row99;
13086 struct Row9;
13087 struct Row10;"#},
13088 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13089 indoc! {r#"struct Row;
13090 struct Row1;
13091 struct Row33;
13092 ˇ
13093 struct Row4;
13094 struct Row5;
13095 struct Row6;
13096 ˇ
13097 struct Row99;
13098 struct Row9;
13099 struct Row10;"#},
13100 base_text,
13101 &mut cx,
13102 );
13103 assert_hunk_revert(
13104 indoc! {r#"struct Row;
13105 struct Row1;
13106 struct Row33;
13107 «ˇ
13108 struct Row4;
13109 struct» Row5;
13110 «struct Row6;
13111 ˇ»
13112 struct Row99;
13113 struct Row9;
13114 struct Row10;"#},
13115 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13116 indoc! {r#"struct Row;
13117 struct Row1;
13118 struct Row33;
13119 «ˇ
13120 struct Row4;
13121 struct» Row5;
13122 «struct Row6;
13123 ˇ»
13124 struct Row99;
13125 struct Row9;
13126 struct Row10;"#},
13127 base_text,
13128 &mut cx,
13129 );
13130
13131 assert_hunk_revert(
13132 indoc! {r#"ˇstruct Row1.1;
13133 struct Row1;
13134 «ˇstr»uct Row22;
13135
13136 struct ˇRow44;
13137 struct Row5;
13138 struct «Rˇ»ow66;ˇ
13139
13140 «struˇ»ct Row88;
13141 struct Row9;
13142 struct Row1011;ˇ"#},
13143 vec![
13144 DiffHunkStatusKind::Modified,
13145 DiffHunkStatusKind::Modified,
13146 DiffHunkStatusKind::Modified,
13147 DiffHunkStatusKind::Modified,
13148 DiffHunkStatusKind::Modified,
13149 DiffHunkStatusKind::Modified,
13150 ],
13151 indoc! {r#"struct Row;
13152 ˇstruct Row1;
13153 struct Row2;
13154 ˇ
13155 struct Row4;
13156 ˇstruct Row5;
13157 struct Row6;
13158 ˇ
13159 struct Row8;
13160 ˇstruct Row9;
13161 struct Row10;ˇ"#},
13162 base_text,
13163 &mut cx,
13164 );
13165}
13166
13167#[gpui::test]
13168async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13169 init_test(cx, |_| {});
13170 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13171 let base_text = indoc! {r#"
13172 one
13173
13174 two
13175 three
13176 "#};
13177
13178 cx.set_head_text(base_text);
13179 cx.set_state("\nˇ\n");
13180 cx.executor().run_until_parked();
13181 cx.update_editor(|editor, _window, cx| {
13182 editor.expand_selected_diff_hunks(cx);
13183 });
13184 cx.executor().run_until_parked();
13185 cx.update_editor(|editor, window, cx| {
13186 editor.backspace(&Default::default(), window, cx);
13187 });
13188 cx.run_until_parked();
13189 cx.assert_state_with_diff(
13190 indoc! {r#"
13191
13192 - two
13193 - threeˇ
13194 +
13195 "#}
13196 .to_string(),
13197 );
13198}
13199
13200#[gpui::test]
13201async fn test_deletion_reverts(cx: &mut TestAppContext) {
13202 init_test(cx, |_| {});
13203 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13204 let base_text = indoc! {r#"struct Row;
13205struct Row1;
13206struct Row2;
13207
13208struct Row4;
13209struct Row5;
13210struct Row6;
13211
13212struct Row8;
13213struct Row9;
13214struct Row10;"#};
13215
13216 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13217 assert_hunk_revert(
13218 indoc! {r#"struct Row;
13219 struct Row2;
13220
13221 ˇstruct Row4;
13222 struct Row5;
13223 struct Row6;
13224 ˇ
13225 struct Row8;
13226 struct Row10;"#},
13227 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13228 indoc! {r#"struct Row;
13229 struct Row2;
13230
13231 ˇstruct Row4;
13232 struct Row5;
13233 struct Row6;
13234 ˇ
13235 struct Row8;
13236 struct Row10;"#},
13237 base_text,
13238 &mut cx,
13239 );
13240 assert_hunk_revert(
13241 indoc! {r#"struct Row;
13242 struct Row2;
13243
13244 «ˇstruct Row4;
13245 struct» Row5;
13246 «struct Row6;
13247 ˇ»
13248 struct Row8;
13249 struct Row10;"#},
13250 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13251 indoc! {r#"struct Row;
13252 struct Row2;
13253
13254 «ˇstruct Row4;
13255 struct» Row5;
13256 «struct Row6;
13257 ˇ»
13258 struct Row8;
13259 struct Row10;"#},
13260 base_text,
13261 &mut cx,
13262 );
13263
13264 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13265 assert_hunk_revert(
13266 indoc! {r#"struct Row;
13267 ˇstruct Row2;
13268
13269 struct Row4;
13270 struct Row5;
13271 struct Row6;
13272
13273 struct Row8;ˇ
13274 struct Row10;"#},
13275 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13276 indoc! {r#"struct Row;
13277 struct Row1;
13278 ˇstruct Row2;
13279
13280 struct Row4;
13281 struct Row5;
13282 struct Row6;
13283
13284 struct Row8;ˇ
13285 struct Row9;
13286 struct Row10;"#},
13287 base_text,
13288 &mut cx,
13289 );
13290 assert_hunk_revert(
13291 indoc! {r#"struct Row;
13292 struct Row2«ˇ;
13293 struct Row4;
13294 struct» Row5;
13295 «struct Row6;
13296
13297 struct Row8;ˇ»
13298 struct Row10;"#},
13299 vec![
13300 DiffHunkStatusKind::Deleted,
13301 DiffHunkStatusKind::Deleted,
13302 DiffHunkStatusKind::Deleted,
13303 ],
13304 indoc! {r#"struct Row;
13305 struct Row1;
13306 struct Row2«ˇ;
13307
13308 struct Row4;
13309 struct» Row5;
13310 «struct Row6;
13311
13312 struct Row8;ˇ»
13313 struct Row9;
13314 struct Row10;"#},
13315 base_text,
13316 &mut cx,
13317 );
13318}
13319
13320#[gpui::test]
13321async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13322 init_test(cx, |_| {});
13323
13324 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13325 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13326 let base_text_3 =
13327 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13328
13329 let text_1 = edit_first_char_of_every_line(base_text_1);
13330 let text_2 = edit_first_char_of_every_line(base_text_2);
13331 let text_3 = edit_first_char_of_every_line(base_text_3);
13332
13333 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13334 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13335 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13336
13337 let multibuffer = cx.new(|cx| {
13338 let mut multibuffer = MultiBuffer::new(ReadWrite);
13339 multibuffer.push_excerpts(
13340 buffer_1.clone(),
13341 [
13342 ExcerptRange {
13343 context: Point::new(0, 0)..Point::new(3, 0),
13344 primary: None,
13345 },
13346 ExcerptRange {
13347 context: Point::new(5, 0)..Point::new(7, 0),
13348 primary: None,
13349 },
13350 ExcerptRange {
13351 context: Point::new(9, 0)..Point::new(10, 4),
13352 primary: None,
13353 },
13354 ],
13355 cx,
13356 );
13357 multibuffer.push_excerpts(
13358 buffer_2.clone(),
13359 [
13360 ExcerptRange {
13361 context: Point::new(0, 0)..Point::new(3, 0),
13362 primary: None,
13363 },
13364 ExcerptRange {
13365 context: Point::new(5, 0)..Point::new(7, 0),
13366 primary: None,
13367 },
13368 ExcerptRange {
13369 context: Point::new(9, 0)..Point::new(10, 4),
13370 primary: None,
13371 },
13372 ],
13373 cx,
13374 );
13375 multibuffer.push_excerpts(
13376 buffer_3.clone(),
13377 [
13378 ExcerptRange {
13379 context: Point::new(0, 0)..Point::new(3, 0),
13380 primary: None,
13381 },
13382 ExcerptRange {
13383 context: Point::new(5, 0)..Point::new(7, 0),
13384 primary: None,
13385 },
13386 ExcerptRange {
13387 context: Point::new(9, 0)..Point::new(10, 4),
13388 primary: None,
13389 },
13390 ],
13391 cx,
13392 );
13393 multibuffer
13394 });
13395
13396 let fs = FakeFs::new(cx.executor());
13397 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13398 let (editor, cx) = cx
13399 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13400 editor.update_in(cx, |editor, _window, cx| {
13401 for (buffer, diff_base) in [
13402 (buffer_1.clone(), base_text_1),
13403 (buffer_2.clone(), base_text_2),
13404 (buffer_3.clone(), base_text_3),
13405 ] {
13406 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13407 editor
13408 .buffer
13409 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13410 }
13411 });
13412 cx.executor().run_until_parked();
13413
13414 editor.update_in(cx, |editor, window, cx| {
13415 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}");
13416 editor.select_all(&SelectAll, window, cx);
13417 editor.git_restore(&Default::default(), window, cx);
13418 });
13419 cx.executor().run_until_parked();
13420
13421 // When all ranges are selected, all buffer hunks are reverted.
13422 editor.update(cx, |editor, cx| {
13423 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");
13424 });
13425 buffer_1.update(cx, |buffer, _| {
13426 assert_eq!(buffer.text(), base_text_1);
13427 });
13428 buffer_2.update(cx, |buffer, _| {
13429 assert_eq!(buffer.text(), base_text_2);
13430 });
13431 buffer_3.update(cx, |buffer, _| {
13432 assert_eq!(buffer.text(), base_text_3);
13433 });
13434
13435 editor.update_in(cx, |editor, window, cx| {
13436 editor.undo(&Default::default(), window, cx);
13437 });
13438
13439 editor.update_in(cx, |editor, window, cx| {
13440 editor.change_selections(None, window, cx, |s| {
13441 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13442 });
13443 editor.git_restore(&Default::default(), window, cx);
13444 });
13445
13446 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13447 // but not affect buffer_2 and its related excerpts.
13448 editor.update(cx, |editor, cx| {
13449 assert_eq!(
13450 editor.text(cx),
13451 "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}"
13452 );
13453 });
13454 buffer_1.update(cx, |buffer, _| {
13455 assert_eq!(buffer.text(), base_text_1);
13456 });
13457 buffer_2.update(cx, |buffer, _| {
13458 assert_eq!(
13459 buffer.text(),
13460 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13461 );
13462 });
13463 buffer_3.update(cx, |buffer, _| {
13464 assert_eq!(
13465 buffer.text(),
13466 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13467 );
13468 });
13469
13470 fn edit_first_char_of_every_line(text: &str) -> String {
13471 text.split('\n')
13472 .map(|line| format!("X{}", &line[1..]))
13473 .collect::<Vec<_>>()
13474 .join("\n")
13475 }
13476}
13477
13478#[gpui::test]
13479async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13480 init_test(cx, |_| {});
13481
13482 let cols = 4;
13483 let rows = 10;
13484 let sample_text_1 = sample_text(rows, cols, 'a');
13485 assert_eq!(
13486 sample_text_1,
13487 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13488 );
13489 let sample_text_2 = sample_text(rows, cols, 'l');
13490 assert_eq!(
13491 sample_text_2,
13492 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13493 );
13494 let sample_text_3 = sample_text(rows, cols, 'v');
13495 assert_eq!(
13496 sample_text_3,
13497 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13498 );
13499
13500 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13501 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13502 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13503
13504 let multi_buffer = cx.new(|cx| {
13505 let mut multibuffer = MultiBuffer::new(ReadWrite);
13506 multibuffer.push_excerpts(
13507 buffer_1.clone(),
13508 [
13509 ExcerptRange {
13510 context: Point::new(0, 0)..Point::new(3, 0),
13511 primary: None,
13512 },
13513 ExcerptRange {
13514 context: Point::new(5, 0)..Point::new(7, 0),
13515 primary: None,
13516 },
13517 ExcerptRange {
13518 context: Point::new(9, 0)..Point::new(10, 4),
13519 primary: None,
13520 },
13521 ],
13522 cx,
13523 );
13524 multibuffer.push_excerpts(
13525 buffer_2.clone(),
13526 [
13527 ExcerptRange {
13528 context: Point::new(0, 0)..Point::new(3, 0),
13529 primary: None,
13530 },
13531 ExcerptRange {
13532 context: Point::new(5, 0)..Point::new(7, 0),
13533 primary: None,
13534 },
13535 ExcerptRange {
13536 context: Point::new(9, 0)..Point::new(10, 4),
13537 primary: None,
13538 },
13539 ],
13540 cx,
13541 );
13542 multibuffer.push_excerpts(
13543 buffer_3.clone(),
13544 [
13545 ExcerptRange {
13546 context: Point::new(0, 0)..Point::new(3, 0),
13547 primary: None,
13548 },
13549 ExcerptRange {
13550 context: Point::new(5, 0)..Point::new(7, 0),
13551 primary: None,
13552 },
13553 ExcerptRange {
13554 context: Point::new(9, 0)..Point::new(10, 4),
13555 primary: None,
13556 },
13557 ],
13558 cx,
13559 );
13560 multibuffer
13561 });
13562
13563 let fs = FakeFs::new(cx.executor());
13564 fs.insert_tree(
13565 "/a",
13566 json!({
13567 "main.rs": sample_text_1,
13568 "other.rs": sample_text_2,
13569 "lib.rs": sample_text_3,
13570 }),
13571 )
13572 .await;
13573 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13574 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13575 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13576 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13577 Editor::new(
13578 EditorMode::Full,
13579 multi_buffer,
13580 Some(project.clone()),
13581 window,
13582 cx,
13583 )
13584 });
13585 let multibuffer_item_id = workspace
13586 .update(cx, |workspace, window, cx| {
13587 assert!(
13588 workspace.active_item(cx).is_none(),
13589 "active item should be None before the first item is added"
13590 );
13591 workspace.add_item_to_active_pane(
13592 Box::new(multi_buffer_editor.clone()),
13593 None,
13594 true,
13595 window,
13596 cx,
13597 );
13598 let active_item = workspace
13599 .active_item(cx)
13600 .expect("should have an active item after adding the multi buffer");
13601 assert!(
13602 !active_item.is_singleton(cx),
13603 "A multi buffer was expected to active after adding"
13604 );
13605 active_item.item_id()
13606 })
13607 .unwrap();
13608 cx.executor().run_until_parked();
13609
13610 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13611 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13612 s.select_ranges(Some(1..2))
13613 });
13614 editor.open_excerpts(&OpenExcerpts, window, cx);
13615 });
13616 cx.executor().run_until_parked();
13617 let first_item_id = workspace
13618 .update(cx, |workspace, window, cx| {
13619 let active_item = workspace
13620 .active_item(cx)
13621 .expect("should have an active item after navigating into the 1st buffer");
13622 let first_item_id = active_item.item_id();
13623 assert_ne!(
13624 first_item_id, multibuffer_item_id,
13625 "Should navigate into the 1st buffer and activate it"
13626 );
13627 assert!(
13628 active_item.is_singleton(cx),
13629 "New active item should be a singleton buffer"
13630 );
13631 assert_eq!(
13632 active_item
13633 .act_as::<Editor>(cx)
13634 .expect("should have navigated into an editor for the 1st buffer")
13635 .read(cx)
13636 .text(cx),
13637 sample_text_1
13638 );
13639
13640 workspace
13641 .go_back(workspace.active_pane().downgrade(), window, cx)
13642 .detach_and_log_err(cx);
13643
13644 first_item_id
13645 })
13646 .unwrap();
13647 cx.executor().run_until_parked();
13648 workspace
13649 .update(cx, |workspace, _, cx| {
13650 let active_item = workspace
13651 .active_item(cx)
13652 .expect("should have an active item after navigating back");
13653 assert_eq!(
13654 active_item.item_id(),
13655 multibuffer_item_id,
13656 "Should navigate back to the multi buffer"
13657 );
13658 assert!(!active_item.is_singleton(cx));
13659 })
13660 .unwrap();
13661
13662 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13663 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13664 s.select_ranges(Some(39..40))
13665 });
13666 editor.open_excerpts(&OpenExcerpts, window, cx);
13667 });
13668 cx.executor().run_until_parked();
13669 let second_item_id = workspace
13670 .update(cx, |workspace, window, cx| {
13671 let active_item = workspace
13672 .active_item(cx)
13673 .expect("should have an active item after navigating into the 2nd buffer");
13674 let second_item_id = active_item.item_id();
13675 assert_ne!(
13676 second_item_id, multibuffer_item_id,
13677 "Should navigate away from the multibuffer"
13678 );
13679 assert_ne!(
13680 second_item_id, first_item_id,
13681 "Should navigate into the 2nd buffer and activate it"
13682 );
13683 assert!(
13684 active_item.is_singleton(cx),
13685 "New active item should be a singleton buffer"
13686 );
13687 assert_eq!(
13688 active_item
13689 .act_as::<Editor>(cx)
13690 .expect("should have navigated into an editor")
13691 .read(cx)
13692 .text(cx),
13693 sample_text_2
13694 );
13695
13696 workspace
13697 .go_back(workspace.active_pane().downgrade(), window, cx)
13698 .detach_and_log_err(cx);
13699
13700 second_item_id
13701 })
13702 .unwrap();
13703 cx.executor().run_until_parked();
13704 workspace
13705 .update(cx, |workspace, _, cx| {
13706 let active_item = workspace
13707 .active_item(cx)
13708 .expect("should have an active item after navigating back from the 2nd buffer");
13709 assert_eq!(
13710 active_item.item_id(),
13711 multibuffer_item_id,
13712 "Should navigate back from the 2nd buffer to the multi buffer"
13713 );
13714 assert!(!active_item.is_singleton(cx));
13715 })
13716 .unwrap();
13717
13718 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13719 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13720 s.select_ranges(Some(70..70))
13721 });
13722 editor.open_excerpts(&OpenExcerpts, window, cx);
13723 });
13724 cx.executor().run_until_parked();
13725 workspace
13726 .update(cx, |workspace, window, cx| {
13727 let active_item = workspace
13728 .active_item(cx)
13729 .expect("should have an active item after navigating into the 3rd buffer");
13730 let third_item_id = active_item.item_id();
13731 assert_ne!(
13732 third_item_id, multibuffer_item_id,
13733 "Should navigate into the 3rd buffer and activate it"
13734 );
13735 assert_ne!(third_item_id, first_item_id);
13736 assert_ne!(third_item_id, second_item_id);
13737 assert!(
13738 active_item.is_singleton(cx),
13739 "New active item should be a singleton buffer"
13740 );
13741 assert_eq!(
13742 active_item
13743 .act_as::<Editor>(cx)
13744 .expect("should have navigated into an editor")
13745 .read(cx)
13746 .text(cx),
13747 sample_text_3
13748 );
13749
13750 workspace
13751 .go_back(workspace.active_pane().downgrade(), window, cx)
13752 .detach_and_log_err(cx);
13753 })
13754 .unwrap();
13755 cx.executor().run_until_parked();
13756 workspace
13757 .update(cx, |workspace, _, cx| {
13758 let active_item = workspace
13759 .active_item(cx)
13760 .expect("should have an active item after navigating back from the 3rd buffer");
13761 assert_eq!(
13762 active_item.item_id(),
13763 multibuffer_item_id,
13764 "Should navigate back from the 3rd buffer to the multi buffer"
13765 );
13766 assert!(!active_item.is_singleton(cx));
13767 })
13768 .unwrap();
13769}
13770
13771#[gpui::test]
13772async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13773 init_test(cx, |_| {});
13774
13775 let mut cx = EditorTestContext::new(cx).await;
13776
13777 let diff_base = r#"
13778 use some::mod;
13779
13780 const A: u32 = 42;
13781
13782 fn main() {
13783 println!("hello");
13784
13785 println!("world");
13786 }
13787 "#
13788 .unindent();
13789
13790 cx.set_state(
13791 &r#"
13792 use some::modified;
13793
13794 ˇ
13795 fn main() {
13796 println!("hello there");
13797
13798 println!("around the");
13799 println!("world");
13800 }
13801 "#
13802 .unindent(),
13803 );
13804
13805 cx.set_head_text(&diff_base);
13806 executor.run_until_parked();
13807
13808 cx.update_editor(|editor, window, cx| {
13809 editor.go_to_next_hunk(&GoToHunk, window, cx);
13810 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13811 });
13812 executor.run_until_parked();
13813 cx.assert_state_with_diff(
13814 r#"
13815 use some::modified;
13816
13817
13818 fn main() {
13819 - println!("hello");
13820 + ˇ println!("hello there");
13821
13822 println!("around the");
13823 println!("world");
13824 }
13825 "#
13826 .unindent(),
13827 );
13828
13829 cx.update_editor(|editor, window, cx| {
13830 for _ in 0..2 {
13831 editor.go_to_next_hunk(&GoToHunk, window, cx);
13832 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13833 }
13834 });
13835 executor.run_until_parked();
13836 cx.assert_state_with_diff(
13837 r#"
13838 - use some::mod;
13839 + ˇuse some::modified;
13840
13841
13842 fn main() {
13843 - println!("hello");
13844 + println!("hello there");
13845
13846 + println!("around the");
13847 println!("world");
13848 }
13849 "#
13850 .unindent(),
13851 );
13852
13853 cx.update_editor(|editor, window, cx| {
13854 editor.go_to_next_hunk(&GoToHunk, window, cx);
13855 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13856 });
13857 executor.run_until_parked();
13858 cx.assert_state_with_diff(
13859 r#"
13860 - use some::mod;
13861 + use some::modified;
13862
13863 - const A: u32 = 42;
13864 ˇ
13865 fn main() {
13866 - println!("hello");
13867 + println!("hello there");
13868
13869 + println!("around the");
13870 println!("world");
13871 }
13872 "#
13873 .unindent(),
13874 );
13875
13876 cx.update_editor(|editor, window, cx| {
13877 editor.cancel(&Cancel, window, cx);
13878 });
13879
13880 cx.assert_state_with_diff(
13881 r#"
13882 use some::modified;
13883
13884 ˇ
13885 fn main() {
13886 println!("hello there");
13887
13888 println!("around the");
13889 println!("world");
13890 }
13891 "#
13892 .unindent(),
13893 );
13894}
13895
13896#[gpui::test]
13897async fn test_diff_base_change_with_expanded_diff_hunks(
13898 executor: BackgroundExecutor,
13899 cx: &mut TestAppContext,
13900) {
13901 init_test(cx, |_| {});
13902
13903 let mut cx = EditorTestContext::new(cx).await;
13904
13905 let diff_base = r#"
13906 use some::mod1;
13907 use some::mod2;
13908
13909 const A: u32 = 42;
13910 const B: u32 = 42;
13911 const C: u32 = 42;
13912
13913 fn main() {
13914 println!("hello");
13915
13916 println!("world");
13917 }
13918 "#
13919 .unindent();
13920
13921 cx.set_state(
13922 &r#"
13923 use some::mod2;
13924
13925 const A: u32 = 42;
13926 const C: u32 = 42;
13927
13928 fn main(ˇ) {
13929 //println!("hello");
13930
13931 println!("world");
13932 //
13933 //
13934 }
13935 "#
13936 .unindent(),
13937 );
13938
13939 cx.set_head_text(&diff_base);
13940 executor.run_until_parked();
13941
13942 cx.update_editor(|editor, window, cx| {
13943 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13944 });
13945 executor.run_until_parked();
13946 cx.assert_state_with_diff(
13947 r#"
13948 - use some::mod1;
13949 use some::mod2;
13950
13951 const A: u32 = 42;
13952 - const B: u32 = 42;
13953 const C: u32 = 42;
13954
13955 fn main(ˇ) {
13956 - println!("hello");
13957 + //println!("hello");
13958
13959 println!("world");
13960 + //
13961 + //
13962 }
13963 "#
13964 .unindent(),
13965 );
13966
13967 cx.set_head_text("new diff base!");
13968 executor.run_until_parked();
13969 cx.assert_state_with_diff(
13970 r#"
13971 - new diff base!
13972 + use some::mod2;
13973 +
13974 + const A: u32 = 42;
13975 + const C: u32 = 42;
13976 +
13977 + fn main(ˇ) {
13978 + //println!("hello");
13979 +
13980 + println!("world");
13981 + //
13982 + //
13983 + }
13984 "#
13985 .unindent(),
13986 );
13987}
13988
13989#[gpui::test]
13990async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13991 init_test(cx, |_| {});
13992
13993 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13994 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13995 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13996 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13997 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13998 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13999
14000 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14001 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14002 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14003
14004 let multi_buffer = cx.new(|cx| {
14005 let mut multibuffer = MultiBuffer::new(ReadWrite);
14006 multibuffer.push_excerpts(
14007 buffer_1.clone(),
14008 [
14009 ExcerptRange {
14010 context: Point::new(0, 0)..Point::new(3, 0),
14011 primary: None,
14012 },
14013 ExcerptRange {
14014 context: Point::new(5, 0)..Point::new(7, 0),
14015 primary: None,
14016 },
14017 ExcerptRange {
14018 context: Point::new(9, 0)..Point::new(10, 3),
14019 primary: None,
14020 },
14021 ],
14022 cx,
14023 );
14024 multibuffer.push_excerpts(
14025 buffer_2.clone(),
14026 [
14027 ExcerptRange {
14028 context: Point::new(0, 0)..Point::new(3, 0),
14029 primary: None,
14030 },
14031 ExcerptRange {
14032 context: Point::new(5, 0)..Point::new(7, 0),
14033 primary: None,
14034 },
14035 ExcerptRange {
14036 context: Point::new(9, 0)..Point::new(10, 3),
14037 primary: None,
14038 },
14039 ],
14040 cx,
14041 );
14042 multibuffer.push_excerpts(
14043 buffer_3.clone(),
14044 [
14045 ExcerptRange {
14046 context: Point::new(0, 0)..Point::new(3, 0),
14047 primary: None,
14048 },
14049 ExcerptRange {
14050 context: Point::new(5, 0)..Point::new(7, 0),
14051 primary: None,
14052 },
14053 ExcerptRange {
14054 context: Point::new(9, 0)..Point::new(10, 3),
14055 primary: None,
14056 },
14057 ],
14058 cx,
14059 );
14060 multibuffer
14061 });
14062
14063 let editor =
14064 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14065 editor
14066 .update(cx, |editor, _window, cx| {
14067 for (buffer, diff_base) in [
14068 (buffer_1.clone(), file_1_old),
14069 (buffer_2.clone(), file_2_old),
14070 (buffer_3.clone(), file_3_old),
14071 ] {
14072 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14073 editor
14074 .buffer
14075 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14076 }
14077 })
14078 .unwrap();
14079
14080 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14081 cx.run_until_parked();
14082
14083 cx.assert_editor_state(
14084 &"
14085 ˇaaa
14086 ccc
14087 ddd
14088
14089 ggg
14090 hhh
14091
14092
14093 lll
14094 mmm
14095 NNN
14096
14097 qqq
14098 rrr
14099
14100 uuu
14101 111
14102 222
14103 333
14104
14105 666
14106 777
14107
14108 000
14109 !!!"
14110 .unindent(),
14111 );
14112
14113 cx.update_editor(|editor, window, cx| {
14114 editor.select_all(&SelectAll, window, cx);
14115 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14116 });
14117 cx.executor().run_until_parked();
14118
14119 cx.assert_state_with_diff(
14120 "
14121 «aaa
14122 - bbb
14123 ccc
14124 ddd
14125
14126 ggg
14127 hhh
14128
14129
14130 lll
14131 mmm
14132 - nnn
14133 + NNN
14134
14135 qqq
14136 rrr
14137
14138 uuu
14139 111
14140 222
14141 333
14142
14143 + 666
14144 777
14145
14146 000
14147 !!!ˇ»"
14148 .unindent(),
14149 );
14150}
14151
14152#[gpui::test]
14153async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14154 init_test(cx, |_| {});
14155
14156 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14157 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14158
14159 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14160 let multi_buffer = cx.new(|cx| {
14161 let mut multibuffer = MultiBuffer::new(ReadWrite);
14162 multibuffer.push_excerpts(
14163 buffer.clone(),
14164 [
14165 ExcerptRange {
14166 context: Point::new(0, 0)..Point::new(2, 0),
14167 primary: None,
14168 },
14169 ExcerptRange {
14170 context: Point::new(4, 0)..Point::new(7, 0),
14171 primary: None,
14172 },
14173 ExcerptRange {
14174 context: Point::new(9, 0)..Point::new(10, 0),
14175 primary: None,
14176 },
14177 ],
14178 cx,
14179 );
14180 multibuffer
14181 });
14182
14183 let editor =
14184 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14185 editor
14186 .update(cx, |editor, _window, cx| {
14187 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14188 editor
14189 .buffer
14190 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14191 })
14192 .unwrap();
14193
14194 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14195 cx.run_until_parked();
14196
14197 cx.update_editor(|editor, window, cx| {
14198 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14199 });
14200 cx.executor().run_until_parked();
14201
14202 // When the start of a hunk coincides with the start of its excerpt,
14203 // the hunk is expanded. When the start of a a hunk is earlier than
14204 // the start of its excerpt, the hunk is not expanded.
14205 cx.assert_state_with_diff(
14206 "
14207 ˇaaa
14208 - bbb
14209 + BBB
14210
14211 - ddd
14212 - eee
14213 + DDD
14214 + EEE
14215 fff
14216
14217 iii
14218 "
14219 .unindent(),
14220 );
14221}
14222
14223#[gpui::test]
14224async fn test_edits_around_expanded_insertion_hunks(
14225 executor: BackgroundExecutor,
14226 cx: &mut TestAppContext,
14227) {
14228 init_test(cx, |_| {});
14229
14230 let mut cx = EditorTestContext::new(cx).await;
14231
14232 let diff_base = r#"
14233 use some::mod1;
14234 use some::mod2;
14235
14236 const A: u32 = 42;
14237
14238 fn main() {
14239 println!("hello");
14240
14241 println!("world");
14242 }
14243 "#
14244 .unindent();
14245 executor.run_until_parked();
14246 cx.set_state(
14247 &r#"
14248 use some::mod1;
14249 use some::mod2;
14250
14251 const A: u32 = 42;
14252 const B: u32 = 42;
14253 const C: u32 = 42;
14254 ˇ
14255
14256 fn main() {
14257 println!("hello");
14258
14259 println!("world");
14260 }
14261 "#
14262 .unindent(),
14263 );
14264
14265 cx.set_head_text(&diff_base);
14266 executor.run_until_parked();
14267
14268 cx.update_editor(|editor, window, cx| {
14269 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14270 });
14271 executor.run_until_parked();
14272
14273 cx.assert_state_with_diff(
14274 r#"
14275 use some::mod1;
14276 use some::mod2;
14277
14278 const A: u32 = 42;
14279 + const B: u32 = 42;
14280 + const C: u32 = 42;
14281 + ˇ
14282
14283 fn main() {
14284 println!("hello");
14285
14286 println!("world");
14287 }
14288 "#
14289 .unindent(),
14290 );
14291
14292 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14293 executor.run_until_parked();
14294
14295 cx.assert_state_with_diff(
14296 r#"
14297 use some::mod1;
14298 use some::mod2;
14299
14300 const A: u32 = 42;
14301 + const B: u32 = 42;
14302 + const C: u32 = 42;
14303 + const D: u32 = 42;
14304 + ˇ
14305
14306 fn main() {
14307 println!("hello");
14308
14309 println!("world");
14310 }
14311 "#
14312 .unindent(),
14313 );
14314
14315 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14316 executor.run_until_parked();
14317
14318 cx.assert_state_with_diff(
14319 r#"
14320 use some::mod1;
14321 use some::mod2;
14322
14323 const A: u32 = 42;
14324 + const B: u32 = 42;
14325 + const C: u32 = 42;
14326 + const D: u32 = 42;
14327 + const E: u32 = 42;
14328 + ˇ
14329
14330 fn main() {
14331 println!("hello");
14332
14333 println!("world");
14334 }
14335 "#
14336 .unindent(),
14337 );
14338
14339 cx.update_editor(|editor, window, cx| {
14340 editor.delete_line(&DeleteLine, window, cx);
14341 });
14342 executor.run_until_parked();
14343
14344 cx.assert_state_with_diff(
14345 r#"
14346 use some::mod1;
14347 use some::mod2;
14348
14349 const A: u32 = 42;
14350 + const B: u32 = 42;
14351 + const C: u32 = 42;
14352 + const D: u32 = 42;
14353 + const E: u32 = 42;
14354 ˇ
14355 fn main() {
14356 println!("hello");
14357
14358 println!("world");
14359 }
14360 "#
14361 .unindent(),
14362 );
14363
14364 cx.update_editor(|editor, window, cx| {
14365 editor.move_up(&MoveUp, window, cx);
14366 editor.delete_line(&DeleteLine, window, cx);
14367 editor.move_up(&MoveUp, window, cx);
14368 editor.delete_line(&DeleteLine, window, cx);
14369 editor.move_up(&MoveUp, window, cx);
14370 editor.delete_line(&DeleteLine, window, cx);
14371 });
14372 executor.run_until_parked();
14373 cx.assert_state_with_diff(
14374 r#"
14375 use some::mod1;
14376 use some::mod2;
14377
14378 const A: u32 = 42;
14379 + const B: u32 = 42;
14380 ˇ
14381 fn main() {
14382 println!("hello");
14383
14384 println!("world");
14385 }
14386 "#
14387 .unindent(),
14388 );
14389
14390 cx.update_editor(|editor, window, cx| {
14391 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14392 editor.delete_line(&DeleteLine, window, cx);
14393 });
14394 executor.run_until_parked();
14395 cx.assert_state_with_diff(
14396 r#"
14397 ˇ
14398 fn main() {
14399 println!("hello");
14400
14401 println!("world");
14402 }
14403 "#
14404 .unindent(),
14405 );
14406}
14407
14408#[gpui::test]
14409async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14410 init_test(cx, |_| {});
14411
14412 let mut cx = EditorTestContext::new(cx).await;
14413 cx.set_head_text(indoc! { "
14414 one
14415 two
14416 three
14417 four
14418 five
14419 "
14420 });
14421 cx.set_state(indoc! { "
14422 one
14423 ˇthree
14424 five
14425 "});
14426 cx.run_until_parked();
14427 cx.update_editor(|editor, window, cx| {
14428 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14429 });
14430 cx.assert_state_with_diff(
14431 indoc! { "
14432 one
14433 - two
14434 ˇthree
14435 - four
14436 five
14437 "}
14438 .to_string(),
14439 );
14440 cx.update_editor(|editor, window, cx| {
14441 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14442 });
14443
14444 cx.assert_state_with_diff(
14445 indoc! { "
14446 one
14447 ˇthree
14448 five
14449 "}
14450 .to_string(),
14451 );
14452
14453 cx.set_state(indoc! { "
14454 one
14455 ˇTWO
14456 three
14457 four
14458 five
14459 "});
14460 cx.run_until_parked();
14461 cx.update_editor(|editor, window, cx| {
14462 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14463 });
14464
14465 cx.assert_state_with_diff(
14466 indoc! { "
14467 one
14468 - two
14469 + ˇTWO
14470 three
14471 four
14472 five
14473 "}
14474 .to_string(),
14475 );
14476 cx.update_editor(|editor, window, cx| {
14477 editor.move_up(&Default::default(), window, cx);
14478 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14479 });
14480 cx.assert_state_with_diff(
14481 indoc! { "
14482 one
14483 ˇTWO
14484 three
14485 four
14486 five
14487 "}
14488 .to_string(),
14489 );
14490}
14491
14492#[gpui::test]
14493async fn test_edits_around_expanded_deletion_hunks(
14494 executor: BackgroundExecutor,
14495 cx: &mut TestAppContext,
14496) {
14497 init_test(cx, |_| {});
14498
14499 let mut cx = EditorTestContext::new(cx).await;
14500
14501 let diff_base = r#"
14502 use some::mod1;
14503 use some::mod2;
14504
14505 const A: u32 = 42;
14506 const B: u32 = 42;
14507 const C: u32 = 42;
14508
14509
14510 fn main() {
14511 println!("hello");
14512
14513 println!("world");
14514 }
14515 "#
14516 .unindent();
14517 executor.run_until_parked();
14518 cx.set_state(
14519 &r#"
14520 use some::mod1;
14521 use some::mod2;
14522
14523 ˇconst B: u32 = 42;
14524 const C: u32 = 42;
14525
14526
14527 fn main() {
14528 println!("hello");
14529
14530 println!("world");
14531 }
14532 "#
14533 .unindent(),
14534 );
14535
14536 cx.set_head_text(&diff_base);
14537 executor.run_until_parked();
14538
14539 cx.update_editor(|editor, window, cx| {
14540 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14541 });
14542 executor.run_until_parked();
14543
14544 cx.assert_state_with_diff(
14545 r#"
14546 use some::mod1;
14547 use some::mod2;
14548
14549 - const A: u32 = 42;
14550 ˇconst B: u32 = 42;
14551 const C: u32 = 42;
14552
14553
14554 fn main() {
14555 println!("hello");
14556
14557 println!("world");
14558 }
14559 "#
14560 .unindent(),
14561 );
14562
14563 cx.update_editor(|editor, window, cx| {
14564 editor.delete_line(&DeleteLine, window, cx);
14565 });
14566 executor.run_until_parked();
14567 cx.assert_state_with_diff(
14568 r#"
14569 use some::mod1;
14570 use some::mod2;
14571
14572 - const A: u32 = 42;
14573 - const B: u32 = 42;
14574 ˇconst C: u32 = 42;
14575
14576
14577 fn main() {
14578 println!("hello");
14579
14580 println!("world");
14581 }
14582 "#
14583 .unindent(),
14584 );
14585
14586 cx.update_editor(|editor, window, cx| {
14587 editor.delete_line(&DeleteLine, window, cx);
14588 });
14589 executor.run_until_parked();
14590 cx.assert_state_with_diff(
14591 r#"
14592 use some::mod1;
14593 use some::mod2;
14594
14595 - const A: u32 = 42;
14596 - const B: u32 = 42;
14597 - const C: u32 = 42;
14598 ˇ
14599
14600 fn main() {
14601 println!("hello");
14602
14603 println!("world");
14604 }
14605 "#
14606 .unindent(),
14607 );
14608
14609 cx.update_editor(|editor, window, cx| {
14610 editor.handle_input("replacement", window, cx);
14611 });
14612 executor.run_until_parked();
14613 cx.assert_state_with_diff(
14614 r#"
14615 use some::mod1;
14616 use some::mod2;
14617
14618 - const A: u32 = 42;
14619 - const B: u32 = 42;
14620 - const C: u32 = 42;
14621 -
14622 + replacementˇ
14623
14624 fn main() {
14625 println!("hello");
14626
14627 println!("world");
14628 }
14629 "#
14630 .unindent(),
14631 );
14632}
14633
14634#[gpui::test]
14635async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14636 init_test(cx, |_| {});
14637
14638 let mut cx = EditorTestContext::new(cx).await;
14639
14640 let base_text = r#"
14641 one
14642 two
14643 three
14644 four
14645 five
14646 "#
14647 .unindent();
14648 executor.run_until_parked();
14649 cx.set_state(
14650 &r#"
14651 one
14652 two
14653 fˇour
14654 five
14655 "#
14656 .unindent(),
14657 );
14658
14659 cx.set_head_text(&base_text);
14660 executor.run_until_parked();
14661
14662 cx.update_editor(|editor, window, cx| {
14663 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14664 });
14665 executor.run_until_parked();
14666
14667 cx.assert_state_with_diff(
14668 r#"
14669 one
14670 two
14671 - three
14672 fˇour
14673 five
14674 "#
14675 .unindent(),
14676 );
14677
14678 cx.update_editor(|editor, window, cx| {
14679 editor.backspace(&Backspace, window, cx);
14680 editor.backspace(&Backspace, window, cx);
14681 });
14682 executor.run_until_parked();
14683 cx.assert_state_with_diff(
14684 r#"
14685 one
14686 two
14687 - threeˇ
14688 - four
14689 + our
14690 five
14691 "#
14692 .unindent(),
14693 );
14694}
14695
14696#[gpui::test]
14697async fn test_edit_after_expanded_modification_hunk(
14698 executor: BackgroundExecutor,
14699 cx: &mut TestAppContext,
14700) {
14701 init_test(cx, |_| {});
14702
14703 let mut cx = EditorTestContext::new(cx).await;
14704
14705 let diff_base = r#"
14706 use some::mod1;
14707 use some::mod2;
14708
14709 const A: u32 = 42;
14710 const B: u32 = 42;
14711 const C: u32 = 42;
14712 const D: u32 = 42;
14713
14714
14715 fn main() {
14716 println!("hello");
14717
14718 println!("world");
14719 }"#
14720 .unindent();
14721
14722 cx.set_state(
14723 &r#"
14724 use some::mod1;
14725 use some::mod2;
14726
14727 const A: u32 = 42;
14728 const B: u32 = 42;
14729 const C: u32 = 43ˇ
14730 const D: u32 = 42;
14731
14732
14733 fn main() {
14734 println!("hello");
14735
14736 println!("world");
14737 }"#
14738 .unindent(),
14739 );
14740
14741 cx.set_head_text(&diff_base);
14742 executor.run_until_parked();
14743 cx.update_editor(|editor, window, cx| {
14744 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14745 });
14746 executor.run_until_parked();
14747
14748 cx.assert_state_with_diff(
14749 r#"
14750 use some::mod1;
14751 use some::mod2;
14752
14753 const A: u32 = 42;
14754 const B: u32 = 42;
14755 - const C: u32 = 42;
14756 + const C: u32 = 43ˇ
14757 const D: u32 = 42;
14758
14759
14760 fn main() {
14761 println!("hello");
14762
14763 println!("world");
14764 }"#
14765 .unindent(),
14766 );
14767
14768 cx.update_editor(|editor, window, cx| {
14769 editor.handle_input("\nnew_line\n", window, cx);
14770 });
14771 executor.run_until_parked();
14772
14773 cx.assert_state_with_diff(
14774 r#"
14775 use some::mod1;
14776 use some::mod2;
14777
14778 const A: u32 = 42;
14779 const B: u32 = 42;
14780 - const C: u32 = 42;
14781 + const C: u32 = 43
14782 + new_line
14783 + ˇ
14784 const D: u32 = 42;
14785
14786
14787 fn main() {
14788 println!("hello");
14789
14790 println!("world");
14791 }"#
14792 .unindent(),
14793 );
14794}
14795
14796#[gpui::test]
14797async fn test_stage_and_unstage_added_file_hunk(
14798 executor: BackgroundExecutor,
14799 cx: &mut TestAppContext,
14800) {
14801 init_test(cx, |_| {});
14802
14803 let mut cx = EditorTestContext::new(cx).await;
14804 cx.update_editor(|editor, _, cx| {
14805 editor.set_expand_all_diff_hunks(cx);
14806 });
14807
14808 let working_copy = r#"
14809 ˇfn main() {
14810 println!("hello, world!");
14811 }
14812 "#
14813 .unindent();
14814
14815 cx.set_state(&working_copy);
14816 executor.run_until_parked();
14817
14818 cx.assert_state_with_diff(
14819 r#"
14820 + ˇfn main() {
14821 + println!("hello, world!");
14822 + }
14823 "#
14824 .unindent(),
14825 );
14826 cx.assert_index_text(None);
14827
14828 cx.update_editor(|editor, window, cx| {
14829 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14830 });
14831 executor.run_until_parked();
14832 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14833 cx.assert_state_with_diff(
14834 r#"
14835 + ˇfn main() {
14836 + println!("hello, world!");
14837 + }
14838 "#
14839 .unindent(),
14840 );
14841
14842 cx.update_editor(|editor, window, cx| {
14843 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14844 });
14845 executor.run_until_parked();
14846 cx.assert_index_text(None);
14847}
14848
14849async fn setup_indent_guides_editor(
14850 text: &str,
14851 cx: &mut TestAppContext,
14852) -> (BufferId, EditorTestContext) {
14853 init_test(cx, |_| {});
14854
14855 let mut cx = EditorTestContext::new(cx).await;
14856
14857 let buffer_id = cx.update_editor(|editor, window, cx| {
14858 editor.set_text(text, window, cx);
14859 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14860
14861 buffer_ids[0]
14862 });
14863
14864 (buffer_id, cx)
14865}
14866
14867fn assert_indent_guides(
14868 range: Range<u32>,
14869 expected: Vec<IndentGuide>,
14870 active_indices: Option<Vec<usize>>,
14871 cx: &mut EditorTestContext,
14872) {
14873 let indent_guides = cx.update_editor(|editor, window, cx| {
14874 let snapshot = editor.snapshot(window, cx).display_snapshot;
14875 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14876 editor,
14877 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14878 true,
14879 &snapshot,
14880 cx,
14881 );
14882
14883 indent_guides.sort_by(|a, b| {
14884 a.depth.cmp(&b.depth).then(
14885 a.start_row
14886 .cmp(&b.start_row)
14887 .then(a.end_row.cmp(&b.end_row)),
14888 )
14889 });
14890 indent_guides
14891 });
14892
14893 if let Some(expected) = active_indices {
14894 let active_indices = cx.update_editor(|editor, window, cx| {
14895 let snapshot = editor.snapshot(window, cx).display_snapshot;
14896 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14897 });
14898
14899 assert_eq!(
14900 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14901 expected,
14902 "Active indent guide indices do not match"
14903 );
14904 }
14905
14906 assert_eq!(indent_guides, expected, "Indent guides do not match");
14907}
14908
14909fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14910 IndentGuide {
14911 buffer_id,
14912 start_row: MultiBufferRow(start_row),
14913 end_row: MultiBufferRow(end_row),
14914 depth,
14915 tab_size: 4,
14916 settings: IndentGuideSettings {
14917 enabled: true,
14918 line_width: 1,
14919 active_line_width: 1,
14920 ..Default::default()
14921 },
14922 }
14923}
14924
14925#[gpui::test]
14926async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14927 let (buffer_id, mut cx) = setup_indent_guides_editor(
14928 &"
14929 fn main() {
14930 let a = 1;
14931 }"
14932 .unindent(),
14933 cx,
14934 )
14935 .await;
14936
14937 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14938}
14939
14940#[gpui::test]
14941async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14942 let (buffer_id, mut cx) = setup_indent_guides_editor(
14943 &"
14944 fn main() {
14945 let a = 1;
14946 let b = 2;
14947 }"
14948 .unindent(),
14949 cx,
14950 )
14951 .await;
14952
14953 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14954}
14955
14956#[gpui::test]
14957async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14958 let (buffer_id, mut cx) = setup_indent_guides_editor(
14959 &"
14960 fn main() {
14961 let a = 1;
14962 if a == 3 {
14963 let b = 2;
14964 } else {
14965 let c = 3;
14966 }
14967 }"
14968 .unindent(),
14969 cx,
14970 )
14971 .await;
14972
14973 assert_indent_guides(
14974 0..8,
14975 vec![
14976 indent_guide(buffer_id, 1, 6, 0),
14977 indent_guide(buffer_id, 3, 3, 1),
14978 indent_guide(buffer_id, 5, 5, 1),
14979 ],
14980 None,
14981 &mut cx,
14982 );
14983}
14984
14985#[gpui::test]
14986async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14987 let (buffer_id, mut cx) = setup_indent_guides_editor(
14988 &"
14989 fn main() {
14990 let a = 1;
14991 let b = 2;
14992 let c = 3;
14993 }"
14994 .unindent(),
14995 cx,
14996 )
14997 .await;
14998
14999 assert_indent_guides(
15000 0..5,
15001 vec![
15002 indent_guide(buffer_id, 1, 3, 0),
15003 indent_guide(buffer_id, 2, 2, 1),
15004 ],
15005 None,
15006 &mut cx,
15007 );
15008}
15009
15010#[gpui::test]
15011async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15012 let (buffer_id, mut cx) = setup_indent_guides_editor(
15013 &"
15014 fn main() {
15015 let a = 1;
15016
15017 let c = 3;
15018 }"
15019 .unindent(),
15020 cx,
15021 )
15022 .await;
15023
15024 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15025}
15026
15027#[gpui::test]
15028async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15029 let (buffer_id, mut cx) = setup_indent_guides_editor(
15030 &"
15031 fn main() {
15032 let a = 1;
15033
15034 let c = 3;
15035
15036 if a == 3 {
15037 let b = 2;
15038 } else {
15039 let c = 3;
15040 }
15041 }"
15042 .unindent(),
15043 cx,
15044 )
15045 .await;
15046
15047 assert_indent_guides(
15048 0..11,
15049 vec![
15050 indent_guide(buffer_id, 1, 9, 0),
15051 indent_guide(buffer_id, 6, 6, 1),
15052 indent_guide(buffer_id, 8, 8, 1),
15053 ],
15054 None,
15055 &mut cx,
15056 );
15057}
15058
15059#[gpui::test]
15060async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15061 let (buffer_id, mut cx) = setup_indent_guides_editor(
15062 &"
15063 fn main() {
15064 let a = 1;
15065
15066 let c = 3;
15067
15068 if a == 3 {
15069 let b = 2;
15070 } else {
15071 let c = 3;
15072 }
15073 }"
15074 .unindent(),
15075 cx,
15076 )
15077 .await;
15078
15079 assert_indent_guides(
15080 1..11,
15081 vec![
15082 indent_guide(buffer_id, 1, 9, 0),
15083 indent_guide(buffer_id, 6, 6, 1),
15084 indent_guide(buffer_id, 8, 8, 1),
15085 ],
15086 None,
15087 &mut cx,
15088 );
15089}
15090
15091#[gpui::test]
15092async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15093 let (buffer_id, mut cx) = setup_indent_guides_editor(
15094 &"
15095 fn main() {
15096 let a = 1;
15097
15098 let c = 3;
15099
15100 if a == 3 {
15101 let b = 2;
15102 } else {
15103 let c = 3;
15104 }
15105 }"
15106 .unindent(),
15107 cx,
15108 )
15109 .await;
15110
15111 assert_indent_guides(
15112 1..10,
15113 vec![
15114 indent_guide(buffer_id, 1, 9, 0),
15115 indent_guide(buffer_id, 6, 6, 1),
15116 indent_guide(buffer_id, 8, 8, 1),
15117 ],
15118 None,
15119 &mut cx,
15120 );
15121}
15122
15123#[gpui::test]
15124async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15125 let (buffer_id, mut cx) = setup_indent_guides_editor(
15126 &"
15127 block1
15128 block2
15129 block3
15130 block4
15131 block2
15132 block1
15133 block1"
15134 .unindent(),
15135 cx,
15136 )
15137 .await;
15138
15139 assert_indent_guides(
15140 1..10,
15141 vec![
15142 indent_guide(buffer_id, 1, 4, 0),
15143 indent_guide(buffer_id, 2, 3, 1),
15144 indent_guide(buffer_id, 3, 3, 2),
15145 ],
15146 None,
15147 &mut cx,
15148 );
15149}
15150
15151#[gpui::test]
15152async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15153 let (buffer_id, mut cx) = setup_indent_guides_editor(
15154 &"
15155 block1
15156 block2
15157 block3
15158
15159 block1
15160 block1"
15161 .unindent(),
15162 cx,
15163 )
15164 .await;
15165
15166 assert_indent_guides(
15167 0..6,
15168 vec![
15169 indent_guide(buffer_id, 1, 2, 0),
15170 indent_guide(buffer_id, 2, 2, 1),
15171 ],
15172 None,
15173 &mut cx,
15174 );
15175}
15176
15177#[gpui::test]
15178async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15179 let (buffer_id, mut cx) = setup_indent_guides_editor(
15180 &"
15181 block1
15182
15183
15184
15185 block2
15186 "
15187 .unindent(),
15188 cx,
15189 )
15190 .await;
15191
15192 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15193}
15194
15195#[gpui::test]
15196async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15197 let (buffer_id, mut cx) = setup_indent_guides_editor(
15198 &"
15199 def a:
15200 \tb = 3
15201 \tif True:
15202 \t\tc = 4
15203 \t\td = 5
15204 \tprint(b)
15205 "
15206 .unindent(),
15207 cx,
15208 )
15209 .await;
15210
15211 assert_indent_guides(
15212 0..6,
15213 vec![
15214 indent_guide(buffer_id, 1, 6, 0),
15215 indent_guide(buffer_id, 3, 4, 1),
15216 ],
15217 None,
15218 &mut cx,
15219 );
15220}
15221
15222#[gpui::test]
15223async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15224 let (buffer_id, mut cx) = setup_indent_guides_editor(
15225 &"
15226 fn main() {
15227 let a = 1;
15228 }"
15229 .unindent(),
15230 cx,
15231 )
15232 .await;
15233
15234 cx.update_editor(|editor, window, cx| {
15235 editor.change_selections(None, window, cx, |s| {
15236 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15237 });
15238 });
15239
15240 assert_indent_guides(
15241 0..3,
15242 vec![indent_guide(buffer_id, 1, 1, 0)],
15243 Some(vec![0]),
15244 &mut cx,
15245 );
15246}
15247
15248#[gpui::test]
15249async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15250 let (buffer_id, mut cx) = setup_indent_guides_editor(
15251 &"
15252 fn main() {
15253 if 1 == 2 {
15254 let a = 1;
15255 }
15256 }"
15257 .unindent(),
15258 cx,
15259 )
15260 .await;
15261
15262 cx.update_editor(|editor, window, cx| {
15263 editor.change_selections(None, window, cx, |s| {
15264 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15265 });
15266 });
15267
15268 assert_indent_guides(
15269 0..4,
15270 vec![
15271 indent_guide(buffer_id, 1, 3, 0),
15272 indent_guide(buffer_id, 2, 2, 1),
15273 ],
15274 Some(vec![1]),
15275 &mut cx,
15276 );
15277
15278 cx.update_editor(|editor, window, cx| {
15279 editor.change_selections(None, window, cx, |s| {
15280 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15281 });
15282 });
15283
15284 assert_indent_guides(
15285 0..4,
15286 vec![
15287 indent_guide(buffer_id, 1, 3, 0),
15288 indent_guide(buffer_id, 2, 2, 1),
15289 ],
15290 Some(vec![1]),
15291 &mut cx,
15292 );
15293
15294 cx.update_editor(|editor, window, cx| {
15295 editor.change_selections(None, window, cx, |s| {
15296 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15297 });
15298 });
15299
15300 assert_indent_guides(
15301 0..4,
15302 vec![
15303 indent_guide(buffer_id, 1, 3, 0),
15304 indent_guide(buffer_id, 2, 2, 1),
15305 ],
15306 Some(vec![0]),
15307 &mut cx,
15308 );
15309}
15310
15311#[gpui::test]
15312async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15313 let (buffer_id, mut cx) = setup_indent_guides_editor(
15314 &"
15315 fn main() {
15316 let a = 1;
15317
15318 let b = 2;
15319 }"
15320 .unindent(),
15321 cx,
15322 )
15323 .await;
15324
15325 cx.update_editor(|editor, window, cx| {
15326 editor.change_selections(None, window, cx, |s| {
15327 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15328 });
15329 });
15330
15331 assert_indent_guides(
15332 0..5,
15333 vec![indent_guide(buffer_id, 1, 3, 0)],
15334 Some(vec![0]),
15335 &mut cx,
15336 );
15337}
15338
15339#[gpui::test]
15340async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15341 let (buffer_id, mut cx) = setup_indent_guides_editor(
15342 &"
15343 def m:
15344 a = 1
15345 pass"
15346 .unindent(),
15347 cx,
15348 )
15349 .await;
15350
15351 cx.update_editor(|editor, window, cx| {
15352 editor.change_selections(None, window, cx, |s| {
15353 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15354 });
15355 });
15356
15357 assert_indent_guides(
15358 0..3,
15359 vec![indent_guide(buffer_id, 1, 2, 0)],
15360 Some(vec![0]),
15361 &mut cx,
15362 );
15363}
15364
15365#[gpui::test]
15366async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15367 init_test(cx, |_| {});
15368 let mut cx = EditorTestContext::new(cx).await;
15369 let text = indoc! {
15370 "
15371 impl A {
15372 fn b() {
15373 0;
15374 3;
15375 5;
15376 6;
15377 7;
15378 }
15379 }
15380 "
15381 };
15382 let base_text = indoc! {
15383 "
15384 impl A {
15385 fn b() {
15386 0;
15387 1;
15388 2;
15389 3;
15390 4;
15391 }
15392 fn c() {
15393 5;
15394 6;
15395 7;
15396 }
15397 }
15398 "
15399 };
15400
15401 cx.update_editor(|editor, window, cx| {
15402 editor.set_text(text, window, cx);
15403
15404 editor.buffer().update(cx, |multibuffer, cx| {
15405 let buffer = multibuffer.as_singleton().unwrap();
15406 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15407
15408 multibuffer.set_all_diff_hunks_expanded(cx);
15409 multibuffer.add_diff(diff, cx);
15410
15411 buffer.read(cx).remote_id()
15412 })
15413 });
15414 cx.run_until_parked();
15415
15416 cx.assert_state_with_diff(
15417 indoc! { "
15418 impl A {
15419 fn b() {
15420 0;
15421 - 1;
15422 - 2;
15423 3;
15424 - 4;
15425 - }
15426 - fn c() {
15427 5;
15428 6;
15429 7;
15430 }
15431 }
15432 ˇ"
15433 }
15434 .to_string(),
15435 );
15436
15437 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15438 editor
15439 .snapshot(window, cx)
15440 .buffer_snapshot
15441 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15442 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15443 .collect::<Vec<_>>()
15444 });
15445 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15446 assert_eq!(
15447 actual_guides,
15448 vec![
15449 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15450 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15451 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15452 ]
15453 );
15454}
15455
15456#[gpui::test]
15457async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15458 init_test(cx, |_| {});
15459 let mut cx = EditorTestContext::new(cx).await;
15460
15461 let diff_base = r#"
15462 a
15463 b
15464 c
15465 "#
15466 .unindent();
15467
15468 cx.set_state(
15469 &r#"
15470 ˇA
15471 b
15472 C
15473 "#
15474 .unindent(),
15475 );
15476 cx.set_head_text(&diff_base);
15477 cx.update_editor(|editor, window, cx| {
15478 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15479 });
15480 executor.run_until_parked();
15481
15482 let both_hunks_expanded = r#"
15483 - a
15484 + ˇA
15485 b
15486 - c
15487 + C
15488 "#
15489 .unindent();
15490
15491 cx.assert_state_with_diff(both_hunks_expanded.clone());
15492
15493 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15494 let snapshot = editor.snapshot(window, cx);
15495 let hunks = editor
15496 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15497 .collect::<Vec<_>>();
15498 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15499 let buffer_id = hunks[0].buffer_id;
15500 hunks
15501 .into_iter()
15502 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15503 .collect::<Vec<_>>()
15504 });
15505 assert_eq!(hunk_ranges.len(), 2);
15506
15507 cx.update_editor(|editor, _, cx| {
15508 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15509 });
15510 executor.run_until_parked();
15511
15512 let second_hunk_expanded = r#"
15513 ˇA
15514 b
15515 - c
15516 + C
15517 "#
15518 .unindent();
15519
15520 cx.assert_state_with_diff(second_hunk_expanded);
15521
15522 cx.update_editor(|editor, _, cx| {
15523 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15524 });
15525 executor.run_until_parked();
15526
15527 cx.assert_state_with_diff(both_hunks_expanded.clone());
15528
15529 cx.update_editor(|editor, _, cx| {
15530 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15531 });
15532 executor.run_until_parked();
15533
15534 let first_hunk_expanded = r#"
15535 - a
15536 + ˇA
15537 b
15538 C
15539 "#
15540 .unindent();
15541
15542 cx.assert_state_with_diff(first_hunk_expanded);
15543
15544 cx.update_editor(|editor, _, cx| {
15545 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15546 });
15547 executor.run_until_parked();
15548
15549 cx.assert_state_with_diff(both_hunks_expanded);
15550
15551 cx.set_state(
15552 &r#"
15553 ˇA
15554 b
15555 "#
15556 .unindent(),
15557 );
15558 cx.run_until_parked();
15559
15560 // TODO this cursor position seems bad
15561 cx.assert_state_with_diff(
15562 r#"
15563 - ˇa
15564 + A
15565 b
15566 "#
15567 .unindent(),
15568 );
15569
15570 cx.update_editor(|editor, window, cx| {
15571 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15572 });
15573
15574 cx.assert_state_with_diff(
15575 r#"
15576 - ˇa
15577 + A
15578 b
15579 - c
15580 "#
15581 .unindent(),
15582 );
15583
15584 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15585 let snapshot = editor.snapshot(window, cx);
15586 let hunks = editor
15587 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15588 .collect::<Vec<_>>();
15589 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15590 let buffer_id = hunks[0].buffer_id;
15591 hunks
15592 .into_iter()
15593 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15594 .collect::<Vec<_>>()
15595 });
15596 assert_eq!(hunk_ranges.len(), 2);
15597
15598 cx.update_editor(|editor, _, cx| {
15599 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15600 });
15601 executor.run_until_parked();
15602
15603 cx.assert_state_with_diff(
15604 r#"
15605 - ˇa
15606 + A
15607 b
15608 "#
15609 .unindent(),
15610 );
15611}
15612
15613#[gpui::test]
15614async fn test_toggle_deletion_hunk_at_start_of_file(
15615 executor: BackgroundExecutor,
15616 cx: &mut TestAppContext,
15617) {
15618 init_test(cx, |_| {});
15619 let mut cx = EditorTestContext::new(cx).await;
15620
15621 let diff_base = r#"
15622 a
15623 b
15624 c
15625 "#
15626 .unindent();
15627
15628 cx.set_state(
15629 &r#"
15630 ˇb
15631 c
15632 "#
15633 .unindent(),
15634 );
15635 cx.set_head_text(&diff_base);
15636 cx.update_editor(|editor, window, cx| {
15637 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15638 });
15639 executor.run_until_parked();
15640
15641 let hunk_expanded = r#"
15642 - a
15643 ˇb
15644 c
15645 "#
15646 .unindent();
15647
15648 cx.assert_state_with_diff(hunk_expanded.clone());
15649
15650 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15651 let snapshot = editor.snapshot(window, cx);
15652 let hunks = editor
15653 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15654 .collect::<Vec<_>>();
15655 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15656 let buffer_id = hunks[0].buffer_id;
15657 hunks
15658 .into_iter()
15659 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15660 .collect::<Vec<_>>()
15661 });
15662 assert_eq!(hunk_ranges.len(), 1);
15663
15664 cx.update_editor(|editor, _, cx| {
15665 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15666 });
15667 executor.run_until_parked();
15668
15669 let hunk_collapsed = r#"
15670 ˇb
15671 c
15672 "#
15673 .unindent();
15674
15675 cx.assert_state_with_diff(hunk_collapsed);
15676
15677 cx.update_editor(|editor, _, cx| {
15678 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15679 });
15680 executor.run_until_parked();
15681
15682 cx.assert_state_with_diff(hunk_expanded.clone());
15683}
15684
15685#[gpui::test]
15686async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15687 init_test(cx, |_| {});
15688
15689 let fs = FakeFs::new(cx.executor());
15690 fs.insert_tree(
15691 path!("/test"),
15692 json!({
15693 ".git": {},
15694 "file-1": "ONE\n",
15695 "file-2": "TWO\n",
15696 "file-3": "THREE\n",
15697 }),
15698 )
15699 .await;
15700
15701 fs.set_head_for_repo(
15702 path!("/test/.git").as_ref(),
15703 &[
15704 ("file-1".into(), "one\n".into()),
15705 ("file-2".into(), "two\n".into()),
15706 ("file-3".into(), "three\n".into()),
15707 ],
15708 );
15709
15710 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15711 let mut buffers = vec![];
15712 for i in 1..=3 {
15713 let buffer = project
15714 .update(cx, |project, cx| {
15715 let path = format!(path!("/test/file-{}"), i);
15716 project.open_local_buffer(path, cx)
15717 })
15718 .await
15719 .unwrap();
15720 buffers.push(buffer);
15721 }
15722
15723 let multibuffer = cx.new(|cx| {
15724 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15725 multibuffer.set_all_diff_hunks_expanded(cx);
15726 for buffer in &buffers {
15727 let snapshot = buffer.read(cx).snapshot();
15728 multibuffer.set_excerpts_for_path(
15729 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15730 buffer.clone(),
15731 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15732 DEFAULT_MULTIBUFFER_CONTEXT,
15733 cx,
15734 );
15735 }
15736 multibuffer
15737 });
15738
15739 let editor = cx.add_window(|window, cx| {
15740 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
15741 });
15742 cx.run_until_parked();
15743
15744 let snapshot = editor
15745 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15746 .unwrap();
15747 let hunks = snapshot
15748 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15749 .map(|hunk| match hunk {
15750 DisplayDiffHunk::Unfolded {
15751 display_row_range, ..
15752 } => display_row_range,
15753 DisplayDiffHunk::Folded { .. } => unreachable!(),
15754 })
15755 .collect::<Vec<_>>();
15756 assert_eq!(
15757 hunks,
15758 [
15759 DisplayRow(2)..DisplayRow(4),
15760 DisplayRow(7)..DisplayRow(9),
15761 DisplayRow(12)..DisplayRow(14),
15762 ]
15763 );
15764}
15765
15766#[gpui::test]
15767async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15768 init_test(cx, |_| {});
15769
15770 let mut cx = EditorTestContext::new(cx).await;
15771 cx.set_head_text(indoc! { "
15772 one
15773 two
15774 three
15775 four
15776 five
15777 "
15778 });
15779 cx.set_index_text(indoc! { "
15780 one
15781 two
15782 three
15783 four
15784 five
15785 "
15786 });
15787 cx.set_state(indoc! {"
15788 one
15789 TWO
15790 ˇTHREE
15791 FOUR
15792 five
15793 "});
15794 cx.run_until_parked();
15795 cx.update_editor(|editor, window, cx| {
15796 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15797 });
15798 cx.run_until_parked();
15799 cx.assert_index_text(Some(indoc! {"
15800 one
15801 TWO
15802 THREE
15803 FOUR
15804 five
15805 "}));
15806 cx.set_state(indoc! { "
15807 one
15808 TWO
15809 ˇTHREE-HUNDRED
15810 FOUR
15811 five
15812 "});
15813 cx.run_until_parked();
15814 cx.update_editor(|editor, window, cx| {
15815 let snapshot = editor.snapshot(window, cx);
15816 let hunks = editor
15817 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15818 .collect::<Vec<_>>();
15819 assert_eq!(hunks.len(), 1);
15820 assert_eq!(
15821 hunks[0].status(),
15822 DiffHunkStatus {
15823 kind: DiffHunkStatusKind::Modified,
15824 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15825 }
15826 );
15827
15828 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15829 });
15830 cx.run_until_parked();
15831 cx.assert_index_text(Some(indoc! {"
15832 one
15833 TWO
15834 THREE-HUNDRED
15835 FOUR
15836 five
15837 "}));
15838}
15839
15840#[gpui::test]
15841fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15842 init_test(cx, |_| {});
15843
15844 let editor = cx.add_window(|window, cx| {
15845 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15846 build_editor(buffer, window, cx)
15847 });
15848
15849 let render_args = Arc::new(Mutex::new(None));
15850 let snapshot = editor
15851 .update(cx, |editor, window, cx| {
15852 let snapshot = editor.buffer().read(cx).snapshot(cx);
15853 let range =
15854 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15855
15856 struct RenderArgs {
15857 row: MultiBufferRow,
15858 folded: bool,
15859 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15860 }
15861
15862 let crease = Crease::inline(
15863 range,
15864 FoldPlaceholder::test(),
15865 {
15866 let toggle_callback = render_args.clone();
15867 move |row, folded, callback, _window, _cx| {
15868 *toggle_callback.lock() = Some(RenderArgs {
15869 row,
15870 folded,
15871 callback,
15872 });
15873 div()
15874 }
15875 },
15876 |_row, _folded, _window, _cx| div(),
15877 );
15878
15879 editor.insert_creases(Some(crease), cx);
15880 let snapshot = editor.snapshot(window, cx);
15881 let _div = snapshot.render_crease_toggle(
15882 MultiBufferRow(1),
15883 false,
15884 cx.entity().clone(),
15885 window,
15886 cx,
15887 );
15888 snapshot
15889 })
15890 .unwrap();
15891
15892 let render_args = render_args.lock().take().unwrap();
15893 assert_eq!(render_args.row, MultiBufferRow(1));
15894 assert!(!render_args.folded);
15895 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15896
15897 cx.update_window(*editor, |_, window, cx| {
15898 (render_args.callback)(true, window, cx)
15899 })
15900 .unwrap();
15901 let snapshot = editor
15902 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15903 .unwrap();
15904 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15905
15906 cx.update_window(*editor, |_, window, cx| {
15907 (render_args.callback)(false, window, cx)
15908 })
15909 .unwrap();
15910 let snapshot = editor
15911 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15912 .unwrap();
15913 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15914}
15915
15916#[gpui::test]
15917async fn test_input_text(cx: &mut TestAppContext) {
15918 init_test(cx, |_| {});
15919 let mut cx = EditorTestContext::new(cx).await;
15920
15921 cx.set_state(
15922 &r#"ˇone
15923 two
15924
15925 three
15926 fourˇ
15927 five
15928
15929 siˇx"#
15930 .unindent(),
15931 );
15932
15933 cx.dispatch_action(HandleInput(String::new()));
15934 cx.assert_editor_state(
15935 &r#"ˇone
15936 two
15937
15938 three
15939 fourˇ
15940 five
15941
15942 siˇx"#
15943 .unindent(),
15944 );
15945
15946 cx.dispatch_action(HandleInput("AAAA".to_string()));
15947 cx.assert_editor_state(
15948 &r#"AAAAˇone
15949 two
15950
15951 three
15952 fourAAAAˇ
15953 five
15954
15955 siAAAAˇx"#
15956 .unindent(),
15957 );
15958}
15959
15960#[gpui::test]
15961async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15962 init_test(cx, |_| {});
15963
15964 let mut cx = EditorTestContext::new(cx).await;
15965 cx.set_state(
15966 r#"let foo = 1;
15967let foo = 2;
15968let foo = 3;
15969let fooˇ = 4;
15970let foo = 5;
15971let foo = 6;
15972let foo = 7;
15973let foo = 8;
15974let foo = 9;
15975let foo = 10;
15976let foo = 11;
15977let foo = 12;
15978let foo = 13;
15979let foo = 14;
15980let foo = 15;"#,
15981 );
15982
15983 cx.update_editor(|e, window, cx| {
15984 assert_eq!(
15985 e.next_scroll_position,
15986 NextScrollCursorCenterTopBottom::Center,
15987 "Default next scroll direction is center",
15988 );
15989
15990 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15991 assert_eq!(
15992 e.next_scroll_position,
15993 NextScrollCursorCenterTopBottom::Top,
15994 "After center, next scroll direction should be top",
15995 );
15996
15997 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15998 assert_eq!(
15999 e.next_scroll_position,
16000 NextScrollCursorCenterTopBottom::Bottom,
16001 "After top, next scroll direction should be bottom",
16002 );
16003
16004 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16005 assert_eq!(
16006 e.next_scroll_position,
16007 NextScrollCursorCenterTopBottom::Center,
16008 "After bottom, scrolling should start over",
16009 );
16010
16011 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16012 assert_eq!(
16013 e.next_scroll_position,
16014 NextScrollCursorCenterTopBottom::Top,
16015 "Scrolling continues if retriggered fast enough"
16016 );
16017 });
16018
16019 cx.executor()
16020 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16021 cx.executor().run_until_parked();
16022 cx.update_editor(|e, _, _| {
16023 assert_eq!(
16024 e.next_scroll_position,
16025 NextScrollCursorCenterTopBottom::Center,
16026 "If scrolling is not triggered fast enough, it should reset"
16027 );
16028 });
16029}
16030
16031#[gpui::test]
16032async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16033 init_test(cx, |_| {});
16034 let mut cx = EditorLspTestContext::new_rust(
16035 lsp::ServerCapabilities {
16036 definition_provider: Some(lsp::OneOf::Left(true)),
16037 references_provider: Some(lsp::OneOf::Left(true)),
16038 ..lsp::ServerCapabilities::default()
16039 },
16040 cx,
16041 )
16042 .await;
16043
16044 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16045 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
16046 move |params, _| async move {
16047 if empty_go_to_definition {
16048 Ok(None)
16049 } else {
16050 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16051 uri: params.text_document_position_params.text_document.uri,
16052 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
16053 })))
16054 }
16055 },
16056 );
16057 let references =
16058 cx.lsp
16059 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
16060 Ok(Some(vec![lsp::Location {
16061 uri: params.text_document_position.text_document.uri,
16062 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16063 }]))
16064 });
16065 (go_to_definition, references)
16066 };
16067
16068 cx.set_state(
16069 &r#"fn one() {
16070 let mut a = ˇtwo();
16071 }
16072
16073 fn two() {}"#
16074 .unindent(),
16075 );
16076 set_up_lsp_handlers(false, &mut cx);
16077 let navigated = cx
16078 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16079 .await
16080 .expect("Failed to navigate to definition");
16081 assert_eq!(
16082 navigated,
16083 Navigated::Yes,
16084 "Should have navigated to definition from the GetDefinition response"
16085 );
16086 cx.assert_editor_state(
16087 &r#"fn one() {
16088 let mut a = two();
16089 }
16090
16091 fn «twoˇ»() {}"#
16092 .unindent(),
16093 );
16094
16095 let editors = cx.update_workspace(|workspace, _, cx| {
16096 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16097 });
16098 cx.update_editor(|_, _, test_editor_cx| {
16099 assert_eq!(
16100 editors.len(),
16101 1,
16102 "Initially, only one, test, editor should be open in the workspace"
16103 );
16104 assert_eq!(
16105 test_editor_cx.entity(),
16106 editors.last().expect("Asserted len is 1").clone()
16107 );
16108 });
16109
16110 set_up_lsp_handlers(true, &mut cx);
16111 let navigated = cx
16112 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16113 .await
16114 .expect("Failed to navigate to lookup references");
16115 assert_eq!(
16116 navigated,
16117 Navigated::Yes,
16118 "Should have navigated to references as a fallback after empty GoToDefinition response"
16119 );
16120 // We should not change the selections in the existing file,
16121 // if opening another milti buffer with the references
16122 cx.assert_editor_state(
16123 &r#"fn one() {
16124 let mut a = two();
16125 }
16126
16127 fn «twoˇ»() {}"#
16128 .unindent(),
16129 );
16130 let editors = cx.update_workspace(|workspace, _, cx| {
16131 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16132 });
16133 cx.update_editor(|_, _, test_editor_cx| {
16134 assert_eq!(
16135 editors.len(),
16136 2,
16137 "After falling back to references search, we open a new editor with the results"
16138 );
16139 let references_fallback_text = editors
16140 .into_iter()
16141 .find(|new_editor| *new_editor != test_editor_cx.entity())
16142 .expect("Should have one non-test editor now")
16143 .read(test_editor_cx)
16144 .text(test_editor_cx);
16145 assert_eq!(
16146 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16147 "Should use the range from the references response and not the GoToDefinition one"
16148 );
16149 });
16150}
16151
16152#[gpui::test]
16153async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16154 init_test(cx, |_| {});
16155
16156 let language = Arc::new(Language::new(
16157 LanguageConfig::default(),
16158 Some(tree_sitter_rust::LANGUAGE.into()),
16159 ));
16160
16161 let text = r#"
16162 #[cfg(test)]
16163 mod tests() {
16164 #[test]
16165 fn runnable_1() {
16166 let a = 1;
16167 }
16168
16169 #[test]
16170 fn runnable_2() {
16171 let a = 1;
16172 let b = 2;
16173 }
16174 }
16175 "#
16176 .unindent();
16177
16178 let fs = FakeFs::new(cx.executor());
16179 fs.insert_file("/file.rs", Default::default()).await;
16180
16181 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16182 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16183 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16184 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16185 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16186
16187 let editor = cx.new_window_entity(|window, cx| {
16188 Editor::new(
16189 EditorMode::Full,
16190 multi_buffer,
16191 Some(project.clone()),
16192 window,
16193 cx,
16194 )
16195 });
16196
16197 editor.update_in(cx, |editor, window, cx| {
16198 let snapshot = editor.buffer().read(cx).snapshot(cx);
16199 editor.tasks.insert(
16200 (buffer.read(cx).remote_id(), 3),
16201 RunnableTasks {
16202 templates: vec![],
16203 offset: snapshot.anchor_before(43),
16204 column: 0,
16205 extra_variables: HashMap::default(),
16206 context_range: BufferOffset(43)..BufferOffset(85),
16207 },
16208 );
16209 editor.tasks.insert(
16210 (buffer.read(cx).remote_id(), 8),
16211 RunnableTasks {
16212 templates: vec![],
16213 offset: snapshot.anchor_before(86),
16214 column: 0,
16215 extra_variables: HashMap::default(),
16216 context_range: BufferOffset(86)..BufferOffset(191),
16217 },
16218 );
16219
16220 // Test finding task when cursor is inside function body
16221 editor.change_selections(None, window, cx, |s| {
16222 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16223 });
16224 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16225 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16226
16227 // Test finding task when cursor is on function name
16228 editor.change_selections(None, window, cx, |s| {
16229 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16230 });
16231 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16232 assert_eq!(row, 8, "Should find task when cursor is on function name");
16233 });
16234}
16235
16236#[gpui::test]
16237async fn test_folding_buffers(cx: &mut TestAppContext) {
16238 init_test(cx, |_| {});
16239
16240 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16241 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16242 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16243
16244 let fs = FakeFs::new(cx.executor());
16245 fs.insert_tree(
16246 path!("/a"),
16247 json!({
16248 "first.rs": sample_text_1,
16249 "second.rs": sample_text_2,
16250 "third.rs": sample_text_3,
16251 }),
16252 )
16253 .await;
16254 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16255 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16256 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16257 let worktree = project.update(cx, |project, cx| {
16258 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16259 assert_eq!(worktrees.len(), 1);
16260 worktrees.pop().unwrap()
16261 });
16262 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16263
16264 let buffer_1 = project
16265 .update(cx, |project, cx| {
16266 project.open_buffer((worktree_id, "first.rs"), cx)
16267 })
16268 .await
16269 .unwrap();
16270 let buffer_2 = project
16271 .update(cx, |project, cx| {
16272 project.open_buffer((worktree_id, "second.rs"), cx)
16273 })
16274 .await
16275 .unwrap();
16276 let buffer_3 = project
16277 .update(cx, |project, cx| {
16278 project.open_buffer((worktree_id, "third.rs"), cx)
16279 })
16280 .await
16281 .unwrap();
16282
16283 let multi_buffer = cx.new(|cx| {
16284 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16285 multi_buffer.push_excerpts(
16286 buffer_1.clone(),
16287 [
16288 ExcerptRange {
16289 context: Point::new(0, 0)..Point::new(3, 0),
16290 primary: None,
16291 },
16292 ExcerptRange {
16293 context: Point::new(5, 0)..Point::new(7, 0),
16294 primary: None,
16295 },
16296 ExcerptRange {
16297 context: Point::new(9, 0)..Point::new(10, 4),
16298 primary: None,
16299 },
16300 ],
16301 cx,
16302 );
16303 multi_buffer.push_excerpts(
16304 buffer_2.clone(),
16305 [
16306 ExcerptRange {
16307 context: Point::new(0, 0)..Point::new(3, 0),
16308 primary: None,
16309 },
16310 ExcerptRange {
16311 context: Point::new(5, 0)..Point::new(7, 0),
16312 primary: None,
16313 },
16314 ExcerptRange {
16315 context: Point::new(9, 0)..Point::new(10, 4),
16316 primary: None,
16317 },
16318 ],
16319 cx,
16320 );
16321 multi_buffer.push_excerpts(
16322 buffer_3.clone(),
16323 [
16324 ExcerptRange {
16325 context: Point::new(0, 0)..Point::new(3, 0),
16326 primary: None,
16327 },
16328 ExcerptRange {
16329 context: Point::new(5, 0)..Point::new(7, 0),
16330 primary: None,
16331 },
16332 ExcerptRange {
16333 context: Point::new(9, 0)..Point::new(10, 4),
16334 primary: None,
16335 },
16336 ],
16337 cx,
16338 );
16339 multi_buffer
16340 });
16341 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16342 Editor::new(
16343 EditorMode::Full,
16344 multi_buffer.clone(),
16345 Some(project.clone()),
16346 window,
16347 cx,
16348 )
16349 });
16350
16351 assert_eq!(
16352 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16353 "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16354 );
16355
16356 multi_buffer_editor.update(cx, |editor, cx| {
16357 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16358 });
16359 assert_eq!(
16360 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16361 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16362 "After folding the first buffer, its text should not be displayed"
16363 );
16364
16365 multi_buffer_editor.update(cx, |editor, cx| {
16366 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16367 });
16368 assert_eq!(
16369 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16370 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16371 "After folding the second buffer, its text should not be displayed"
16372 );
16373
16374 multi_buffer_editor.update(cx, |editor, cx| {
16375 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16376 });
16377 assert_eq!(
16378 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16379 "\n\n\n\n\n",
16380 "After folding the third buffer, its text should not be displayed"
16381 );
16382
16383 // Emulate selection inside the fold logic, that should work
16384 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16385 editor
16386 .snapshot(window, cx)
16387 .next_line_boundary(Point::new(0, 4));
16388 });
16389
16390 multi_buffer_editor.update(cx, |editor, cx| {
16391 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16392 });
16393 assert_eq!(
16394 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16395 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16396 "After unfolding the second buffer, its text should be displayed"
16397 );
16398
16399 // Typing inside of buffer 1 causes that buffer to be unfolded.
16400 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16401 assert_eq!(
16402 multi_buffer
16403 .read(cx)
16404 .snapshot(cx)
16405 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16406 .collect::<String>(),
16407 "bbbb"
16408 );
16409 editor.change_selections(None, window, cx, |selections| {
16410 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16411 });
16412 editor.handle_input("B", window, cx);
16413 });
16414
16415 assert_eq!(
16416 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16417 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16418 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16419 );
16420
16421 multi_buffer_editor.update(cx, |editor, cx| {
16422 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16423 });
16424 assert_eq!(
16425 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16426 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16427 "After unfolding the all buffers, all original text should be displayed"
16428 );
16429}
16430
16431#[gpui::test]
16432async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16433 init_test(cx, |_| {});
16434
16435 let sample_text_1 = "1111\n2222\n3333".to_string();
16436 let sample_text_2 = "4444\n5555\n6666".to_string();
16437 let sample_text_3 = "7777\n8888\n9999".to_string();
16438
16439 let fs = FakeFs::new(cx.executor());
16440 fs.insert_tree(
16441 path!("/a"),
16442 json!({
16443 "first.rs": sample_text_1,
16444 "second.rs": sample_text_2,
16445 "third.rs": sample_text_3,
16446 }),
16447 )
16448 .await;
16449 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16450 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16451 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16452 let worktree = project.update(cx, |project, cx| {
16453 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16454 assert_eq!(worktrees.len(), 1);
16455 worktrees.pop().unwrap()
16456 });
16457 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16458
16459 let buffer_1 = project
16460 .update(cx, |project, cx| {
16461 project.open_buffer((worktree_id, "first.rs"), cx)
16462 })
16463 .await
16464 .unwrap();
16465 let buffer_2 = project
16466 .update(cx, |project, cx| {
16467 project.open_buffer((worktree_id, "second.rs"), cx)
16468 })
16469 .await
16470 .unwrap();
16471 let buffer_3 = project
16472 .update(cx, |project, cx| {
16473 project.open_buffer((worktree_id, "third.rs"), cx)
16474 })
16475 .await
16476 .unwrap();
16477
16478 let multi_buffer = cx.new(|cx| {
16479 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16480 multi_buffer.push_excerpts(
16481 buffer_1.clone(),
16482 [ExcerptRange {
16483 context: Point::new(0, 0)..Point::new(3, 0),
16484 primary: None,
16485 }],
16486 cx,
16487 );
16488 multi_buffer.push_excerpts(
16489 buffer_2.clone(),
16490 [ExcerptRange {
16491 context: Point::new(0, 0)..Point::new(3, 0),
16492 primary: None,
16493 }],
16494 cx,
16495 );
16496 multi_buffer.push_excerpts(
16497 buffer_3.clone(),
16498 [ExcerptRange {
16499 context: Point::new(0, 0)..Point::new(3, 0),
16500 primary: None,
16501 }],
16502 cx,
16503 );
16504 multi_buffer
16505 });
16506
16507 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16508 Editor::new(
16509 EditorMode::Full,
16510 multi_buffer,
16511 Some(project.clone()),
16512 window,
16513 cx,
16514 )
16515 });
16516
16517 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16518 assert_eq!(
16519 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16520 full_text,
16521 );
16522
16523 multi_buffer_editor.update(cx, |editor, cx| {
16524 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16525 });
16526 assert_eq!(
16527 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16528 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16529 "After folding the first buffer, its text should not be displayed"
16530 );
16531
16532 multi_buffer_editor.update(cx, |editor, cx| {
16533 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16534 });
16535
16536 assert_eq!(
16537 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16538 "\n\n\n\n\n\n7777\n8888\n9999",
16539 "After folding the second buffer, its text should not be displayed"
16540 );
16541
16542 multi_buffer_editor.update(cx, |editor, cx| {
16543 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16544 });
16545 assert_eq!(
16546 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16547 "\n\n\n\n\n",
16548 "After folding the third buffer, its text should not be displayed"
16549 );
16550
16551 multi_buffer_editor.update(cx, |editor, cx| {
16552 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16553 });
16554 assert_eq!(
16555 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16556 "\n\n\n\n4444\n5555\n6666\n\n",
16557 "After unfolding the second buffer, its text should be displayed"
16558 );
16559
16560 multi_buffer_editor.update(cx, |editor, cx| {
16561 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16562 });
16563 assert_eq!(
16564 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16565 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16566 "After unfolding the first buffer, its text should be displayed"
16567 );
16568
16569 multi_buffer_editor.update(cx, |editor, cx| {
16570 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16571 });
16572 assert_eq!(
16573 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16574 full_text,
16575 "After unfolding all buffers, all original text should be displayed"
16576 );
16577}
16578
16579#[gpui::test]
16580async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16581 init_test(cx, |_| {});
16582
16583 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16584
16585 let fs = FakeFs::new(cx.executor());
16586 fs.insert_tree(
16587 path!("/a"),
16588 json!({
16589 "main.rs": sample_text,
16590 }),
16591 )
16592 .await;
16593 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16594 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16595 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16596 let worktree = project.update(cx, |project, cx| {
16597 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16598 assert_eq!(worktrees.len(), 1);
16599 worktrees.pop().unwrap()
16600 });
16601 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16602
16603 let buffer_1 = project
16604 .update(cx, |project, cx| {
16605 project.open_buffer((worktree_id, "main.rs"), cx)
16606 })
16607 .await
16608 .unwrap();
16609
16610 let multi_buffer = cx.new(|cx| {
16611 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16612 multi_buffer.push_excerpts(
16613 buffer_1.clone(),
16614 [ExcerptRange {
16615 context: Point::new(0, 0)
16616 ..Point::new(
16617 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16618 0,
16619 ),
16620 primary: None,
16621 }],
16622 cx,
16623 );
16624 multi_buffer
16625 });
16626 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16627 Editor::new(
16628 EditorMode::Full,
16629 multi_buffer,
16630 Some(project.clone()),
16631 window,
16632 cx,
16633 )
16634 });
16635
16636 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16637 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16638 enum TestHighlight {}
16639 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16640 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16641 editor.highlight_text::<TestHighlight>(
16642 vec![highlight_range.clone()],
16643 HighlightStyle::color(Hsla::green()),
16644 cx,
16645 );
16646 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16647 });
16648
16649 let full_text = format!("\n\n{sample_text}");
16650 assert_eq!(
16651 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16652 full_text,
16653 );
16654}
16655
16656#[gpui::test]
16657async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16658 init_test(cx, |_| {});
16659 cx.update(|cx| {
16660 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16661 "keymaps/default-linux.json",
16662 cx,
16663 )
16664 .unwrap();
16665 cx.bind_keys(default_key_bindings);
16666 });
16667
16668 let (editor, cx) = cx.add_window_view(|window, cx| {
16669 let multi_buffer = MultiBuffer::build_multi(
16670 [
16671 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16672 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16673 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16674 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16675 ],
16676 cx,
16677 );
16678 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16679
16680 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16681 // fold all but the second buffer, so that we test navigating between two
16682 // adjacent folded buffers, as well as folded buffers at the start and
16683 // end the multibuffer
16684 editor.fold_buffer(buffer_ids[0], cx);
16685 editor.fold_buffer(buffer_ids[2], cx);
16686 editor.fold_buffer(buffer_ids[3], cx);
16687
16688 editor
16689 });
16690 cx.simulate_resize(size(px(1000.), px(1000.)));
16691
16692 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16693 cx.assert_excerpts_with_selections(indoc! {"
16694 [EXCERPT]
16695 ˇ[FOLDED]
16696 [EXCERPT]
16697 a1
16698 b1
16699 [EXCERPT]
16700 [FOLDED]
16701 [EXCERPT]
16702 [FOLDED]
16703 "
16704 });
16705 cx.simulate_keystroke("down");
16706 cx.assert_excerpts_with_selections(indoc! {"
16707 [EXCERPT]
16708 [FOLDED]
16709 [EXCERPT]
16710 ˇa1
16711 b1
16712 [EXCERPT]
16713 [FOLDED]
16714 [EXCERPT]
16715 [FOLDED]
16716 "
16717 });
16718 cx.simulate_keystroke("down");
16719 cx.assert_excerpts_with_selections(indoc! {"
16720 [EXCERPT]
16721 [FOLDED]
16722 [EXCERPT]
16723 a1
16724 ˇb1
16725 [EXCERPT]
16726 [FOLDED]
16727 [EXCERPT]
16728 [FOLDED]
16729 "
16730 });
16731 cx.simulate_keystroke("down");
16732 cx.assert_excerpts_with_selections(indoc! {"
16733 [EXCERPT]
16734 [FOLDED]
16735 [EXCERPT]
16736 a1
16737 b1
16738 ˇ[EXCERPT]
16739 [FOLDED]
16740 [EXCERPT]
16741 [FOLDED]
16742 "
16743 });
16744 cx.simulate_keystroke("down");
16745 cx.assert_excerpts_with_selections(indoc! {"
16746 [EXCERPT]
16747 [FOLDED]
16748 [EXCERPT]
16749 a1
16750 b1
16751 [EXCERPT]
16752 ˇ[FOLDED]
16753 [EXCERPT]
16754 [FOLDED]
16755 "
16756 });
16757 for _ in 0..5 {
16758 cx.simulate_keystroke("down");
16759 cx.assert_excerpts_with_selections(indoc! {"
16760 [EXCERPT]
16761 [FOLDED]
16762 [EXCERPT]
16763 a1
16764 b1
16765 [EXCERPT]
16766 [FOLDED]
16767 [EXCERPT]
16768 ˇ[FOLDED]
16769 "
16770 });
16771 }
16772
16773 cx.simulate_keystroke("up");
16774 cx.assert_excerpts_with_selections(indoc! {"
16775 [EXCERPT]
16776 [FOLDED]
16777 [EXCERPT]
16778 a1
16779 b1
16780 [EXCERPT]
16781 ˇ[FOLDED]
16782 [EXCERPT]
16783 [FOLDED]
16784 "
16785 });
16786 cx.simulate_keystroke("up");
16787 cx.assert_excerpts_with_selections(indoc! {"
16788 [EXCERPT]
16789 [FOLDED]
16790 [EXCERPT]
16791 a1
16792 b1
16793 ˇ[EXCERPT]
16794 [FOLDED]
16795 [EXCERPT]
16796 [FOLDED]
16797 "
16798 });
16799 cx.simulate_keystroke("up");
16800 cx.assert_excerpts_with_selections(indoc! {"
16801 [EXCERPT]
16802 [FOLDED]
16803 [EXCERPT]
16804 a1
16805 ˇb1
16806 [EXCERPT]
16807 [FOLDED]
16808 [EXCERPT]
16809 [FOLDED]
16810 "
16811 });
16812 cx.simulate_keystroke("up");
16813 cx.assert_excerpts_with_selections(indoc! {"
16814 [EXCERPT]
16815 [FOLDED]
16816 [EXCERPT]
16817 ˇa1
16818 b1
16819 [EXCERPT]
16820 [FOLDED]
16821 [EXCERPT]
16822 [FOLDED]
16823 "
16824 });
16825 for _ in 0..5 {
16826 cx.simulate_keystroke("up");
16827 cx.assert_excerpts_with_selections(indoc! {"
16828 [EXCERPT]
16829 ˇ[FOLDED]
16830 [EXCERPT]
16831 a1
16832 b1
16833 [EXCERPT]
16834 [FOLDED]
16835 [EXCERPT]
16836 [FOLDED]
16837 "
16838 });
16839 }
16840}
16841
16842#[gpui::test]
16843async fn test_inline_completion_text(cx: &mut TestAppContext) {
16844 init_test(cx, |_| {});
16845
16846 // Simple insertion
16847 assert_highlighted_edits(
16848 "Hello, world!",
16849 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16850 true,
16851 cx,
16852 |highlighted_edits, cx| {
16853 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16854 assert_eq!(highlighted_edits.highlights.len(), 1);
16855 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16856 assert_eq!(
16857 highlighted_edits.highlights[0].1.background_color,
16858 Some(cx.theme().status().created_background)
16859 );
16860 },
16861 )
16862 .await;
16863
16864 // Replacement
16865 assert_highlighted_edits(
16866 "This is a test.",
16867 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16868 false,
16869 cx,
16870 |highlighted_edits, cx| {
16871 assert_eq!(highlighted_edits.text, "That is a test.");
16872 assert_eq!(highlighted_edits.highlights.len(), 1);
16873 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16874 assert_eq!(
16875 highlighted_edits.highlights[0].1.background_color,
16876 Some(cx.theme().status().created_background)
16877 );
16878 },
16879 )
16880 .await;
16881
16882 // Multiple edits
16883 assert_highlighted_edits(
16884 "Hello, world!",
16885 vec![
16886 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16887 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16888 ],
16889 false,
16890 cx,
16891 |highlighted_edits, cx| {
16892 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16893 assert_eq!(highlighted_edits.highlights.len(), 2);
16894 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16895 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16896 assert_eq!(
16897 highlighted_edits.highlights[0].1.background_color,
16898 Some(cx.theme().status().created_background)
16899 );
16900 assert_eq!(
16901 highlighted_edits.highlights[1].1.background_color,
16902 Some(cx.theme().status().created_background)
16903 );
16904 },
16905 )
16906 .await;
16907
16908 // Multiple lines with edits
16909 assert_highlighted_edits(
16910 "First line\nSecond line\nThird line\nFourth line",
16911 vec![
16912 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16913 (
16914 Point::new(2, 0)..Point::new(2, 10),
16915 "New third line".to_string(),
16916 ),
16917 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16918 ],
16919 false,
16920 cx,
16921 |highlighted_edits, cx| {
16922 assert_eq!(
16923 highlighted_edits.text,
16924 "Second modified\nNew third line\nFourth updated line"
16925 );
16926 assert_eq!(highlighted_edits.highlights.len(), 3);
16927 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16928 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16929 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16930 for highlight in &highlighted_edits.highlights {
16931 assert_eq!(
16932 highlight.1.background_color,
16933 Some(cx.theme().status().created_background)
16934 );
16935 }
16936 },
16937 )
16938 .await;
16939}
16940
16941#[gpui::test]
16942async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16943 init_test(cx, |_| {});
16944
16945 // Deletion
16946 assert_highlighted_edits(
16947 "Hello, world!",
16948 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16949 true,
16950 cx,
16951 |highlighted_edits, cx| {
16952 assert_eq!(highlighted_edits.text, "Hello, world!");
16953 assert_eq!(highlighted_edits.highlights.len(), 1);
16954 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16955 assert_eq!(
16956 highlighted_edits.highlights[0].1.background_color,
16957 Some(cx.theme().status().deleted_background)
16958 );
16959 },
16960 )
16961 .await;
16962
16963 // Insertion
16964 assert_highlighted_edits(
16965 "Hello, world!",
16966 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16967 true,
16968 cx,
16969 |highlighted_edits, cx| {
16970 assert_eq!(highlighted_edits.highlights.len(), 1);
16971 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16972 assert_eq!(
16973 highlighted_edits.highlights[0].1.background_color,
16974 Some(cx.theme().status().created_background)
16975 );
16976 },
16977 )
16978 .await;
16979}
16980
16981async fn assert_highlighted_edits(
16982 text: &str,
16983 edits: Vec<(Range<Point>, String)>,
16984 include_deletions: bool,
16985 cx: &mut TestAppContext,
16986 assertion_fn: impl Fn(HighlightedText, &App),
16987) {
16988 let window = cx.add_window(|window, cx| {
16989 let buffer = MultiBuffer::build_simple(text, cx);
16990 Editor::new(EditorMode::Full, buffer, None, window, cx)
16991 });
16992 let cx = &mut VisualTestContext::from_window(*window, cx);
16993
16994 let (buffer, snapshot) = window
16995 .update(cx, |editor, _window, cx| {
16996 (
16997 editor.buffer().clone(),
16998 editor.buffer().read(cx).snapshot(cx),
16999 )
17000 })
17001 .unwrap();
17002
17003 let edits = edits
17004 .into_iter()
17005 .map(|(range, edit)| {
17006 (
17007 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17008 edit,
17009 )
17010 })
17011 .collect::<Vec<_>>();
17012
17013 let text_anchor_edits = edits
17014 .clone()
17015 .into_iter()
17016 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17017 .collect::<Vec<_>>();
17018
17019 let edit_preview = window
17020 .update(cx, |_, _window, cx| {
17021 buffer
17022 .read(cx)
17023 .as_singleton()
17024 .unwrap()
17025 .read(cx)
17026 .preview_edits(text_anchor_edits.into(), cx)
17027 })
17028 .unwrap()
17029 .await;
17030
17031 cx.update(|_window, cx| {
17032 let highlighted_edits = inline_completion_edit_text(
17033 &snapshot.as_singleton().unwrap().2,
17034 &edits,
17035 &edit_preview,
17036 include_deletions,
17037 cx,
17038 );
17039 assertion_fn(highlighted_edits, cx)
17040 });
17041}
17042
17043#[gpui::test]
17044async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17045 init_test(cx, |_| {});
17046 let capabilities = lsp::ServerCapabilities {
17047 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17048 prepare_provider: Some(true),
17049 work_done_progress_options: Default::default(),
17050 })),
17051 ..Default::default()
17052 };
17053 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17054
17055 cx.set_state(indoc! {"
17056 struct Fˇoo {}
17057 "});
17058
17059 cx.update_editor(|editor, _, cx| {
17060 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17061 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17062 editor.highlight_background::<DocumentHighlightRead>(
17063 &[highlight_range],
17064 |c| c.editor_document_highlight_read_background,
17065 cx,
17066 );
17067 });
17068
17069 let mut prepare_rename_handler =
17070 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17071 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17072 start: lsp::Position {
17073 line: 0,
17074 character: 7,
17075 },
17076 end: lsp::Position {
17077 line: 0,
17078 character: 10,
17079 },
17080 })))
17081 });
17082 let prepare_rename_task = cx
17083 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17084 .expect("Prepare rename was not started");
17085 prepare_rename_handler.next().await.unwrap();
17086 prepare_rename_task.await.expect("Prepare rename failed");
17087
17088 let mut rename_handler =
17089 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17090 let edit = lsp::TextEdit {
17091 range: lsp::Range {
17092 start: lsp::Position {
17093 line: 0,
17094 character: 7,
17095 },
17096 end: lsp::Position {
17097 line: 0,
17098 character: 10,
17099 },
17100 },
17101 new_text: "FooRenamed".to_string(),
17102 };
17103 Ok(Some(lsp::WorkspaceEdit::new(
17104 // Specify the same edit twice
17105 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17106 )))
17107 });
17108 let rename_task = cx
17109 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17110 .expect("Confirm rename was not started");
17111 rename_handler.next().await.unwrap();
17112 rename_task.await.expect("Confirm rename failed");
17113 cx.run_until_parked();
17114
17115 // Despite two edits, only one is actually applied as those are identical
17116 cx.assert_editor_state(indoc! {"
17117 struct FooRenamedˇ {}
17118 "});
17119}
17120
17121#[gpui::test]
17122async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17123 init_test(cx, |_| {});
17124 // These capabilities indicate that the server does not support prepare rename.
17125 let capabilities = lsp::ServerCapabilities {
17126 rename_provider: Some(lsp::OneOf::Left(true)),
17127 ..Default::default()
17128 };
17129 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17130
17131 cx.set_state(indoc! {"
17132 struct Fˇoo {}
17133 "});
17134
17135 cx.update_editor(|editor, _window, cx| {
17136 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17137 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17138 editor.highlight_background::<DocumentHighlightRead>(
17139 &[highlight_range],
17140 |c| c.editor_document_highlight_read_background,
17141 cx,
17142 );
17143 });
17144
17145 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17146 .expect("Prepare rename was not started")
17147 .await
17148 .expect("Prepare rename failed");
17149
17150 let mut rename_handler =
17151 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17152 let edit = lsp::TextEdit {
17153 range: lsp::Range {
17154 start: lsp::Position {
17155 line: 0,
17156 character: 7,
17157 },
17158 end: lsp::Position {
17159 line: 0,
17160 character: 10,
17161 },
17162 },
17163 new_text: "FooRenamed".to_string(),
17164 };
17165 Ok(Some(lsp::WorkspaceEdit::new(
17166 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17167 )))
17168 });
17169 let rename_task = cx
17170 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17171 .expect("Confirm rename was not started");
17172 rename_handler.next().await.unwrap();
17173 rename_task.await.expect("Confirm rename failed");
17174 cx.run_until_parked();
17175
17176 // Correct range is renamed, as `surrounding_word` is used to find it.
17177 cx.assert_editor_state(indoc! {"
17178 struct FooRenamedˇ {}
17179 "});
17180}
17181
17182#[gpui::test]
17183async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17184 init_test(cx, |_| {});
17185 let mut cx = EditorTestContext::new(cx).await;
17186
17187 let language = Arc::new(
17188 Language::new(
17189 LanguageConfig::default(),
17190 Some(tree_sitter_html::LANGUAGE.into()),
17191 )
17192 .with_brackets_query(
17193 r#"
17194 ("<" @open "/>" @close)
17195 ("</" @open ">" @close)
17196 ("<" @open ">" @close)
17197 ("\"" @open "\"" @close)
17198 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17199 "#,
17200 )
17201 .unwrap(),
17202 );
17203 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17204
17205 cx.set_state(indoc! {"
17206 <span>ˇ</span>
17207 "});
17208 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17209 cx.assert_editor_state(indoc! {"
17210 <span>
17211 ˇ
17212 </span>
17213 "});
17214
17215 cx.set_state(indoc! {"
17216 <span><span></span>ˇ</span>
17217 "});
17218 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17219 cx.assert_editor_state(indoc! {"
17220 <span><span></span>
17221 ˇ</span>
17222 "});
17223
17224 cx.set_state(indoc! {"
17225 <span>ˇ
17226 </span>
17227 "});
17228 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17229 cx.assert_editor_state(indoc! {"
17230 <span>
17231 ˇ
17232 </span>
17233 "});
17234}
17235
17236#[gpui::test(iterations = 10)]
17237async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
17238 init_test(cx, |_| {});
17239
17240 let fs = FakeFs::new(cx.executor());
17241 fs.insert_tree(
17242 path!("/dir"),
17243 json!({
17244 "a.ts": "a",
17245 }),
17246 )
17247 .await;
17248
17249 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
17250 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17251 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17252
17253 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17254 language_registry.add(Arc::new(Language::new(
17255 LanguageConfig {
17256 name: "TypeScript".into(),
17257 matcher: LanguageMatcher {
17258 path_suffixes: vec!["ts".to_string()],
17259 ..Default::default()
17260 },
17261 ..Default::default()
17262 },
17263 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17264 )));
17265 let mut fake_language_servers = language_registry.register_fake_lsp(
17266 "TypeScript",
17267 FakeLspAdapter {
17268 capabilities: lsp::ServerCapabilities {
17269 code_lens_provider: Some(lsp::CodeLensOptions {
17270 resolve_provider: Some(true),
17271 }),
17272 execute_command_provider: Some(lsp::ExecuteCommandOptions {
17273 commands: vec!["_the/command".to_string()],
17274 ..lsp::ExecuteCommandOptions::default()
17275 }),
17276 ..lsp::ServerCapabilities::default()
17277 },
17278 ..FakeLspAdapter::default()
17279 },
17280 );
17281
17282 let (buffer, _handle) = project
17283 .update(cx, |p, cx| {
17284 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
17285 })
17286 .await
17287 .unwrap();
17288 cx.executor().run_until_parked();
17289
17290 let fake_server = fake_language_servers.next().await.unwrap();
17291
17292 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
17293 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
17294 drop(buffer_snapshot);
17295 let actions = cx
17296 .update_window(*workspace, |_, window, cx| {
17297 project.code_actions(&buffer, anchor..anchor, window, cx)
17298 })
17299 .unwrap();
17300
17301 fake_server
17302 .handle_request::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
17303 Ok(Some(vec![
17304 lsp::CodeLens {
17305 range: lsp::Range::default(),
17306 command: Some(lsp::Command {
17307 title: "Code lens command".to_owned(),
17308 command: "_the/command".to_owned(),
17309 arguments: None,
17310 }),
17311 data: None,
17312 },
17313 lsp::CodeLens {
17314 range: lsp::Range::default(),
17315 command: Some(lsp::Command {
17316 title: "Command not in capabilities".to_owned(),
17317 command: "not in capabilities".to_owned(),
17318 arguments: None,
17319 }),
17320 data: None,
17321 },
17322 lsp::CodeLens {
17323 range: lsp::Range {
17324 start: lsp::Position {
17325 line: 1,
17326 character: 1,
17327 },
17328 end: lsp::Position {
17329 line: 1,
17330 character: 1,
17331 },
17332 },
17333 command: Some(lsp::Command {
17334 title: "Command not in range".to_owned(),
17335 command: "_the/command".to_owned(),
17336 arguments: None,
17337 }),
17338 data: None,
17339 },
17340 ]))
17341 })
17342 .next()
17343 .await;
17344
17345 let actions = actions.await.unwrap();
17346 assert_eq!(
17347 actions.len(),
17348 1,
17349 "Should have only one valid action for the 0..0 range"
17350 );
17351 let action = actions[0].clone();
17352 let apply = project.update(cx, |project, cx| {
17353 project.apply_code_action(buffer.clone(), action, true, cx)
17354 });
17355
17356 // Resolving the code action does not populate its edits. In absence of
17357 // edits, we must execute the given command.
17358 fake_server.handle_request::<lsp::request::CodeLensResolve, _, _>(|mut lens, _| async move {
17359 let lens_command = lens.command.as_mut().expect("should have a command");
17360 assert_eq!(lens_command.title, "Code lens command");
17361 lens_command.arguments = Some(vec![json!("the-argument")]);
17362 Ok(lens)
17363 });
17364
17365 // While executing the command, the language server sends the editor
17366 // a `workspaceEdit` request.
17367 fake_server
17368 .handle_request::<lsp::request::ExecuteCommand, _, _>({
17369 let fake = fake_server.clone();
17370 move |params, _| {
17371 assert_eq!(params.command, "_the/command");
17372 let fake = fake.clone();
17373 async move {
17374 fake.server
17375 .request::<lsp::request::ApplyWorkspaceEdit>(
17376 lsp::ApplyWorkspaceEditParams {
17377 label: None,
17378 edit: lsp::WorkspaceEdit {
17379 changes: Some(
17380 [(
17381 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
17382 vec![lsp::TextEdit {
17383 range: lsp::Range::new(
17384 lsp::Position::new(0, 0),
17385 lsp::Position::new(0, 0),
17386 ),
17387 new_text: "X".into(),
17388 }],
17389 )]
17390 .into_iter()
17391 .collect(),
17392 ),
17393 ..Default::default()
17394 },
17395 },
17396 )
17397 .await
17398 .unwrap();
17399 Ok(Some(json!(null)))
17400 }
17401 }
17402 })
17403 .next()
17404 .await;
17405
17406 // Applying the code lens command returns a project transaction containing the edits
17407 // sent by the language server in its `workspaceEdit` request.
17408 let transaction = apply.await.unwrap();
17409 assert!(transaction.0.contains_key(&buffer));
17410 buffer.update(cx, |buffer, cx| {
17411 assert_eq!(buffer.text(), "Xa");
17412 buffer.undo(cx);
17413 assert_eq!(buffer.text(), "a");
17414 });
17415}
17416
17417mod autoclose_tags {
17418 use super::*;
17419 use language::language_settings::JsxTagAutoCloseSettings;
17420 use languages::language;
17421
17422 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17423 init_test(cx, |settings| {
17424 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17425 });
17426
17427 let mut cx = EditorTestContext::new(cx).await;
17428 cx.update_buffer(|buffer, cx| {
17429 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17430
17431 buffer.set_language(Some(language), cx)
17432 });
17433
17434 cx
17435 }
17436
17437 macro_rules! check {
17438 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17439 #[gpui::test]
17440 async fn $name(cx: &mut TestAppContext) {
17441 let mut cx = test_setup(cx).await;
17442 cx.set_state($initial);
17443 cx.run_until_parked();
17444
17445 cx.update_editor(|editor, window, cx| {
17446 editor.handle_input($input, window, cx);
17447 });
17448 cx.run_until_parked();
17449 cx.assert_editor_state($expected);
17450 }
17451 };
17452 }
17453
17454 check!(
17455 test_basic,
17456 "<divˇ" + ">" => "<div>ˇ</div>"
17457 );
17458
17459 check!(
17460 test_basic_nested,
17461 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17462 );
17463
17464 check!(
17465 test_basic_ignore_already_closed,
17466 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17467 );
17468
17469 check!(
17470 test_doesnt_autoclose_closing_tag,
17471 "</divˇ" + ">" => "</div>ˇ"
17472 );
17473
17474 check!(
17475 test_jsx_attr,
17476 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17477 );
17478
17479 check!(
17480 test_ignores_closing_tags_in_expr_block,
17481 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17482 );
17483
17484 check!(
17485 test_doesnt_autoclose_on_gt_in_expr,
17486 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17487 );
17488
17489 check!(
17490 test_ignores_closing_tags_with_different_tag_names,
17491 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17492 );
17493
17494 check!(
17495 test_autocloses_in_jsx_expression,
17496 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17497 );
17498
17499 check!(
17500 test_doesnt_autoclose_already_closed_in_jsx_expression,
17501 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17502 );
17503
17504 check!(
17505 test_autocloses_fragment,
17506 "<ˇ" + ">" => "<>ˇ</>"
17507 );
17508
17509 check!(
17510 test_does_not_include_type_argument_in_autoclose_tag_name,
17511 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17512 );
17513
17514 check!(
17515 test_does_not_autoclose_doctype,
17516 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17517 );
17518
17519 check!(
17520 test_does_not_autoclose_comment,
17521 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17522 );
17523
17524 check!(
17525 test_multi_cursor_autoclose_same_tag,
17526 r#"
17527 <divˇ
17528 <divˇ
17529 "#
17530 + ">" =>
17531 r#"
17532 <div>ˇ</div>
17533 <div>ˇ</div>
17534 "#
17535 );
17536
17537 check!(
17538 test_multi_cursor_autoclose_different_tags,
17539 r#"
17540 <divˇ
17541 <spanˇ
17542 "#
17543 + ">" =>
17544 r#"
17545 <div>ˇ</div>
17546 <span>ˇ</span>
17547 "#
17548 );
17549
17550 check!(
17551 test_multi_cursor_autoclose_some_dont_autoclose_others,
17552 r#"
17553 <divˇ
17554 <div /ˇ
17555 <spanˇ</span>
17556 <!DOCTYPE htmlˇ
17557 </headˇ
17558 <Component<T>ˇ
17559 ˇ
17560 "#
17561 + ">" =>
17562 r#"
17563 <div>ˇ</div>
17564 <div />ˇ
17565 <span>ˇ</span>
17566 <!DOCTYPE html>ˇ
17567 </head>ˇ
17568 <Component<T>>ˇ</Component>
17569 >ˇ
17570 "#
17571 );
17572
17573 check!(
17574 test_doesnt_mess_up_trailing_text,
17575 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17576 );
17577
17578 #[gpui::test]
17579 async fn test_multibuffer(cx: &mut TestAppContext) {
17580 init_test(cx, |settings| {
17581 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17582 });
17583
17584 let buffer_a = cx.new(|cx| {
17585 let mut buf = language::Buffer::local("<div", cx);
17586 buf.set_language(
17587 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17588 cx,
17589 );
17590 buf
17591 });
17592 let buffer_b = cx.new(|cx| {
17593 let mut buf = language::Buffer::local("<pre", cx);
17594 buf.set_language(
17595 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17596 cx,
17597 );
17598 buf
17599 });
17600 let buffer_c = cx.new(|cx| {
17601 let buf = language::Buffer::local("<span", cx);
17602 buf
17603 });
17604 let buffer = cx.new(|cx| {
17605 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17606 buf.push_excerpts(
17607 buffer_a,
17608 [ExcerptRange {
17609 context: text::Anchor::MIN..text::Anchor::MAX,
17610 primary: None,
17611 }],
17612 cx,
17613 );
17614 buf.push_excerpts(
17615 buffer_b,
17616 [ExcerptRange {
17617 context: text::Anchor::MIN..text::Anchor::MAX,
17618 primary: None,
17619 }],
17620 cx,
17621 );
17622 buf.push_excerpts(
17623 buffer_c,
17624 [ExcerptRange {
17625 context: text::Anchor::MIN..text::Anchor::MAX,
17626 primary: None,
17627 }],
17628 cx,
17629 );
17630 buf
17631 });
17632 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17633
17634 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17635
17636 cx.update_editor(|editor, window, cx| {
17637 editor.change_selections(None, window, cx, |selections| {
17638 selections.select(vec![
17639 Selection::from_offset(4),
17640 Selection::from_offset(9),
17641 Selection::from_offset(15),
17642 ])
17643 })
17644 });
17645 cx.run_until_parked();
17646
17647 cx.update_editor(|editor, window, cx| {
17648 editor.handle_input(">", window, cx);
17649 });
17650 cx.run_until_parked();
17651
17652 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17653 }
17654}
17655
17656fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17657 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17658 point..point
17659}
17660
17661fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17662 let (text, ranges) = marked_text_ranges(marked_text, true);
17663 assert_eq!(editor.text(cx), text);
17664 assert_eq!(
17665 editor.selections.ranges(cx),
17666 ranges,
17667 "Assert selections are {}",
17668 marked_text
17669 );
17670}
17671
17672pub fn handle_signature_help_request(
17673 cx: &mut EditorLspTestContext,
17674 mocked_response: lsp::SignatureHelp,
17675) -> impl Future<Output = ()> {
17676 let mut request =
17677 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17678 let mocked_response = mocked_response.clone();
17679 async move { Ok(Some(mocked_response)) }
17680 });
17681
17682 async move {
17683 request.next().await;
17684 }
17685}
17686
17687/// Handle completion request passing a marked string specifying where the completion
17688/// should be triggered from using '|' character, what range should be replaced, and what completions
17689/// should be returned using '<' and '>' to delimit the range
17690pub fn handle_completion_request(
17691 cx: &mut EditorLspTestContext,
17692 marked_string: &str,
17693 completions: Vec<&'static str>,
17694 counter: Arc<AtomicUsize>,
17695) -> impl Future<Output = ()> {
17696 let complete_from_marker: TextRangeMarker = '|'.into();
17697 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17698 let (_, mut marked_ranges) = marked_text_ranges_by(
17699 marked_string,
17700 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17701 );
17702
17703 let complete_from_position =
17704 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17705 let replace_range =
17706 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17707
17708 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17709 let completions = completions.clone();
17710 counter.fetch_add(1, atomic::Ordering::Release);
17711 async move {
17712 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17713 assert_eq!(
17714 params.text_document_position.position,
17715 complete_from_position
17716 );
17717 Ok(Some(lsp::CompletionResponse::Array(
17718 completions
17719 .iter()
17720 .map(|completion_text| lsp::CompletionItem {
17721 label: completion_text.to_string(),
17722 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17723 range: replace_range,
17724 new_text: completion_text.to_string(),
17725 })),
17726 ..Default::default()
17727 })
17728 .collect(),
17729 )))
17730 }
17731 });
17732
17733 async move {
17734 request.next().await;
17735 }
17736}
17737
17738fn handle_resolve_completion_request(
17739 cx: &mut EditorLspTestContext,
17740 edits: Option<Vec<(&'static str, &'static str)>>,
17741) -> impl Future<Output = ()> {
17742 let edits = edits.map(|edits| {
17743 edits
17744 .iter()
17745 .map(|(marked_string, new_text)| {
17746 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17747 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17748 lsp::TextEdit::new(replace_range, new_text.to_string())
17749 })
17750 .collect::<Vec<_>>()
17751 });
17752
17753 let mut request =
17754 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17755 let edits = edits.clone();
17756 async move {
17757 Ok(lsp::CompletionItem {
17758 additional_text_edits: edits,
17759 ..Default::default()
17760 })
17761 }
17762 });
17763
17764 async move {
17765 request.next().await;
17766 }
17767}
17768
17769pub(crate) fn update_test_language_settings(
17770 cx: &mut TestAppContext,
17771 f: impl Fn(&mut AllLanguageSettingsContent),
17772) {
17773 cx.update(|cx| {
17774 SettingsStore::update_global(cx, |store, cx| {
17775 store.update_user_settings::<AllLanguageSettings>(cx, f);
17776 });
17777 });
17778}
17779
17780pub(crate) fn update_test_project_settings(
17781 cx: &mut TestAppContext,
17782 f: impl Fn(&mut ProjectSettings),
17783) {
17784 cx.update(|cx| {
17785 SettingsStore::update_global(cx, |store, cx| {
17786 store.update_user_settings::<ProjectSettings>(cx, f);
17787 });
17788 });
17789}
17790
17791pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17792 cx.update(|cx| {
17793 assets::Assets.load_test_fonts(cx);
17794 let store = SettingsStore::test(cx);
17795 cx.set_global(store);
17796 theme::init(theme::LoadThemes::JustBase, cx);
17797 release_channel::init(SemanticVersion::default(), cx);
17798 client::init_settings(cx);
17799 language::init(cx);
17800 Project::init_settings(cx);
17801 workspace::init_settings(cx);
17802 crate::init(cx);
17803 });
17804
17805 update_test_language_settings(cx, f);
17806}
17807
17808#[track_caller]
17809fn assert_hunk_revert(
17810 not_reverted_text_with_selections: &str,
17811 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17812 expected_reverted_text_with_selections: &str,
17813 base_text: &str,
17814 cx: &mut EditorLspTestContext,
17815) {
17816 cx.set_state(not_reverted_text_with_selections);
17817 cx.set_head_text(base_text);
17818 cx.executor().run_until_parked();
17819
17820 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17821 let snapshot = editor.snapshot(window, cx);
17822 let reverted_hunk_statuses = snapshot
17823 .buffer_snapshot
17824 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17825 .map(|hunk| hunk.status().kind)
17826 .collect::<Vec<_>>();
17827
17828 editor.git_restore(&Default::default(), window, cx);
17829 reverted_hunk_statuses
17830 });
17831 cx.executor().run_until_parked();
17832 cx.assert_editor_state(expected_reverted_text_with_selections);
17833 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17834}