1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor,
6 editor_lsp_test_context::{git_commit_lang, EditorLspTestContext},
7 editor_test_context::EditorTestContext,
8 select_ranges,
9 },
10 JoinLines,
11};
12use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
13use futures::StreamExt;
14use gpui::{
15 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
16 WindowBounds, WindowOptions,
17};
18use indoc::indoc;
19use language::{
20 language_settings::{
21 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
22 LanguageSettingsContent, PrettierSettings,
23 },
24 BracketPairConfig,
25 Capability::ReadWrite,
26 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
27 Override, Point,
28};
29use language_settings::{Formatter, FormatterList, IndentGuideSettings};
30use multi_buffer::{IndentGuide, PathKey};
31use parking_lot::Mutex;
32use pretty_assertions::{assert_eq, assert_ne};
33use project::{
34 debugger::breakpoint_store::{BreakpointKind, SerializedBreakpoint},
35 project_settings::{LspSettings, ProjectSettings},
36 FakeFs,
37};
38use serde_json::{self, json};
39use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
40use std::{
41 iter,
42 sync::atomic::{self, AtomicUsize},
43};
44use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
45use text::ToPoint as _;
46use unindent::Unindent;
47use util::{
48 assert_set_eq, path,
49 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
50 uri,
51};
52use workspace::{
53 item::{FollowEvent, FollowableItem, Item, ItemHandle},
54 NavigationEntry, ViewId,
55};
56
57#[gpui::test]
58fn test_edit_events(cx: &mut TestAppContext) {
59 init_test(cx, |_| {});
60
61 let buffer = cx.new(|cx| {
62 let mut buffer = language::Buffer::local("123456", cx);
63 buffer.set_group_interval(Duration::from_secs(1));
64 buffer
65 });
66
67 let events = Rc::new(RefCell::new(Vec::new()));
68 let editor1 = cx.add_window({
69 let events = events.clone();
70 |window, cx| {
71 let entity = cx.entity().clone();
72 cx.subscribe_in(
73 &entity,
74 window,
75 move |_, _, event: &EditorEvent, _, _| match event {
76 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
77 EditorEvent::BufferEdited => {
78 events.borrow_mut().push(("editor1", "buffer edited"))
79 }
80 _ => {}
81 },
82 )
83 .detach();
84 Editor::for_buffer(buffer.clone(), None, window, cx)
85 }
86 });
87
88 let editor2 = cx.add_window({
89 let events = events.clone();
90 |window, cx| {
91 cx.subscribe_in(
92 &cx.entity().clone(),
93 window,
94 move |_, _, event: &EditorEvent, _, _| match event {
95 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
96 EditorEvent::BufferEdited => {
97 events.borrow_mut().push(("editor2", "buffer edited"))
98 }
99 _ => {}
100 },
101 )
102 .detach();
103 Editor::for_buffer(buffer.clone(), None, window, cx)
104 }
105 });
106
107 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
108
109 // Mutating editor 1 will emit an `Edited` event only for that editor.
110 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
111 assert_eq!(
112 mem::take(&mut *events.borrow_mut()),
113 [
114 ("editor1", "edited"),
115 ("editor1", "buffer edited"),
116 ("editor2", "buffer edited"),
117 ]
118 );
119
120 // Mutating editor 2 will emit an `Edited` event only for that editor.
121 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
122 assert_eq!(
123 mem::take(&mut *events.borrow_mut()),
124 [
125 ("editor2", "edited"),
126 ("editor1", "buffer edited"),
127 ("editor2", "buffer edited"),
128 ]
129 );
130
131 // Undoing on editor 1 will emit an `Edited` event only for that editor.
132 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
133 assert_eq!(
134 mem::take(&mut *events.borrow_mut()),
135 [
136 ("editor1", "edited"),
137 ("editor1", "buffer edited"),
138 ("editor2", "buffer edited"),
139 ]
140 );
141
142 // Redoing on editor 1 will emit an `Edited` event only for that editor.
143 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
144 assert_eq!(
145 mem::take(&mut *events.borrow_mut()),
146 [
147 ("editor1", "edited"),
148 ("editor1", "buffer edited"),
149 ("editor2", "buffer edited"),
150 ]
151 );
152
153 // Undoing on editor 2 will emit an `Edited` event only for that editor.
154 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
155 assert_eq!(
156 mem::take(&mut *events.borrow_mut()),
157 [
158 ("editor2", "edited"),
159 ("editor1", "buffer edited"),
160 ("editor2", "buffer edited"),
161 ]
162 );
163
164 // Redoing on editor 2 will emit an `Edited` event only for that editor.
165 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
166 assert_eq!(
167 mem::take(&mut *events.borrow_mut()),
168 [
169 ("editor2", "edited"),
170 ("editor1", "buffer edited"),
171 ("editor2", "buffer edited"),
172 ]
173 );
174
175 // No event is emitted when the mutation is a no-op.
176 _ = editor2.update(cx, |editor, window, cx| {
177 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
178
179 editor.backspace(&Backspace, window, cx);
180 });
181 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
182}
183
184#[gpui::test]
185fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
186 init_test(cx, |_| {});
187
188 let mut now = Instant::now();
189 let group_interval = Duration::from_millis(1);
190 let buffer = cx.new(|cx| {
191 let mut buf = language::Buffer::local("123456", cx);
192 buf.set_group_interval(group_interval);
193 buf
194 });
195 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
196 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
197
198 _ = editor.update(cx, |editor, window, cx| {
199 editor.start_transaction_at(now, window, cx);
200 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
201
202 editor.insert("cd", window, cx);
203 editor.end_transaction_at(now, cx);
204 assert_eq!(editor.text(cx), "12cd56");
205 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
206
207 editor.start_transaction_at(now, window, cx);
208 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
209 editor.insert("e", window, cx);
210 editor.end_transaction_at(now, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
213
214 now += group_interval + Duration::from_millis(1);
215 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
216
217 // Simulate an edit in another editor
218 buffer.update(cx, |buffer, cx| {
219 buffer.start_transaction_at(now, cx);
220 buffer.edit([(0..1, "a")], None, cx);
221 buffer.edit([(1..1, "b")], None, cx);
222 buffer.end_transaction_at(now, cx);
223 });
224
225 assert_eq!(editor.text(cx), "ab2cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
227
228 // Last transaction happened past the group interval in a different editor.
229 // Undo it individually and don't restore selections.
230 editor.undo(&Undo, window, cx);
231 assert_eq!(editor.text(cx), "12cde6");
232 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
233
234 // First two transactions happened within the group interval in this editor.
235 // Undo them together and restore selections.
236 editor.undo(&Undo, window, cx);
237 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
238 assert_eq!(editor.text(cx), "123456");
239 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
240
241 // Redo the first two transactions together.
242 editor.redo(&Redo, window, cx);
243 assert_eq!(editor.text(cx), "12cde6");
244 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
245
246 // Redo the last transaction on its own.
247 editor.redo(&Redo, window, cx);
248 assert_eq!(editor.text(cx), "ab2cde6");
249 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
250
251 // Test empty transactions.
252 editor.start_transaction_at(now, window, cx);
253 editor.end_transaction_at(now, cx);
254 editor.undo(&Undo, window, cx);
255 assert_eq!(editor.text(cx), "12cde6");
256 });
257}
258
259#[gpui::test]
260fn test_ime_composition(cx: &mut TestAppContext) {
261 init_test(cx, |_| {});
262
263 let buffer = cx.new(|cx| {
264 let mut buffer = language::Buffer::local("abcde", cx);
265 // Ensure automatic grouping doesn't occur.
266 buffer.set_group_interval(Duration::ZERO);
267 buffer
268 });
269
270 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
271 cx.add_window(|window, cx| {
272 let mut editor = build_editor(buffer.clone(), window, cx);
273
274 // Start a new IME composition.
275 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
276 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
277 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
278 assert_eq!(editor.text(cx), "äbcde");
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Finalize IME composition.
285 editor.replace_text_in_range(None, "ā", window, cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // IME composition edits are grouped and are undone/redone at once.
290 editor.undo(&Default::default(), window, cx);
291 assert_eq!(editor.text(cx), "abcde");
292 assert_eq!(editor.marked_text_ranges(cx), None);
293 editor.redo(&Default::default(), window, cx);
294 assert_eq!(editor.text(cx), "ābcde");
295 assert_eq!(editor.marked_text_ranges(cx), None);
296
297 // Start a new IME composition.
298 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
299 assert_eq!(
300 editor.marked_text_ranges(cx),
301 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
302 );
303
304 // Undoing during an IME composition cancels it.
305 editor.undo(&Default::default(), window, cx);
306 assert_eq!(editor.text(cx), "ābcde");
307 assert_eq!(editor.marked_text_ranges(cx), None);
308
309 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
310 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
311 assert_eq!(editor.text(cx), "ābcdè");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
315 );
316
317 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
318 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
319 assert_eq!(editor.text(cx), "ābcdę");
320 assert_eq!(editor.marked_text_ranges(cx), None);
321
322 // Start a new IME composition with multiple cursors.
323 editor.change_selections(None, window, cx, |s| {
324 s.select_ranges([
325 OffsetUtf16(1)..OffsetUtf16(1),
326 OffsetUtf16(3)..OffsetUtf16(3),
327 OffsetUtf16(5)..OffsetUtf16(5),
328 ])
329 });
330 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
331 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
332 assert_eq!(
333 editor.marked_text_ranges(cx),
334 Some(vec![
335 OffsetUtf16(0)..OffsetUtf16(3),
336 OffsetUtf16(4)..OffsetUtf16(7),
337 OffsetUtf16(8)..OffsetUtf16(11)
338 ])
339 );
340
341 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
342 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
343 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
344 assert_eq!(
345 editor.marked_text_ranges(cx),
346 Some(vec![
347 OffsetUtf16(1)..OffsetUtf16(2),
348 OffsetUtf16(5)..OffsetUtf16(6),
349 OffsetUtf16(9)..OffsetUtf16(10)
350 ])
351 );
352
353 // Finalize IME composition with multiple cursors.
354 editor.replace_text_in_range(Some(9..10), "2", window, cx);
355 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
356 assert_eq!(editor.marked_text_ranges(cx), None);
357
358 editor
359 });
360}
361
362#[gpui::test]
363fn test_selection_with_mouse(cx: &mut TestAppContext) {
364 init_test(cx, |_| {});
365
366 let editor = cx.add_window(|window, cx| {
367 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
368 build_editor(buffer, window, cx)
369 });
370
371 _ = editor.update(cx, |editor, window, cx| {
372 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
373 });
374 assert_eq!(
375 editor
376 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
379 );
380
381 _ = editor.update(cx, |editor, window, cx| {
382 editor.update_selection(
383 DisplayPoint::new(DisplayRow(3), 3),
384 0,
385 gpui::Point::<f32>::default(),
386 window,
387 cx,
388 );
389 });
390
391 assert_eq!(
392 editor
393 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
394 .unwrap(),
395 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
396 );
397
398 _ = editor.update(cx, |editor, window, cx| {
399 editor.update_selection(
400 DisplayPoint::new(DisplayRow(1), 1),
401 0,
402 gpui::Point::<f32>::default(),
403 window,
404 cx,
405 );
406 });
407
408 assert_eq!(
409 editor
410 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
411 .unwrap(),
412 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
413 );
414
415 _ = editor.update(cx, |editor, window, cx| {
416 editor.end_selection(window, cx);
417 editor.update_selection(
418 DisplayPoint::new(DisplayRow(3), 3),
419 0,
420 gpui::Point::<f32>::default(),
421 window,
422 cx,
423 );
424 });
425
426 assert_eq!(
427 editor
428 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
429 .unwrap(),
430 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
431 );
432
433 _ = editor.update(cx, |editor, window, cx| {
434 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
435 editor.update_selection(
436 DisplayPoint::new(DisplayRow(0), 0),
437 0,
438 gpui::Point::<f32>::default(),
439 window,
440 cx,
441 );
442 });
443
444 assert_eq!(
445 editor
446 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
447 .unwrap(),
448 [
449 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
450 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
451 ]
452 );
453
454 _ = editor.update(cx, |editor, window, cx| {
455 editor.end_selection(window, cx);
456 });
457
458 assert_eq!(
459 editor
460 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
461 .unwrap(),
462 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
463 );
464}
465
466#[gpui::test]
467fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
468 init_test(cx, |_| {});
469
470 let editor = cx.add_window(|window, cx| {
471 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
472 build_editor(buffer, window, cx)
473 });
474
475 _ = editor.update(cx, |editor, window, cx| {
476 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
477 });
478
479 _ = editor.update(cx, |editor, window, cx| {
480 editor.end_selection(window, cx);
481 });
482
483 _ = editor.update(cx, |editor, window, cx| {
484 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
485 });
486
487 _ = editor.update(cx, |editor, window, cx| {
488 editor.end_selection(window, cx);
489 });
490
491 assert_eq!(
492 editor
493 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
494 .unwrap(),
495 [
496 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
497 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
498 ]
499 );
500
501 _ = editor.update(cx, |editor, window, cx| {
502 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
503 });
504
505 _ = editor.update(cx, |editor, window, cx| {
506 editor.end_selection(window, cx);
507 });
508
509 assert_eq!(
510 editor
511 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
512 .unwrap(),
513 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
514 );
515}
516
517#[gpui::test]
518fn test_canceling_pending_selection(cx: &mut TestAppContext) {
519 init_test(cx, |_| {});
520
521 let editor = cx.add_window(|window, cx| {
522 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
523 build_editor(buffer, window, cx)
524 });
525
526 _ = editor.update(cx, |editor, window, cx| {
527 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
528 assert_eq!(
529 editor.selections.display_ranges(cx),
530 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
531 );
532 });
533
534 _ = editor.update(cx, |editor, window, cx| {
535 editor.update_selection(
536 DisplayPoint::new(DisplayRow(3), 3),
537 0,
538 gpui::Point::<f32>::default(),
539 window,
540 cx,
541 );
542 assert_eq!(
543 editor.selections.display_ranges(cx),
544 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
545 );
546 });
547
548 _ = editor.update(cx, |editor, window, cx| {
549 editor.cancel(&Cancel, window, cx);
550 editor.update_selection(
551 DisplayPoint::new(DisplayRow(1), 1),
552 0,
553 gpui::Point::<f32>::default(),
554 window,
555 cx,
556 );
557 assert_eq!(
558 editor.selections.display_ranges(cx),
559 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
560 );
561 });
562}
563
564#[gpui::test]
565fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
566 init_test(cx, |_| {});
567
568 let editor = cx.add_window(|window, cx| {
569 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
570 build_editor(buffer, window, cx)
571 });
572
573 _ = editor.update(cx, |editor, window, cx| {
574 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
575 assert_eq!(
576 editor.selections.display_ranges(cx),
577 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
578 );
579
580 editor.move_down(&Default::default(), window, cx);
581 assert_eq!(
582 editor.selections.display_ranges(cx),
583 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
584 );
585
586 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
587 assert_eq!(
588 editor.selections.display_ranges(cx),
589 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
590 );
591
592 editor.move_up(&Default::default(), window, cx);
593 assert_eq!(
594 editor.selections.display_ranges(cx),
595 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
596 );
597 });
598}
599
600#[gpui::test]
601fn test_clone(cx: &mut TestAppContext) {
602 init_test(cx, |_| {});
603
604 let (text, selection_ranges) = marked_text_ranges(
605 indoc! {"
606 one
607 two
608 threeˇ
609 four
610 fiveˇ
611 "},
612 true,
613 );
614
615 let editor = cx.add_window(|window, cx| {
616 let buffer = MultiBuffer::build_simple(&text, cx);
617 build_editor(buffer, window, cx)
618 });
619
620 _ = editor.update(cx, |editor, window, cx| {
621 editor.change_selections(None, window, cx, |s| {
622 s.select_ranges(selection_ranges.clone())
623 });
624 editor.fold_creases(
625 vec![
626 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
627 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
628 ],
629 true,
630 window,
631 cx,
632 );
633 });
634
635 let cloned_editor = editor
636 .update(cx, |editor, _, cx| {
637 cx.open_window(Default::default(), |window, cx| {
638 cx.new(|cx| editor.clone(window, cx))
639 })
640 })
641 .unwrap()
642 .unwrap();
643
644 let snapshot = editor
645 .update(cx, |e, window, cx| e.snapshot(window, cx))
646 .unwrap();
647 let cloned_snapshot = cloned_editor
648 .update(cx, |e, window, cx| e.snapshot(window, cx))
649 .unwrap();
650
651 assert_eq!(
652 cloned_editor
653 .update(cx, |e, _, cx| e.display_text(cx))
654 .unwrap(),
655 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
656 );
657 assert_eq!(
658 cloned_snapshot
659 .folds_in_range(0..text.len())
660 .collect::<Vec<_>>(),
661 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
662 );
663 assert_set_eq!(
664 cloned_editor
665 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
666 .unwrap(),
667 editor
668 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
669 .unwrap()
670 );
671 assert_set_eq!(
672 cloned_editor
673 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
674 .unwrap(),
675 editor
676 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
677 .unwrap()
678 );
679}
680
681#[gpui::test]
682async fn test_navigation_history(cx: &mut TestAppContext) {
683 init_test(cx, |_| {});
684
685 use workspace::item::Item;
686
687 let fs = FakeFs::new(cx.executor());
688 let project = Project::test(fs, [], cx).await;
689 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
690 let pane = workspace
691 .update(cx, |workspace, _, _| workspace.active_pane().clone())
692 .unwrap();
693
694 _ = workspace.update(cx, |_v, window, cx| {
695 cx.new(|cx| {
696 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
697 let mut editor = build_editor(buffer.clone(), window, cx);
698 let handle = cx.entity();
699 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
700
701 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
702 editor.nav_history.as_mut().unwrap().pop_backward(cx)
703 }
704
705 // Move the cursor a small distance.
706 // Nothing is added to the navigation history.
707 editor.change_selections(None, window, cx, |s| {
708 s.select_display_ranges([
709 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
710 ])
711 });
712 editor.change_selections(None, window, cx, |s| {
713 s.select_display_ranges([
714 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
715 ])
716 });
717 assert!(pop_history(&mut editor, cx).is_none());
718
719 // Move the cursor a large distance.
720 // The history can jump back to the previous position.
721 editor.change_selections(None, window, cx, |s| {
722 s.select_display_ranges([
723 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
724 ])
725 });
726 let nav_entry = pop_history(&mut editor, cx).unwrap();
727 editor.navigate(nav_entry.data.unwrap(), window, cx);
728 assert_eq!(nav_entry.item.id(), cx.entity_id());
729 assert_eq!(
730 editor.selections.display_ranges(cx),
731 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
732 );
733 assert!(pop_history(&mut editor, cx).is_none());
734
735 // Move the cursor a small distance via the mouse.
736 // Nothing is added to the navigation history.
737 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
738 editor.end_selection(window, cx);
739 assert_eq!(
740 editor.selections.display_ranges(cx),
741 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
742 );
743 assert!(pop_history(&mut editor, cx).is_none());
744
745 // Move the cursor a large distance via the mouse.
746 // The history can jump back to the previous position.
747 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
748 editor.end_selection(window, cx);
749 assert_eq!(
750 editor.selections.display_ranges(cx),
751 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
752 );
753 let nav_entry = pop_history(&mut editor, cx).unwrap();
754 editor.navigate(nav_entry.data.unwrap(), window, cx);
755 assert_eq!(nav_entry.item.id(), cx.entity_id());
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
759 );
760 assert!(pop_history(&mut editor, cx).is_none());
761
762 // Set scroll position to check later
763 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
764 let original_scroll_position = editor.scroll_manager.anchor();
765
766 // Jump to the end of the document and adjust scroll
767 editor.move_to_end(&MoveToEnd, window, cx);
768 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
769 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
770
771 let nav_entry = pop_history(&mut editor, cx).unwrap();
772 editor.navigate(nav_entry.data.unwrap(), window, cx);
773 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
774
775 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
776 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
777 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
778 let invalid_point = Point::new(9999, 0);
779 editor.navigate(
780 Box::new(NavigationData {
781 cursor_anchor: invalid_anchor,
782 cursor_position: invalid_point,
783 scroll_anchor: ScrollAnchor {
784 anchor: invalid_anchor,
785 offset: Default::default(),
786 },
787 scroll_top_row: invalid_point.row,
788 }),
789 window,
790 cx,
791 );
792 assert_eq!(
793 editor.selections.display_ranges(cx),
794 &[editor.max_point(cx)..editor.max_point(cx)]
795 );
796 assert_eq!(
797 editor.scroll_position(cx),
798 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
799 );
800
801 editor
802 })
803 });
804}
805
806#[gpui::test]
807fn test_cancel(cx: &mut TestAppContext) {
808 init_test(cx, |_| {});
809
810 let editor = cx.add_window(|window, cx| {
811 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
812 build_editor(buffer, window, cx)
813 });
814
815 _ = editor.update(cx, |editor, window, cx| {
816 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
817 editor.update_selection(
818 DisplayPoint::new(DisplayRow(1), 1),
819 0,
820 gpui::Point::<f32>::default(),
821 window,
822 cx,
823 );
824 editor.end_selection(window, cx);
825
826 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
827 editor.update_selection(
828 DisplayPoint::new(DisplayRow(0), 3),
829 0,
830 gpui::Point::<f32>::default(),
831 window,
832 cx,
833 );
834 editor.end_selection(window, cx);
835 assert_eq!(
836 editor.selections.display_ranges(cx),
837 [
838 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
839 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
840 ]
841 );
842 });
843
844 _ = editor.update(cx, |editor, window, cx| {
845 editor.cancel(&Cancel, window, cx);
846 assert_eq!(
847 editor.selections.display_ranges(cx),
848 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
849 );
850 });
851
852 _ = editor.update(cx, |editor, window, cx| {
853 editor.cancel(&Cancel, window, cx);
854 assert_eq!(
855 editor.selections.display_ranges(cx),
856 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
857 );
858 });
859}
860
861#[gpui::test]
862fn test_fold_action(cx: &mut TestAppContext) {
863 init_test(cx, |_| {});
864
865 let editor = cx.add_window(|window, cx| {
866 let buffer = MultiBuffer::build_simple(
867 &"
868 impl Foo {
869 // Hello!
870
871 fn a() {
872 1
873 }
874
875 fn b() {
876 2
877 }
878
879 fn c() {
880 3
881 }
882 }
883 "
884 .unindent(),
885 cx,
886 );
887 build_editor(buffer.clone(), window, cx)
888 });
889
890 _ = editor.update(cx, |editor, window, cx| {
891 editor.change_selections(None, window, cx, |s| {
892 s.select_display_ranges([
893 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
894 ]);
895 });
896 editor.fold(&Fold, window, cx);
897 assert_eq!(
898 editor.display_text(cx),
899 "
900 impl Foo {
901 // Hello!
902
903 fn a() {
904 1
905 }
906
907 fn b() {⋯
908 }
909
910 fn c() {⋯
911 }
912 }
913 "
914 .unindent(),
915 );
916
917 editor.fold(&Fold, window, cx);
918 assert_eq!(
919 editor.display_text(cx),
920 "
921 impl Foo {⋯
922 }
923 "
924 .unindent(),
925 );
926
927 editor.unfold_lines(&UnfoldLines, window, cx);
928 assert_eq!(
929 editor.display_text(cx),
930 "
931 impl Foo {
932 // Hello!
933
934 fn a() {
935 1
936 }
937
938 fn b() {⋯
939 }
940
941 fn c() {⋯
942 }
943 }
944 "
945 .unindent(),
946 );
947
948 editor.unfold_lines(&UnfoldLines, window, cx);
949 assert_eq!(
950 editor.display_text(cx),
951 editor.buffer.read(cx).read(cx).text()
952 );
953 });
954}
955
956#[gpui::test]
957fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
958 init_test(cx, |_| {});
959
960 let editor = cx.add_window(|window, cx| {
961 let buffer = MultiBuffer::build_simple(
962 &"
963 class Foo:
964 # Hello!
965
966 def a():
967 print(1)
968
969 def b():
970 print(2)
971
972 def c():
973 print(3)
974 "
975 .unindent(),
976 cx,
977 );
978 build_editor(buffer.clone(), window, cx)
979 });
980
981 _ = editor.update(cx, |editor, window, cx| {
982 editor.change_selections(None, window, cx, |s| {
983 s.select_display_ranges([
984 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
985 ]);
986 });
987 editor.fold(&Fold, window, cx);
988 assert_eq!(
989 editor.display_text(cx),
990 "
991 class Foo:
992 # Hello!
993
994 def a():
995 print(1)
996
997 def b():⋯
998
999 def c():⋯
1000 "
1001 .unindent(),
1002 );
1003
1004 editor.fold(&Fold, window, cx);
1005 assert_eq!(
1006 editor.display_text(cx),
1007 "
1008 class Foo:⋯
1009 "
1010 .unindent(),
1011 );
1012
1013 editor.unfold_lines(&UnfoldLines, window, cx);
1014 assert_eq!(
1015 editor.display_text(cx),
1016 "
1017 class Foo:
1018 # Hello!
1019
1020 def a():
1021 print(1)
1022
1023 def b():⋯
1024
1025 def c():⋯
1026 "
1027 .unindent(),
1028 );
1029
1030 editor.unfold_lines(&UnfoldLines, window, cx);
1031 assert_eq!(
1032 editor.display_text(cx),
1033 editor.buffer.read(cx).read(cx).text()
1034 );
1035 });
1036}
1037
1038#[gpui::test]
1039fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1040 init_test(cx, |_| {});
1041
1042 let editor = cx.add_window(|window, cx| {
1043 let buffer = MultiBuffer::build_simple(
1044 &"
1045 class Foo:
1046 # Hello!
1047
1048 def a():
1049 print(1)
1050
1051 def b():
1052 print(2)
1053
1054
1055 def c():
1056 print(3)
1057
1058
1059 "
1060 .unindent(),
1061 cx,
1062 );
1063 build_editor(buffer.clone(), window, cx)
1064 });
1065
1066 _ = editor.update(cx, |editor, window, cx| {
1067 editor.change_selections(None, window, cx, |s| {
1068 s.select_display_ranges([
1069 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1070 ]);
1071 });
1072 editor.fold(&Fold, window, cx);
1073 assert_eq!(
1074 editor.display_text(cx),
1075 "
1076 class Foo:
1077 # Hello!
1078
1079 def a():
1080 print(1)
1081
1082 def b():⋯
1083
1084
1085 def c():⋯
1086
1087
1088 "
1089 .unindent(),
1090 );
1091
1092 editor.fold(&Fold, window, cx);
1093 assert_eq!(
1094 editor.display_text(cx),
1095 "
1096 class Foo:⋯
1097
1098
1099 "
1100 .unindent(),
1101 );
1102
1103 editor.unfold_lines(&UnfoldLines, window, cx);
1104 assert_eq!(
1105 editor.display_text(cx),
1106 "
1107 class Foo:
1108 # Hello!
1109
1110 def a():
1111 print(1)
1112
1113 def b():⋯
1114
1115
1116 def c():⋯
1117
1118
1119 "
1120 .unindent(),
1121 );
1122
1123 editor.unfold_lines(&UnfoldLines, window, cx);
1124 assert_eq!(
1125 editor.display_text(cx),
1126 editor.buffer.read(cx).read(cx).text()
1127 );
1128 });
1129}
1130
1131#[gpui::test]
1132fn test_fold_at_level(cx: &mut TestAppContext) {
1133 init_test(cx, |_| {});
1134
1135 let editor = cx.add_window(|window, cx| {
1136 let buffer = MultiBuffer::build_simple(
1137 &"
1138 class Foo:
1139 # Hello!
1140
1141 def a():
1142 print(1)
1143
1144 def b():
1145 print(2)
1146
1147
1148 class Bar:
1149 # World!
1150
1151 def a():
1152 print(1)
1153
1154 def b():
1155 print(2)
1156
1157
1158 "
1159 .unindent(),
1160 cx,
1161 );
1162 build_editor(buffer.clone(), window, cx)
1163 });
1164
1165 _ = editor.update(cx, |editor, window, cx| {
1166 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1167 assert_eq!(
1168 editor.display_text(cx),
1169 "
1170 class Foo:
1171 # Hello!
1172
1173 def a():⋯
1174
1175 def b():⋯
1176
1177
1178 class Bar:
1179 # World!
1180
1181 def a():⋯
1182
1183 def b():⋯
1184
1185
1186 "
1187 .unindent(),
1188 );
1189
1190 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1191 assert_eq!(
1192 editor.display_text(cx),
1193 "
1194 class Foo:⋯
1195
1196
1197 class Bar:⋯
1198
1199
1200 "
1201 .unindent(),
1202 );
1203
1204 editor.unfold_all(&UnfoldAll, window, cx);
1205 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1206 assert_eq!(
1207 editor.display_text(cx),
1208 "
1209 class Foo:
1210 # Hello!
1211
1212 def a():
1213 print(1)
1214
1215 def b():
1216 print(2)
1217
1218
1219 class Bar:
1220 # World!
1221
1222 def a():
1223 print(1)
1224
1225 def b():
1226 print(2)
1227
1228
1229 "
1230 .unindent(),
1231 );
1232
1233 assert_eq!(
1234 editor.display_text(cx),
1235 editor.buffer.read(cx).read(cx).text()
1236 );
1237 });
1238}
1239
1240#[gpui::test]
1241fn test_move_cursor(cx: &mut TestAppContext) {
1242 init_test(cx, |_| {});
1243
1244 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1245 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1246
1247 buffer.update(cx, |buffer, cx| {
1248 buffer.edit(
1249 vec![
1250 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1251 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1252 ],
1253 None,
1254 cx,
1255 );
1256 });
1257 _ = editor.update(cx, |editor, window, cx| {
1258 assert_eq!(
1259 editor.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1261 );
1262
1263 editor.move_down(&MoveDown, window, cx);
1264 assert_eq!(
1265 editor.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1267 );
1268
1269 editor.move_right(&MoveRight, window, cx);
1270 assert_eq!(
1271 editor.selections.display_ranges(cx),
1272 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1273 );
1274
1275 editor.move_left(&MoveLeft, window, cx);
1276 assert_eq!(
1277 editor.selections.display_ranges(cx),
1278 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1279 );
1280
1281 editor.move_up(&MoveUp, window, cx);
1282 assert_eq!(
1283 editor.selections.display_ranges(cx),
1284 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1285 );
1286
1287 editor.move_to_end(&MoveToEnd, window, cx);
1288 assert_eq!(
1289 editor.selections.display_ranges(cx),
1290 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1291 );
1292
1293 editor.move_to_beginning(&MoveToBeginning, window, cx);
1294 assert_eq!(
1295 editor.selections.display_ranges(cx),
1296 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1297 );
1298
1299 editor.change_selections(None, window, cx, |s| {
1300 s.select_display_ranges([
1301 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1302 ]);
1303 });
1304 editor.select_to_beginning(&SelectToBeginning, window, cx);
1305 assert_eq!(
1306 editor.selections.display_ranges(cx),
1307 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1308 );
1309
1310 editor.select_to_end(&SelectToEnd, window, cx);
1311 assert_eq!(
1312 editor.selections.display_ranges(cx),
1313 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1314 );
1315 });
1316}
1317
1318// TODO: Re-enable this test
1319#[cfg(target_os = "macos")]
1320#[gpui::test]
1321fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1322 init_test(cx, |_| {});
1323
1324 let editor = cx.add_window(|window, cx| {
1325 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1326 build_editor(buffer.clone(), window, cx)
1327 });
1328
1329 assert_eq!('🟥'.len_utf8(), 4);
1330 assert_eq!('α'.len_utf8(), 2);
1331
1332 _ = editor.update(cx, |editor, window, cx| {
1333 editor.fold_creases(
1334 vec![
1335 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1336 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1337 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1338 ],
1339 true,
1340 window,
1341 cx,
1342 );
1343 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
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 editor.move_right(&MoveRight, window, cx);
1356 assert_eq!(
1357 editor.selections.display_ranges(cx),
1358 &[empty_range(0, "🟥🟧⋯".len())]
1359 );
1360
1361 editor.move_down(&MoveDown, window, cx);
1362 assert_eq!(
1363 editor.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯e".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, "ab".len())]
1375 );
1376 editor.move_left(&MoveLeft, window, cx);
1377 assert_eq!(
1378 editor.selections.display_ranges(cx),
1379 &[empty_range(1, "a".len())]
1380 );
1381
1382 editor.move_down(&MoveDown, 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 editor.move_right(&MoveRight, window, cx);
1398 assert_eq!(
1399 editor.selections.display_ranges(cx),
1400 &[empty_range(2, "αβ⋯ε".len())]
1401 );
1402
1403 editor.move_up(&MoveUp, window, cx);
1404 assert_eq!(
1405 editor.selections.display_ranges(cx),
1406 &[empty_range(1, "ab⋯e".len())]
1407 );
1408 editor.move_down(&MoveDown, window, cx);
1409 assert_eq!(
1410 editor.selections.display_ranges(cx),
1411 &[empty_range(2, "αβ⋯ε".len())]
1412 );
1413 editor.move_up(&MoveUp, window, cx);
1414 assert_eq!(
1415 editor.selections.display_ranges(cx),
1416 &[empty_range(1, "ab⋯e".len())]
1417 );
1418
1419 editor.move_up(&MoveUp, 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 editor.move_left(&MoveLeft, window, cx);
1430 assert_eq!(
1431 editor.selections.display_ranges(cx),
1432 &[empty_range(0, "".len())]
1433 );
1434 });
1435}
1436
1437#[gpui::test]
1438fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1439 init_test(cx, |_| {});
1440
1441 let editor = cx.add_window(|window, cx| {
1442 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1443 build_editor(buffer.clone(), window, cx)
1444 });
1445 _ = editor.update(cx, |editor, window, cx| {
1446 editor.change_selections(None, window, cx, |s| {
1447 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1448 });
1449
1450 // moving above start of document should move selection to start of document,
1451 // but the next move down should still be at the original goal_x
1452 editor.move_up(&MoveUp, window, cx);
1453 assert_eq!(
1454 editor.selections.display_ranges(cx),
1455 &[empty_range(0, "".len())]
1456 );
1457
1458 editor.move_down(&MoveDown, window, cx);
1459 assert_eq!(
1460 editor.selections.display_ranges(cx),
1461 &[empty_range(1, "abcd".len())]
1462 );
1463
1464 editor.move_down(&MoveDown, window, cx);
1465 assert_eq!(
1466 editor.selections.display_ranges(cx),
1467 &[empty_range(2, "αβγ".len())]
1468 );
1469
1470 editor.move_down(&MoveDown, window, cx);
1471 assert_eq!(
1472 editor.selections.display_ranges(cx),
1473 &[empty_range(3, "abcd".len())]
1474 );
1475
1476 editor.move_down(&MoveDown, window, cx);
1477 assert_eq!(
1478 editor.selections.display_ranges(cx),
1479 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1480 );
1481
1482 // moving past end of document should not change goal_x
1483 editor.move_down(&MoveDown, window, cx);
1484 assert_eq!(
1485 editor.selections.display_ranges(cx),
1486 &[empty_range(5, "".len())]
1487 );
1488
1489 editor.move_down(&MoveDown, window, cx);
1490 assert_eq!(
1491 editor.selections.display_ranges(cx),
1492 &[empty_range(5, "".len())]
1493 );
1494
1495 editor.move_up(&MoveUp, window, cx);
1496 assert_eq!(
1497 editor.selections.display_ranges(cx),
1498 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1499 );
1500
1501 editor.move_up(&MoveUp, window, cx);
1502 assert_eq!(
1503 editor.selections.display_ranges(cx),
1504 &[empty_range(3, "abcd".len())]
1505 );
1506
1507 editor.move_up(&MoveUp, window, cx);
1508 assert_eq!(
1509 editor.selections.display_ranges(cx),
1510 &[empty_range(2, "αβγ".len())]
1511 );
1512 });
1513}
1514
1515#[gpui::test]
1516fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1517 init_test(cx, |_| {});
1518 let move_to_beg = MoveToBeginningOfLine {
1519 stop_at_soft_wraps: true,
1520 stop_at_indent: true,
1521 };
1522
1523 let delete_to_beg = DeleteToBeginningOfLine {
1524 stop_at_indent: false,
1525 };
1526
1527 let move_to_end = MoveToEndOfLine {
1528 stop_at_soft_wraps: true,
1529 };
1530
1531 let editor = cx.add_window(|window, cx| {
1532 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1533 build_editor(buffer, window, cx)
1534 });
1535 _ = editor.update(cx, |editor, window, cx| {
1536 editor.change_selections(None, window, cx, |s| {
1537 s.select_display_ranges([
1538 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1539 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1540 ]);
1541 });
1542 });
1543
1544 _ = editor.update(cx, |editor, window, cx| {
1545 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1546 assert_eq!(
1547 editor.selections.display_ranges(cx),
1548 &[
1549 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1550 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1551 ]
1552 );
1553 });
1554
1555 _ = editor.update(cx, |editor, window, cx| {
1556 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1557 assert_eq!(
1558 editor.selections.display_ranges(cx),
1559 &[
1560 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1561 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1562 ]
1563 );
1564 });
1565
1566 _ = editor.update(cx, |editor, window, cx| {
1567 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1568 assert_eq!(
1569 editor.selections.display_ranges(cx),
1570 &[
1571 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1572 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1573 ]
1574 );
1575 });
1576
1577 _ = editor.update(cx, |editor, window, cx| {
1578 editor.move_to_end_of_line(&move_to_end, window, cx);
1579 assert_eq!(
1580 editor.selections.display_ranges(cx),
1581 &[
1582 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1583 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1584 ]
1585 );
1586 });
1587
1588 // Moving to the end of line again is a no-op.
1589 _ = editor.update(cx, |editor, window, cx| {
1590 editor.move_to_end_of_line(&move_to_end, window, cx);
1591 assert_eq!(
1592 editor.selections.display_ranges(cx),
1593 &[
1594 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1595 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1596 ]
1597 );
1598 });
1599
1600 _ = editor.update(cx, |editor, window, cx| {
1601 editor.move_left(&MoveLeft, window, cx);
1602 editor.select_to_beginning_of_line(
1603 &SelectToBeginningOfLine {
1604 stop_at_soft_wraps: true,
1605 stop_at_indent: true,
1606 },
1607 window,
1608 cx,
1609 );
1610 assert_eq!(
1611 editor.selections.display_ranges(cx),
1612 &[
1613 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1614 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1615 ]
1616 );
1617 });
1618
1619 _ = editor.update(cx, |editor, window, cx| {
1620 editor.select_to_beginning_of_line(
1621 &SelectToBeginningOfLine {
1622 stop_at_soft_wraps: true,
1623 stop_at_indent: true,
1624 },
1625 window,
1626 cx,
1627 );
1628 assert_eq!(
1629 editor.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636
1637 _ = editor.update(cx, |editor, window, cx| {
1638 editor.select_to_beginning_of_line(
1639 &SelectToBeginningOfLine {
1640 stop_at_soft_wraps: true,
1641 stop_at_indent: true,
1642 },
1643 window,
1644 cx,
1645 );
1646 assert_eq!(
1647 editor.selections.display_ranges(cx),
1648 &[
1649 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1650 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1651 ]
1652 );
1653 });
1654
1655 _ = editor.update(cx, |editor, window, cx| {
1656 editor.select_to_end_of_line(
1657 &SelectToEndOfLine {
1658 stop_at_soft_wraps: true,
1659 },
1660 window,
1661 cx,
1662 );
1663 assert_eq!(
1664 editor.selections.display_ranges(cx),
1665 &[
1666 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1667 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1668 ]
1669 );
1670 });
1671
1672 _ = editor.update(cx, |editor, window, cx| {
1673 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1674 assert_eq!(editor.display_text(cx), "ab\n de");
1675 assert_eq!(
1676 editor.selections.display_ranges(cx),
1677 &[
1678 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1679 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1680 ]
1681 );
1682 });
1683
1684 _ = editor.update(cx, |editor, window, cx| {
1685 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1686 assert_eq!(editor.display_text(cx), "\n");
1687 assert_eq!(
1688 editor.selections.display_ranges(cx),
1689 &[
1690 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1691 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1692 ]
1693 );
1694 });
1695}
1696
1697#[gpui::test]
1698fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1699 init_test(cx, |_| {});
1700 let move_to_beg = MoveToBeginningOfLine {
1701 stop_at_soft_wraps: false,
1702 stop_at_indent: false,
1703 };
1704
1705 let move_to_end = MoveToEndOfLine {
1706 stop_at_soft_wraps: false,
1707 };
1708
1709 let editor = cx.add_window(|window, cx| {
1710 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1711 build_editor(buffer, window, cx)
1712 });
1713
1714 _ = editor.update(cx, |editor, window, cx| {
1715 editor.set_wrap_width(Some(140.0.into()), cx);
1716
1717 // We expect the following lines after wrapping
1718 // ```
1719 // thequickbrownfox
1720 // jumpedoverthelazydo
1721 // gs
1722 // ```
1723 // The final `gs` was soft-wrapped onto a new line.
1724 assert_eq!(
1725 "thequickbrownfox\njumpedoverthelaz\nydogs",
1726 editor.display_text(cx),
1727 );
1728
1729 // First, let's assert behavior on the first line, that was not soft-wrapped.
1730 // Start the cursor at the `k` on the first line
1731 editor.change_selections(None, window, cx, |s| {
1732 s.select_display_ranges([
1733 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1734 ]);
1735 });
1736
1737 // Moving to the beginning of the line should put us at the beginning of the line.
1738 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1739 assert_eq!(
1740 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1741 editor.selections.display_ranges(cx)
1742 );
1743
1744 // Moving to the end of the line should put us at the end of the line.
1745 editor.move_to_end_of_line(&move_to_end, window, cx);
1746 assert_eq!(
1747 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1748 editor.selections.display_ranges(cx)
1749 );
1750
1751 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1752 // Start the cursor at the last line (`y` that was wrapped to a new line)
1753 editor.change_selections(None, window, cx, |s| {
1754 s.select_display_ranges([
1755 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1756 ]);
1757 });
1758
1759 // Moving to the beginning of the line should put us at the start of the second line of
1760 // display text, i.e., the `j`.
1761 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1762 assert_eq!(
1763 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1764 editor.selections.display_ranges(cx)
1765 );
1766
1767 // Moving to the beginning of the line again should be a no-op.
1768 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1769 assert_eq!(
1770 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1771 editor.selections.display_ranges(cx)
1772 );
1773
1774 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1775 // next display line.
1776 editor.move_to_end_of_line(&move_to_end, window, cx);
1777 assert_eq!(
1778 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1779 editor.selections.display_ranges(cx)
1780 );
1781
1782 // Moving to the end of the line again should be a no-op.
1783 editor.move_to_end_of_line(&move_to_end, window, cx);
1784 assert_eq!(
1785 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1786 editor.selections.display_ranges(cx)
1787 );
1788 });
1789}
1790
1791#[gpui::test]
1792fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1793 init_test(cx, |_| {});
1794
1795 let move_to_beg = MoveToBeginningOfLine {
1796 stop_at_soft_wraps: true,
1797 stop_at_indent: true,
1798 };
1799
1800 let select_to_beg = SelectToBeginningOfLine {
1801 stop_at_soft_wraps: true,
1802 stop_at_indent: true,
1803 };
1804
1805 let delete_to_beg = DeleteToBeginningOfLine {
1806 stop_at_indent: true,
1807 };
1808
1809 let move_to_end = MoveToEndOfLine {
1810 stop_at_soft_wraps: false,
1811 };
1812
1813 let editor = cx.add_window(|window, cx| {
1814 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1815 build_editor(buffer, window, cx)
1816 });
1817
1818 _ = editor.update(cx, |editor, window, cx| {
1819 editor.change_selections(None, window, cx, |s| {
1820 s.select_display_ranges([
1821 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1822 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1823 ]);
1824 });
1825
1826 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1827 // and the second cursor at the first non-whitespace character in the line.
1828 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1829 assert_eq!(
1830 editor.selections.display_ranges(cx),
1831 &[
1832 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1834 ]
1835 );
1836
1837 // Moving to the beginning of the line again should be a no-op for the first cursor,
1838 // and should move the second cursor to the beginning of the line.
1839 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1840 assert_eq!(
1841 editor.selections.display_ranges(cx),
1842 &[
1843 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1844 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1845 ]
1846 );
1847
1848 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1849 // and should move the second cursor back to the first non-whitespace character in the line.
1850 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1851 assert_eq!(
1852 editor.selections.display_ranges(cx),
1853 &[
1854 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1855 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1856 ]
1857 );
1858
1859 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1860 // and to the first non-whitespace character in the line for the second cursor.
1861 editor.move_to_end_of_line(&move_to_end, window, cx);
1862 editor.move_left(&MoveLeft, window, cx);
1863 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1864 assert_eq!(
1865 editor.selections.display_ranges(cx),
1866 &[
1867 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1868 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1869 ]
1870 );
1871
1872 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1873 // and should select to the beginning of the line for the second cursor.
1874 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1875 assert_eq!(
1876 editor.selections.display_ranges(cx),
1877 &[
1878 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1879 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1880 ]
1881 );
1882
1883 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1884 // and should delete to the first non-whitespace character in the line for the second cursor.
1885 editor.move_to_end_of_line(&move_to_end, window, cx);
1886 editor.move_left(&MoveLeft, window, cx);
1887 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1888 assert_eq!(editor.text(cx), "c\n f");
1889 });
1890}
1891
1892#[gpui::test]
1893fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1894 init_test(cx, |_| {});
1895
1896 let editor = cx.add_window(|window, cx| {
1897 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1898 build_editor(buffer, window, cx)
1899 });
1900 _ = editor.update(cx, |editor, window, cx| {
1901 editor.change_selections(None, window, cx, |s| {
1902 s.select_display_ranges([
1903 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1904 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1905 ])
1906 });
1907
1908 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1909 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1910
1911 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1912 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1913
1914 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1915 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1916
1917 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1918 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1919
1920 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1921 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1922
1923 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1924 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1925
1926 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1927 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1928
1929 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1930 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1931
1932 editor.move_right(&MoveRight, window, cx);
1933 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1934 assert_selection_ranges(
1935 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1936 editor,
1937 cx,
1938 );
1939
1940 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1941 assert_selection_ranges(
1942 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1943 editor,
1944 cx,
1945 );
1946
1947 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1948 assert_selection_ranges(
1949 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1950 editor,
1951 cx,
1952 );
1953 });
1954}
1955
1956#[gpui::test]
1957fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1958 init_test(cx, |_| {});
1959
1960 let editor = cx.add_window(|window, cx| {
1961 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1962 build_editor(buffer, window, cx)
1963 });
1964
1965 _ = editor.update(cx, |editor, window, cx| {
1966 editor.set_wrap_width(Some(140.0.into()), cx);
1967 assert_eq!(
1968 editor.display_text(cx),
1969 "use one::{\n two::three::\n four::five\n};"
1970 );
1971
1972 editor.change_selections(None, window, cx, |s| {
1973 s.select_display_ranges([
1974 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1975 ]);
1976 });
1977
1978 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1979 assert_eq!(
1980 editor.selections.display_ranges(cx),
1981 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1982 );
1983
1984 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1985 assert_eq!(
1986 editor.selections.display_ranges(cx),
1987 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1988 );
1989
1990 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1991 assert_eq!(
1992 editor.selections.display_ranges(cx),
1993 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1994 );
1995
1996 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1997 assert_eq!(
1998 editor.selections.display_ranges(cx),
1999 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
2000 );
2001
2002 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2003 assert_eq!(
2004 editor.selections.display_ranges(cx),
2005 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2006 );
2007
2008 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2009 assert_eq!(
2010 editor.selections.display_ranges(cx),
2011 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2012 );
2013 });
2014}
2015
2016#[gpui::test]
2017async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2018 init_test(cx, |_| {});
2019 let mut cx = EditorTestContext::new(cx).await;
2020
2021 let line_height = cx.editor(|editor, window, _| {
2022 editor
2023 .style()
2024 .unwrap()
2025 .text
2026 .line_height_in_pixels(window.rem_size())
2027 });
2028 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2029
2030 cx.set_state(
2031 &r#"ˇone
2032 two
2033
2034 three
2035 fourˇ
2036 five
2037
2038 six"#
2039 .unindent(),
2040 );
2041
2042 cx.update_editor(|editor, window, cx| {
2043 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2044 });
2045 cx.assert_editor_state(
2046 &r#"one
2047 two
2048 ˇ
2049 three
2050 four
2051 five
2052 ˇ
2053 six"#
2054 .unindent(),
2055 );
2056
2057 cx.update_editor(|editor, window, cx| {
2058 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2059 });
2060 cx.assert_editor_state(
2061 &r#"one
2062 two
2063
2064 three
2065 four
2066 five
2067 ˇ
2068 sixˇ"#
2069 .unindent(),
2070 );
2071
2072 cx.update_editor(|editor, window, cx| {
2073 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2074 });
2075 cx.assert_editor_state(
2076 &r#"one
2077 two
2078
2079 three
2080 four
2081 five
2082
2083 sixˇ"#
2084 .unindent(),
2085 );
2086
2087 cx.update_editor(|editor, window, cx| {
2088 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2089 });
2090 cx.assert_editor_state(
2091 &r#"one
2092 two
2093
2094 three
2095 four
2096 five
2097 ˇ
2098 six"#
2099 .unindent(),
2100 );
2101
2102 cx.update_editor(|editor, window, cx| {
2103 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2104 });
2105 cx.assert_editor_state(
2106 &r#"one
2107 two
2108 ˇ
2109 three
2110 four
2111 five
2112
2113 six"#
2114 .unindent(),
2115 );
2116
2117 cx.update_editor(|editor, window, cx| {
2118 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2119 });
2120 cx.assert_editor_state(
2121 &r#"ˇone
2122 two
2123
2124 three
2125 four
2126 five
2127
2128 six"#
2129 .unindent(),
2130 );
2131}
2132
2133#[gpui::test]
2134async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2135 init_test(cx, |_| {});
2136 let mut cx = EditorTestContext::new(cx).await;
2137 let line_height = cx.editor(|editor, window, _| {
2138 editor
2139 .style()
2140 .unwrap()
2141 .text
2142 .line_height_in_pixels(window.rem_size())
2143 });
2144 let window = cx.window;
2145 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2146
2147 cx.set_state(
2148 r#"ˇone
2149 two
2150 three
2151 four
2152 five
2153 six
2154 seven
2155 eight
2156 nine
2157 ten
2158 "#,
2159 );
2160
2161 cx.update_editor(|editor, window, cx| {
2162 assert_eq!(
2163 editor.snapshot(window, cx).scroll_position(),
2164 gpui::Point::new(0., 0.)
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., 3.)
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., 6.)
2175 );
2176 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2177 assert_eq!(
2178 editor.snapshot(window, cx).scroll_position(),
2179 gpui::Point::new(0., 3.)
2180 );
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., 1.)
2186 );
2187 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2188 assert_eq!(
2189 editor.snapshot(window, cx).scroll_position(),
2190 gpui::Point::new(0., 3.)
2191 );
2192 });
2193}
2194
2195#[gpui::test]
2196async fn test_autoscroll(cx: &mut TestAppContext) {
2197 init_test(cx, |_| {});
2198 let mut cx = EditorTestContext::new(cx).await;
2199
2200 let line_height = cx.update_editor(|editor, window, cx| {
2201 editor.set_vertical_scroll_margin(2, cx);
2202 editor
2203 .style()
2204 .unwrap()
2205 .text
2206 .line_height_in_pixels(window.rem_size())
2207 });
2208 let window = cx.window;
2209 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2210
2211 cx.set_state(
2212 r#"ˇone
2213 two
2214 three
2215 four
2216 five
2217 six
2218 seven
2219 eight
2220 nine
2221 ten
2222 "#,
2223 );
2224 cx.update_editor(|editor, window, cx| {
2225 assert_eq!(
2226 editor.snapshot(window, cx).scroll_position(),
2227 gpui::Point::new(0., 0.0)
2228 );
2229 });
2230
2231 // Add a cursor below the visible area. Since both cursors cannot fit
2232 // on screen, the editor autoscrolls to reveal the newest cursor, and
2233 // allows the vertical scroll margin below that cursor.
2234 cx.update_editor(|editor, window, cx| {
2235 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2236 selections.select_ranges([
2237 Point::new(0, 0)..Point::new(0, 0),
2238 Point::new(6, 0)..Point::new(6, 0),
2239 ]);
2240 })
2241 });
2242 cx.update_editor(|editor, window, cx| {
2243 assert_eq!(
2244 editor.snapshot(window, cx).scroll_position(),
2245 gpui::Point::new(0., 3.0)
2246 );
2247 });
2248
2249 // Move down. The editor cursor scrolls down to track the newest cursor.
2250 cx.update_editor(|editor, window, cx| {
2251 editor.move_down(&Default::default(), window, cx);
2252 });
2253 cx.update_editor(|editor, window, cx| {
2254 assert_eq!(
2255 editor.snapshot(window, cx).scroll_position(),
2256 gpui::Point::new(0., 4.0)
2257 );
2258 });
2259
2260 // Add a cursor above the visible area. Since both cursors fit on screen,
2261 // the editor scrolls to show both.
2262 cx.update_editor(|editor, window, cx| {
2263 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2264 selections.select_ranges([
2265 Point::new(1, 0)..Point::new(1, 0),
2266 Point::new(6, 0)..Point::new(6, 0),
2267 ]);
2268 })
2269 });
2270 cx.update_editor(|editor, window, cx| {
2271 assert_eq!(
2272 editor.snapshot(window, cx).scroll_position(),
2273 gpui::Point::new(0., 1.0)
2274 );
2275 });
2276}
2277
2278#[gpui::test]
2279async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2280 init_test(cx, |_| {});
2281 let mut cx = EditorTestContext::new(cx).await;
2282
2283 let line_height = cx.editor(|editor, window, _cx| {
2284 editor
2285 .style()
2286 .unwrap()
2287 .text
2288 .line_height_in_pixels(window.rem_size())
2289 });
2290 let window = cx.window;
2291 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2292 cx.set_state(
2293 &r#"
2294 ˇone
2295 two
2296 threeˇ
2297 four
2298 five
2299 six
2300 seven
2301 eight
2302 nine
2303 ten
2304 "#
2305 .unindent(),
2306 );
2307
2308 cx.update_editor(|editor, window, cx| {
2309 editor.move_page_down(&MovePageDown::default(), window, cx)
2310 });
2311 cx.assert_editor_state(
2312 &r#"
2313 one
2314 two
2315 three
2316 ˇfour
2317 five
2318 sixˇ
2319 seven
2320 eight
2321 nine
2322 ten
2323 "#
2324 .unindent(),
2325 );
2326
2327 cx.update_editor(|editor, window, cx| {
2328 editor.move_page_down(&MovePageDown::default(), window, cx)
2329 });
2330 cx.assert_editor_state(
2331 &r#"
2332 one
2333 two
2334 three
2335 four
2336 five
2337 six
2338 ˇseven
2339 eight
2340 nineˇ
2341 ten
2342 "#
2343 .unindent(),
2344 );
2345
2346 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2347 cx.assert_editor_state(
2348 &r#"
2349 one
2350 two
2351 three
2352 ˇfour
2353 five
2354 sixˇ
2355 seven
2356 eight
2357 nine
2358 ten
2359 "#
2360 .unindent(),
2361 );
2362
2363 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2364 cx.assert_editor_state(
2365 &r#"
2366 ˇone
2367 two
2368 threeˇ
2369 four
2370 five
2371 six
2372 seven
2373 eight
2374 nine
2375 ten
2376 "#
2377 .unindent(),
2378 );
2379
2380 // Test select collapsing
2381 cx.update_editor(|editor, window, cx| {
2382 editor.move_page_down(&MovePageDown::default(), window, cx);
2383 editor.move_page_down(&MovePageDown::default(), window, cx);
2384 editor.move_page_down(&MovePageDown::default(), window, cx);
2385 });
2386 cx.assert_editor_state(
2387 &r#"
2388 one
2389 two
2390 three
2391 four
2392 five
2393 six
2394 seven
2395 eight
2396 nine
2397 ˇten
2398 ˇ"#
2399 .unindent(),
2400 );
2401}
2402
2403#[gpui::test]
2404async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2405 init_test(cx, |_| {});
2406 let mut cx = EditorTestContext::new(cx).await;
2407 cx.set_state("one «two threeˇ» four");
2408 cx.update_editor(|editor, window, cx| {
2409 editor.delete_to_beginning_of_line(
2410 &DeleteToBeginningOfLine {
2411 stop_at_indent: false,
2412 },
2413 window,
2414 cx,
2415 );
2416 assert_eq!(editor.text(cx), " four");
2417 });
2418}
2419
2420#[gpui::test]
2421fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2422 init_test(cx, |_| {});
2423
2424 let editor = cx.add_window(|window, cx| {
2425 let buffer = MultiBuffer::build_simple("one two three four", cx);
2426 build_editor(buffer.clone(), window, cx)
2427 });
2428
2429 _ = editor.update(cx, |editor, window, cx| {
2430 editor.change_selections(None, window, cx, |s| {
2431 s.select_display_ranges([
2432 // an empty selection - the preceding word fragment is deleted
2433 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2434 // characters selected - they are deleted
2435 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2436 ])
2437 });
2438 editor.delete_to_previous_word_start(
2439 &DeleteToPreviousWordStart {
2440 ignore_newlines: false,
2441 },
2442 window,
2443 cx,
2444 );
2445 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2446 });
2447
2448 _ = editor.update(cx, |editor, window, cx| {
2449 editor.change_selections(None, window, cx, |s| {
2450 s.select_display_ranges([
2451 // an empty selection - the following word fragment is deleted
2452 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2453 // characters selected - they are deleted
2454 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2455 ])
2456 });
2457 editor.delete_to_next_word_end(
2458 &DeleteToNextWordEnd {
2459 ignore_newlines: false,
2460 },
2461 window,
2462 cx,
2463 );
2464 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2465 });
2466}
2467
2468#[gpui::test]
2469fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2470 init_test(cx, |_| {});
2471
2472 let editor = cx.add_window(|window, cx| {
2473 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2474 build_editor(buffer.clone(), window, cx)
2475 });
2476 let del_to_prev_word_start = DeleteToPreviousWordStart {
2477 ignore_newlines: false,
2478 };
2479 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2480 ignore_newlines: true,
2481 };
2482
2483 _ = editor.update(cx, |editor, window, cx| {
2484 editor.change_selections(None, window, cx, |s| {
2485 s.select_display_ranges([
2486 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2487 ])
2488 });
2489 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2490 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2491 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2492 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2493 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2494 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2495 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2496 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2497 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2498 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2499 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2500 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2501 });
2502}
2503
2504#[gpui::test]
2505fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2506 init_test(cx, |_| {});
2507
2508 let editor = cx.add_window(|window, cx| {
2509 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2510 build_editor(buffer.clone(), window, cx)
2511 });
2512 let del_to_next_word_end = DeleteToNextWordEnd {
2513 ignore_newlines: false,
2514 };
2515 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2516 ignore_newlines: true,
2517 };
2518
2519 _ = editor.update(cx, |editor, window, cx| {
2520 editor.change_selections(None, window, cx, |s| {
2521 s.select_display_ranges([
2522 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2523 ])
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 "one\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 "\n two\nthree\n four"
2534 );
2535 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2536 assert_eq!(
2537 editor.buffer.read(cx).read(cx).text(),
2538 "two\nthree\n four"
2539 );
2540 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2541 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2542 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2543 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2544 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2545 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2546 });
2547}
2548
2549#[gpui::test]
2550fn test_newline(cx: &mut TestAppContext) {
2551 init_test(cx, |_| {});
2552
2553 let editor = cx.add_window(|window, cx| {
2554 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2555 build_editor(buffer.clone(), window, cx)
2556 });
2557
2558 _ = editor.update(cx, |editor, window, cx| {
2559 editor.change_selections(None, window, cx, |s| {
2560 s.select_display_ranges([
2561 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2562 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2563 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2564 ])
2565 });
2566
2567 editor.newline(&Newline, window, cx);
2568 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2569 });
2570}
2571
2572#[gpui::test]
2573fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2574 init_test(cx, |_| {});
2575
2576 let editor = cx.add_window(|window, cx| {
2577 let buffer = MultiBuffer::build_simple(
2578 "
2579 a
2580 b(
2581 X
2582 )
2583 c(
2584 X
2585 )
2586 "
2587 .unindent()
2588 .as_str(),
2589 cx,
2590 );
2591 let mut editor = build_editor(buffer.clone(), window, cx);
2592 editor.change_selections(None, window, cx, |s| {
2593 s.select_ranges([
2594 Point::new(2, 4)..Point::new(2, 5),
2595 Point::new(5, 4)..Point::new(5, 5),
2596 ])
2597 });
2598 editor
2599 });
2600
2601 _ = editor.update(cx, |editor, window, cx| {
2602 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2603 editor.buffer.update(cx, |buffer, cx| {
2604 buffer.edit(
2605 [
2606 (Point::new(1, 2)..Point::new(3, 0), ""),
2607 (Point::new(4, 2)..Point::new(6, 0), ""),
2608 ],
2609 None,
2610 cx,
2611 );
2612 assert_eq!(
2613 buffer.read(cx).text(),
2614 "
2615 a
2616 b()
2617 c()
2618 "
2619 .unindent()
2620 );
2621 });
2622 assert_eq!(
2623 editor.selections.ranges(cx),
2624 &[
2625 Point::new(1, 2)..Point::new(1, 2),
2626 Point::new(2, 2)..Point::new(2, 2),
2627 ],
2628 );
2629
2630 editor.newline(&Newline, window, cx);
2631 assert_eq!(
2632 editor.text(cx),
2633 "
2634 a
2635 b(
2636 )
2637 c(
2638 )
2639 "
2640 .unindent()
2641 );
2642
2643 // The selections are moved after the inserted newlines
2644 assert_eq!(
2645 editor.selections.ranges(cx),
2646 &[
2647 Point::new(2, 0)..Point::new(2, 0),
2648 Point::new(4, 0)..Point::new(4, 0),
2649 ],
2650 );
2651 });
2652}
2653
2654#[gpui::test]
2655async fn test_newline_above(cx: &mut TestAppContext) {
2656 init_test(cx, |settings| {
2657 settings.defaults.tab_size = NonZeroU32::new(4)
2658 });
2659
2660 let language = Arc::new(
2661 Language::new(
2662 LanguageConfig::default(),
2663 Some(tree_sitter_rust::LANGUAGE.into()),
2664 )
2665 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2666 .unwrap(),
2667 );
2668
2669 let mut cx = EditorTestContext::new(cx).await;
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671 cx.set_state(indoc! {"
2672 const a: ˇA = (
2673 (ˇ
2674 «const_functionˇ»(ˇ),
2675 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2676 )ˇ
2677 ˇ);ˇ
2678 "});
2679
2680 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2681 cx.assert_editor_state(indoc! {"
2682 ˇ
2683 const a: A = (
2684 ˇ
2685 (
2686 ˇ
2687 ˇ
2688 const_function(),
2689 ˇ
2690 ˇ
2691 ˇ
2692 ˇ
2693 something_else,
2694 ˇ
2695 )
2696 ˇ
2697 ˇ
2698 );
2699 "});
2700}
2701
2702#[gpui::test]
2703async fn test_newline_below(cx: &mut TestAppContext) {
2704 init_test(cx, |settings| {
2705 settings.defaults.tab_size = NonZeroU32::new(4)
2706 });
2707
2708 let language = Arc::new(
2709 Language::new(
2710 LanguageConfig::default(),
2711 Some(tree_sitter_rust::LANGUAGE.into()),
2712 )
2713 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2714 .unwrap(),
2715 );
2716
2717 let mut cx = EditorTestContext::new(cx).await;
2718 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2719 cx.set_state(indoc! {"
2720 const a: ˇA = (
2721 (ˇ
2722 «const_functionˇ»(ˇ),
2723 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2724 )ˇ
2725 ˇ);ˇ
2726 "});
2727
2728 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2729 cx.assert_editor_state(indoc! {"
2730 const a: A = (
2731 ˇ
2732 (
2733 ˇ
2734 const_function(),
2735 ˇ
2736 ˇ
2737 something_else,
2738 ˇ
2739 ˇ
2740 ˇ
2741 ˇ
2742 )
2743 ˇ
2744 );
2745 ˇ
2746 ˇ
2747 "});
2748}
2749
2750#[gpui::test]
2751async fn test_newline_comments(cx: &mut TestAppContext) {
2752 init_test(cx, |settings| {
2753 settings.defaults.tab_size = NonZeroU32::new(4)
2754 });
2755
2756 let language = Arc::new(Language::new(
2757 LanguageConfig {
2758 line_comments: vec!["//".into()],
2759 ..LanguageConfig::default()
2760 },
2761 None,
2762 ));
2763 {
2764 let mut cx = EditorTestContext::new(cx).await;
2765 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2766 cx.set_state(indoc! {"
2767 // Fooˇ
2768 "});
2769
2770 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2771 cx.assert_editor_state(indoc! {"
2772 // Foo
2773 //ˇ
2774 "});
2775 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2776 cx.set_state(indoc! {"
2777 ˇ// Foo
2778 "});
2779 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2780 cx.assert_editor_state(indoc! {"
2781
2782 ˇ// Foo
2783 "});
2784 }
2785 // Ensure that comment continuations can be disabled.
2786 update_test_language_settings(cx, |settings| {
2787 settings.defaults.extend_comment_on_newline = Some(false);
2788 });
2789 let mut cx = EditorTestContext::new(cx).await;
2790 cx.set_state(indoc! {"
2791 // Fooˇ
2792 "});
2793 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2794 cx.assert_editor_state(indoc! {"
2795 // Foo
2796 ˇ
2797 "});
2798}
2799
2800#[gpui::test]
2801fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2802 init_test(cx, |_| {});
2803
2804 let editor = cx.add_window(|window, cx| {
2805 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2806 let mut editor = build_editor(buffer.clone(), window, cx);
2807 editor.change_selections(None, window, cx, |s| {
2808 s.select_ranges([3..4, 11..12, 19..20])
2809 });
2810 editor
2811 });
2812
2813 _ = editor.update(cx, |editor, window, cx| {
2814 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2815 editor.buffer.update(cx, |buffer, cx| {
2816 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2817 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2818 });
2819 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2820
2821 editor.insert("Z", window, cx);
2822 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2823
2824 // The selections are moved after the inserted characters
2825 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2826 });
2827}
2828
2829#[gpui::test]
2830async fn test_tab(cx: &mut TestAppContext) {
2831 init_test(cx, |settings| {
2832 settings.defaults.tab_size = NonZeroU32::new(3)
2833 });
2834
2835 let mut cx = EditorTestContext::new(cx).await;
2836 cx.set_state(indoc! {"
2837 ˇabˇc
2838 ˇ🏀ˇ🏀ˇefg
2839 dˇ
2840 "});
2841 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2842 cx.assert_editor_state(indoc! {"
2843 ˇab ˇc
2844 ˇ🏀 ˇ🏀 ˇefg
2845 d ˇ
2846 "});
2847
2848 cx.set_state(indoc! {"
2849 a
2850 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2851 "});
2852 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2853 cx.assert_editor_state(indoc! {"
2854 a
2855 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2856 "});
2857}
2858
2859#[gpui::test]
2860async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2861 init_test(cx, |_| {});
2862
2863 let mut cx = EditorTestContext::new(cx).await;
2864 let language = Arc::new(
2865 Language::new(
2866 LanguageConfig::default(),
2867 Some(tree_sitter_rust::LANGUAGE.into()),
2868 )
2869 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2870 .unwrap(),
2871 );
2872 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2873
2874 // cursors that are already at the suggested indent level insert
2875 // a soft tab. cursors that are to the left of the suggested indent
2876 // auto-indent their line.
2877 cx.set_state(indoc! {"
2878 ˇ
2879 const a: B = (
2880 c(
2881 d(
2882 ˇ
2883 )
2884 ˇ
2885 ˇ )
2886 );
2887 "});
2888 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2889 cx.assert_editor_state(indoc! {"
2890 ˇ
2891 const a: B = (
2892 c(
2893 d(
2894 ˇ
2895 )
2896 ˇ
2897 ˇ)
2898 );
2899 "});
2900
2901 // handle auto-indent when there are multiple cursors on the same line
2902 cx.set_state(indoc! {"
2903 const a: B = (
2904 c(
2905 ˇ ˇ
2906 ˇ )
2907 );
2908 "});
2909 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2910 cx.assert_editor_state(indoc! {"
2911 const a: B = (
2912 c(
2913 ˇ
2914 ˇ)
2915 );
2916 "});
2917}
2918
2919#[gpui::test]
2920async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2921 init_test(cx, |settings| {
2922 settings.defaults.tab_size = NonZeroU32::new(4)
2923 });
2924
2925 let language = Arc::new(
2926 Language::new(
2927 LanguageConfig::default(),
2928 Some(tree_sitter_rust::LANGUAGE.into()),
2929 )
2930 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2931 .unwrap(),
2932 );
2933
2934 let mut cx = EditorTestContext::new(cx).await;
2935 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2936 cx.set_state(indoc! {"
2937 fn a() {
2938 if b {
2939 \t ˇc
2940 }
2941 }
2942 "});
2943
2944 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2945 cx.assert_editor_state(indoc! {"
2946 fn a() {
2947 if b {
2948 ˇc
2949 }
2950 }
2951 "});
2952}
2953
2954#[gpui::test]
2955async fn test_indent_outdent(cx: &mut TestAppContext) {
2956 init_test(cx, |settings| {
2957 settings.defaults.tab_size = NonZeroU32::new(4);
2958 });
2959
2960 let mut cx = EditorTestContext::new(cx).await;
2961
2962 cx.set_state(indoc! {"
2963 «oneˇ» «twoˇ»
2964 three
2965 four
2966 "});
2967 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2968 cx.assert_editor_state(indoc! {"
2969 «oneˇ» «twoˇ»
2970 three
2971 four
2972 "});
2973
2974 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2975 cx.assert_editor_state(indoc! {"
2976 «oneˇ» «twoˇ»
2977 three
2978 four
2979 "});
2980
2981 // select across line ending
2982 cx.set_state(indoc! {"
2983 one two
2984 t«hree
2985 ˇ» four
2986 "});
2987 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2988 cx.assert_editor_state(indoc! {"
2989 one two
2990 t«hree
2991 ˇ» four
2992 "});
2993
2994 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2995 cx.assert_editor_state(indoc! {"
2996 one two
2997 t«hree
2998 ˇ» four
2999 "});
3000
3001 // Ensure that indenting/outdenting works when the cursor is at column 0.
3002 cx.set_state(indoc! {"
3003 one two
3004 ˇthree
3005 four
3006 "});
3007 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3008 cx.assert_editor_state(indoc! {"
3009 one two
3010 ˇthree
3011 four
3012 "});
3013
3014 cx.set_state(indoc! {"
3015 one two
3016 ˇ three
3017 four
3018 "});
3019 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3020 cx.assert_editor_state(indoc! {"
3021 one two
3022 ˇthree
3023 four
3024 "});
3025}
3026
3027#[gpui::test]
3028async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3029 init_test(cx, |settings| {
3030 settings.defaults.hard_tabs = Some(true);
3031 });
3032
3033 let mut cx = EditorTestContext::new(cx).await;
3034
3035 // select two ranges on one line
3036 cx.set_state(indoc! {"
3037 «oneˇ» «twoˇ»
3038 three
3039 four
3040 "});
3041 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3042 cx.assert_editor_state(indoc! {"
3043 \t«oneˇ» «twoˇ»
3044 three
3045 four
3046 "});
3047 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3048 cx.assert_editor_state(indoc! {"
3049 \t\t«oneˇ» «twoˇ»
3050 three
3051 four
3052 "});
3053 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3054 cx.assert_editor_state(indoc! {"
3055 \t«oneˇ» «twoˇ»
3056 three
3057 four
3058 "});
3059 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3060 cx.assert_editor_state(indoc! {"
3061 «oneˇ» «twoˇ»
3062 three
3063 four
3064 "});
3065
3066 // select across a line ending
3067 cx.set_state(indoc! {"
3068 one two
3069 t«hree
3070 ˇ»four
3071 "});
3072 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3073 cx.assert_editor_state(indoc! {"
3074 one two
3075 \tt«hree
3076 ˇ»four
3077 "});
3078 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3079 cx.assert_editor_state(indoc! {"
3080 one two
3081 \t\tt«hree
3082 ˇ»four
3083 "});
3084 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3085 cx.assert_editor_state(indoc! {"
3086 one two
3087 \tt«hree
3088 ˇ»four
3089 "});
3090 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3091 cx.assert_editor_state(indoc! {"
3092 one two
3093 t«hree
3094 ˇ»four
3095 "});
3096
3097 // Ensure that indenting/outdenting works when the cursor is at column 0.
3098 cx.set_state(indoc! {"
3099 one two
3100 ˇthree
3101 four
3102 "});
3103 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3104 cx.assert_editor_state(indoc! {"
3105 one two
3106 ˇthree
3107 four
3108 "});
3109 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3110 cx.assert_editor_state(indoc! {"
3111 one two
3112 \tˇthree
3113 four
3114 "});
3115 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3116 cx.assert_editor_state(indoc! {"
3117 one two
3118 ˇthree
3119 four
3120 "});
3121}
3122
3123#[gpui::test]
3124fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3125 init_test(cx, |settings| {
3126 settings.languages.extend([
3127 (
3128 "TOML".into(),
3129 LanguageSettingsContent {
3130 tab_size: NonZeroU32::new(2),
3131 ..Default::default()
3132 },
3133 ),
3134 (
3135 "Rust".into(),
3136 LanguageSettingsContent {
3137 tab_size: NonZeroU32::new(4),
3138 ..Default::default()
3139 },
3140 ),
3141 ]);
3142 });
3143
3144 let toml_language = Arc::new(Language::new(
3145 LanguageConfig {
3146 name: "TOML".into(),
3147 ..Default::default()
3148 },
3149 None,
3150 ));
3151 let rust_language = Arc::new(Language::new(
3152 LanguageConfig {
3153 name: "Rust".into(),
3154 ..Default::default()
3155 },
3156 None,
3157 ));
3158
3159 let toml_buffer =
3160 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3161 let rust_buffer =
3162 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3163 let multibuffer = cx.new(|cx| {
3164 let mut multibuffer = MultiBuffer::new(ReadWrite);
3165 multibuffer.push_excerpts(
3166 toml_buffer.clone(),
3167 [ExcerptRange {
3168 context: Point::new(0, 0)..Point::new(2, 0),
3169 primary: None,
3170 }],
3171 cx,
3172 );
3173 multibuffer.push_excerpts(
3174 rust_buffer.clone(),
3175 [ExcerptRange {
3176 context: Point::new(0, 0)..Point::new(1, 0),
3177 primary: None,
3178 }],
3179 cx,
3180 );
3181 multibuffer
3182 });
3183
3184 cx.add_window(|window, cx| {
3185 let mut editor = build_editor(multibuffer, window, cx);
3186
3187 assert_eq!(
3188 editor.text(cx),
3189 indoc! {"
3190 a = 1
3191 b = 2
3192
3193 const c: usize = 3;
3194 "}
3195 );
3196
3197 select_ranges(
3198 &mut editor,
3199 indoc! {"
3200 «aˇ» = 1
3201 b = 2
3202
3203 «const c:ˇ» usize = 3;
3204 "},
3205 window,
3206 cx,
3207 );
3208
3209 editor.tab(&Tab, window, cx);
3210 assert_text_with_selections(
3211 &mut editor,
3212 indoc! {"
3213 «aˇ» = 1
3214 b = 2
3215
3216 «const c:ˇ» usize = 3;
3217 "},
3218 cx,
3219 );
3220 editor.backtab(&Backtab, window, cx);
3221 assert_text_with_selections(
3222 &mut editor,
3223 indoc! {"
3224 «aˇ» = 1
3225 b = 2
3226
3227 «const c:ˇ» usize = 3;
3228 "},
3229 cx,
3230 );
3231
3232 editor
3233 });
3234}
3235
3236#[gpui::test]
3237async fn test_backspace(cx: &mut TestAppContext) {
3238 init_test(cx, |_| {});
3239
3240 let mut cx = EditorTestContext::new(cx).await;
3241
3242 // Basic backspace
3243 cx.set_state(indoc! {"
3244 onˇe two three
3245 fou«rˇ» five six
3246 seven «ˇeight nine
3247 »ten
3248 "});
3249 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3250 cx.assert_editor_state(indoc! {"
3251 oˇe two three
3252 fouˇ five six
3253 seven ˇten
3254 "});
3255
3256 // Test backspace inside and around indents
3257 cx.set_state(indoc! {"
3258 zero
3259 ˇone
3260 ˇtwo
3261 ˇ ˇ ˇ three
3262 ˇ ˇ four
3263 "});
3264 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3265 cx.assert_editor_state(indoc! {"
3266 zero
3267 ˇone
3268 ˇtwo
3269 ˇ threeˇ four
3270 "});
3271
3272 // Test backspace with line_mode set to true
3273 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3274 cx.set_state(indoc! {"
3275 The ˇquick ˇbrown
3276 fox jumps over
3277 the lazy dog
3278 ˇThe qu«ick bˇ»rown"});
3279 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3280 cx.assert_editor_state(indoc! {"
3281 ˇfox jumps over
3282 the lazy dogˇ"});
3283}
3284
3285#[gpui::test]
3286async fn test_delete(cx: &mut TestAppContext) {
3287 init_test(cx, |_| {});
3288
3289 let mut cx = EditorTestContext::new(cx).await;
3290 cx.set_state(indoc! {"
3291 onˇe two three
3292 fou«rˇ» five six
3293 seven «ˇeight nine
3294 »ten
3295 "});
3296 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3297 cx.assert_editor_state(indoc! {"
3298 onˇ two three
3299 fouˇ five six
3300 seven ˇten
3301 "});
3302
3303 // Test backspace with line_mode set to true
3304 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3305 cx.set_state(indoc! {"
3306 The ˇquick ˇbrown
3307 fox «ˇjum»ps over
3308 the lazy dog
3309 ˇThe qu«ick bˇ»rown"});
3310 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3311 cx.assert_editor_state("ˇthe lazy dogˇ");
3312}
3313
3314#[gpui::test]
3315fn test_delete_line(cx: &mut TestAppContext) {
3316 init_test(cx, |_| {});
3317
3318 let editor = cx.add_window(|window, cx| {
3319 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3320 build_editor(buffer, window, cx)
3321 });
3322 _ = editor.update(cx, |editor, window, cx| {
3323 editor.change_selections(None, window, cx, |s| {
3324 s.select_display_ranges([
3325 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3326 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3327 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3328 ])
3329 });
3330 editor.delete_line(&DeleteLine, window, cx);
3331 assert_eq!(editor.display_text(cx), "ghi");
3332 assert_eq!(
3333 editor.selections.display_ranges(cx),
3334 vec![
3335 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3336 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3337 ]
3338 );
3339 });
3340
3341 let editor = cx.add_window(|window, cx| {
3342 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3343 build_editor(buffer, window, cx)
3344 });
3345 _ = editor.update(cx, |editor, window, cx| {
3346 editor.change_selections(None, window, cx, |s| {
3347 s.select_display_ranges([
3348 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3349 ])
3350 });
3351 editor.delete_line(&DeleteLine, window, cx);
3352 assert_eq!(editor.display_text(cx), "ghi\n");
3353 assert_eq!(
3354 editor.selections.display_ranges(cx),
3355 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3356 );
3357 });
3358}
3359
3360#[gpui::test]
3361fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3362 init_test(cx, |_| {});
3363
3364 cx.add_window(|window, cx| {
3365 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3366 let mut editor = build_editor(buffer.clone(), window, cx);
3367 let buffer = buffer.read(cx).as_singleton().unwrap();
3368
3369 assert_eq!(
3370 editor.selections.ranges::<Point>(cx),
3371 &[Point::new(0, 0)..Point::new(0, 0)]
3372 );
3373
3374 // When on single line, replace newline at end by space
3375 editor.join_lines(&JoinLines, window, cx);
3376 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3377 assert_eq!(
3378 editor.selections.ranges::<Point>(cx),
3379 &[Point::new(0, 3)..Point::new(0, 3)]
3380 );
3381
3382 // When multiple lines are selected, remove newlines that are spanned by the selection
3383 editor.change_selections(None, window, cx, |s| {
3384 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3385 });
3386 editor.join_lines(&JoinLines, window, cx);
3387 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3388 assert_eq!(
3389 editor.selections.ranges::<Point>(cx),
3390 &[Point::new(0, 11)..Point::new(0, 11)]
3391 );
3392
3393 // Undo should be transactional
3394 editor.undo(&Undo, window, cx);
3395 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3396 assert_eq!(
3397 editor.selections.ranges::<Point>(cx),
3398 &[Point::new(0, 5)..Point::new(2, 2)]
3399 );
3400
3401 // When joining an empty line don't insert a space
3402 editor.change_selections(None, window, cx, |s| {
3403 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3404 });
3405 editor.join_lines(&JoinLines, window, cx);
3406 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3407 assert_eq!(
3408 editor.selections.ranges::<Point>(cx),
3409 [Point::new(2, 3)..Point::new(2, 3)]
3410 );
3411
3412 // We can remove trailing newlines
3413 editor.join_lines(&JoinLines, window, cx);
3414 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3415 assert_eq!(
3416 editor.selections.ranges::<Point>(cx),
3417 [Point::new(2, 3)..Point::new(2, 3)]
3418 );
3419
3420 // We don't blow up on the last line
3421 editor.join_lines(&JoinLines, window, cx);
3422 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3423 assert_eq!(
3424 editor.selections.ranges::<Point>(cx),
3425 [Point::new(2, 3)..Point::new(2, 3)]
3426 );
3427
3428 // reset to test indentation
3429 editor.buffer.update(cx, |buffer, cx| {
3430 buffer.edit(
3431 [
3432 (Point::new(1, 0)..Point::new(1, 2), " "),
3433 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3434 ],
3435 None,
3436 cx,
3437 )
3438 });
3439
3440 // We remove any leading spaces
3441 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3442 editor.change_selections(None, window, cx, |s| {
3443 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3444 });
3445 editor.join_lines(&JoinLines, window, cx);
3446 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3447
3448 // We don't insert a space for a line containing only spaces
3449 editor.join_lines(&JoinLines, window, cx);
3450 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3451
3452 // We ignore any leading tabs
3453 editor.join_lines(&JoinLines, window, cx);
3454 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3455
3456 editor
3457 });
3458}
3459
3460#[gpui::test]
3461fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3462 init_test(cx, |_| {});
3463
3464 cx.add_window(|window, cx| {
3465 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3466 let mut editor = build_editor(buffer.clone(), window, cx);
3467 let buffer = buffer.read(cx).as_singleton().unwrap();
3468
3469 editor.change_selections(None, window, cx, |s| {
3470 s.select_ranges([
3471 Point::new(0, 2)..Point::new(1, 1),
3472 Point::new(1, 2)..Point::new(1, 2),
3473 Point::new(3, 1)..Point::new(3, 2),
3474 ])
3475 });
3476
3477 editor.join_lines(&JoinLines, window, cx);
3478 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3479
3480 assert_eq!(
3481 editor.selections.ranges::<Point>(cx),
3482 [
3483 Point::new(0, 7)..Point::new(0, 7),
3484 Point::new(1, 3)..Point::new(1, 3)
3485 ]
3486 );
3487 editor
3488 });
3489}
3490
3491#[gpui::test]
3492async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3493 init_test(cx, |_| {});
3494
3495 let mut cx = EditorTestContext::new(cx).await;
3496
3497 let diff_base = r#"
3498 Line 0
3499 Line 1
3500 Line 2
3501 Line 3
3502 "#
3503 .unindent();
3504
3505 cx.set_state(
3506 &r#"
3507 ˇLine 0
3508 Line 1
3509 Line 2
3510 Line 3
3511 "#
3512 .unindent(),
3513 );
3514
3515 cx.set_head_text(&diff_base);
3516 executor.run_until_parked();
3517
3518 // Join lines
3519 cx.update_editor(|editor, window, cx| {
3520 editor.join_lines(&JoinLines, window, cx);
3521 });
3522 executor.run_until_parked();
3523
3524 cx.assert_editor_state(
3525 &r#"
3526 Line 0ˇ Line 1
3527 Line 2
3528 Line 3
3529 "#
3530 .unindent(),
3531 );
3532 // Join again
3533 cx.update_editor(|editor, window, cx| {
3534 editor.join_lines(&JoinLines, window, cx);
3535 });
3536 executor.run_until_parked();
3537
3538 cx.assert_editor_state(
3539 &r#"
3540 Line 0 Line 1ˇ Line 2
3541 Line 3
3542 "#
3543 .unindent(),
3544 );
3545}
3546
3547#[gpui::test]
3548async fn test_custom_newlines_cause_no_false_positive_diffs(
3549 executor: BackgroundExecutor,
3550 cx: &mut TestAppContext,
3551) {
3552 init_test(cx, |_| {});
3553 let mut cx = EditorTestContext::new(cx).await;
3554 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3555 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3556 executor.run_until_parked();
3557
3558 cx.update_editor(|editor, window, cx| {
3559 let snapshot = editor.snapshot(window, cx);
3560 assert_eq!(
3561 snapshot
3562 .buffer_snapshot
3563 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3564 .collect::<Vec<_>>(),
3565 Vec::new(),
3566 "Should not have any diffs for files with custom newlines"
3567 );
3568 });
3569}
3570
3571#[gpui::test]
3572async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3573 init_test(cx, |_| {});
3574
3575 let mut cx = EditorTestContext::new(cx).await;
3576
3577 // Test sort_lines_case_insensitive()
3578 cx.set_state(indoc! {"
3579 «z
3580 y
3581 x
3582 Z
3583 Y
3584 Xˇ»
3585 "});
3586 cx.update_editor(|e, window, cx| {
3587 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3588 });
3589 cx.assert_editor_state(indoc! {"
3590 «x
3591 X
3592 y
3593 Y
3594 z
3595 Zˇ»
3596 "});
3597
3598 // Test reverse_lines()
3599 cx.set_state(indoc! {"
3600 «5
3601 4
3602 3
3603 2
3604 1ˇ»
3605 "});
3606 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3607 cx.assert_editor_state(indoc! {"
3608 «1
3609 2
3610 3
3611 4
3612 5ˇ»
3613 "});
3614
3615 // Skip testing shuffle_line()
3616
3617 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3618 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3619
3620 // Don't manipulate when cursor is on single line, but expand the selection
3621 cx.set_state(indoc! {"
3622 ddˇdd
3623 ccc
3624 bb
3625 a
3626 "});
3627 cx.update_editor(|e, window, cx| {
3628 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3629 });
3630 cx.assert_editor_state(indoc! {"
3631 «ddddˇ»
3632 ccc
3633 bb
3634 a
3635 "});
3636
3637 // Basic manipulate case
3638 // Start selection moves to column 0
3639 // End of selection shrinks to fit shorter line
3640 cx.set_state(indoc! {"
3641 dd«d
3642 ccc
3643 bb
3644 aaaaaˇ»
3645 "});
3646 cx.update_editor(|e, window, cx| {
3647 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3648 });
3649 cx.assert_editor_state(indoc! {"
3650 «aaaaa
3651 bb
3652 ccc
3653 dddˇ»
3654 "});
3655
3656 // Manipulate case with newlines
3657 cx.set_state(indoc! {"
3658 dd«d
3659 ccc
3660
3661 bb
3662 aaaaa
3663
3664 ˇ»
3665 "});
3666 cx.update_editor(|e, window, cx| {
3667 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3668 });
3669 cx.assert_editor_state(indoc! {"
3670 «
3671
3672 aaaaa
3673 bb
3674 ccc
3675 dddˇ»
3676
3677 "});
3678
3679 // Adding new line
3680 cx.set_state(indoc! {"
3681 aa«a
3682 bbˇ»b
3683 "});
3684 cx.update_editor(|e, window, cx| {
3685 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3686 });
3687 cx.assert_editor_state(indoc! {"
3688 «aaa
3689 bbb
3690 added_lineˇ»
3691 "});
3692
3693 // Removing line
3694 cx.set_state(indoc! {"
3695 aa«a
3696 bbbˇ»
3697 "});
3698 cx.update_editor(|e, window, cx| {
3699 e.manipulate_lines(window, cx, |lines| {
3700 lines.pop();
3701 })
3702 });
3703 cx.assert_editor_state(indoc! {"
3704 «aaaˇ»
3705 "});
3706
3707 // Removing all lines
3708 cx.set_state(indoc! {"
3709 aa«a
3710 bbbˇ»
3711 "});
3712 cx.update_editor(|e, window, cx| {
3713 e.manipulate_lines(window, cx, |lines| {
3714 lines.drain(..);
3715 })
3716 });
3717 cx.assert_editor_state(indoc! {"
3718 ˇ
3719 "});
3720}
3721
3722#[gpui::test]
3723async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3724 init_test(cx, |_| {});
3725
3726 let mut cx = EditorTestContext::new(cx).await;
3727
3728 // Consider continuous selection as single selection
3729 cx.set_state(indoc! {"
3730 Aaa«aa
3731 cˇ»c«c
3732 bb
3733 aaaˇ»aa
3734 "});
3735 cx.update_editor(|e, window, cx| {
3736 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3737 });
3738 cx.assert_editor_state(indoc! {"
3739 «Aaaaa
3740 ccc
3741 bb
3742 aaaaaˇ»
3743 "});
3744
3745 cx.set_state(indoc! {"
3746 Aaa«aa
3747 cˇ»c«c
3748 bb
3749 aaaˇ»aa
3750 "});
3751 cx.update_editor(|e, window, cx| {
3752 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3753 });
3754 cx.assert_editor_state(indoc! {"
3755 «Aaaaa
3756 ccc
3757 bbˇ»
3758 "});
3759
3760 // Consider non continuous selection as distinct dedup operations
3761 cx.set_state(indoc! {"
3762 «aaaaa
3763 bb
3764 aaaaa
3765 aaaaaˇ»
3766
3767 aaa«aaˇ»
3768 "});
3769 cx.update_editor(|e, window, cx| {
3770 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3771 });
3772 cx.assert_editor_state(indoc! {"
3773 «aaaaa
3774 bbˇ»
3775
3776 «aaaaaˇ»
3777 "});
3778}
3779
3780#[gpui::test]
3781async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3782 init_test(cx, |_| {});
3783
3784 let mut cx = EditorTestContext::new(cx).await;
3785
3786 cx.set_state(indoc! {"
3787 «Aaa
3788 aAa
3789 Aaaˇ»
3790 "});
3791 cx.update_editor(|e, window, cx| {
3792 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3793 });
3794 cx.assert_editor_state(indoc! {"
3795 «Aaa
3796 aAaˇ»
3797 "});
3798
3799 cx.set_state(indoc! {"
3800 «Aaa
3801 aAa
3802 aaAˇ»
3803 "});
3804 cx.update_editor(|e, window, cx| {
3805 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3806 });
3807 cx.assert_editor_state(indoc! {"
3808 «Aaaˇ»
3809 "});
3810}
3811
3812#[gpui::test]
3813async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3814 init_test(cx, |_| {});
3815
3816 let mut cx = EditorTestContext::new(cx).await;
3817
3818 // Manipulate with multiple selections on a single line
3819 cx.set_state(indoc! {"
3820 dd«dd
3821 cˇ»c«c
3822 bb
3823 aaaˇ»aa
3824 "});
3825 cx.update_editor(|e, window, cx| {
3826 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3827 });
3828 cx.assert_editor_state(indoc! {"
3829 «aaaaa
3830 bb
3831 ccc
3832 ddddˇ»
3833 "});
3834
3835 // Manipulate with multiple disjoin selections
3836 cx.set_state(indoc! {"
3837 5«
3838 4
3839 3
3840 2
3841 1ˇ»
3842
3843 dd«dd
3844 ccc
3845 bb
3846 aaaˇ»aa
3847 "});
3848 cx.update_editor(|e, window, cx| {
3849 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3850 });
3851 cx.assert_editor_state(indoc! {"
3852 «1
3853 2
3854 3
3855 4
3856 5ˇ»
3857
3858 «aaaaa
3859 bb
3860 ccc
3861 ddddˇ»
3862 "});
3863
3864 // Adding lines on each selection
3865 cx.set_state(indoc! {"
3866 2«
3867 1ˇ»
3868
3869 bb«bb
3870 aaaˇ»aa
3871 "});
3872 cx.update_editor(|e, window, cx| {
3873 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3874 });
3875 cx.assert_editor_state(indoc! {"
3876 «2
3877 1
3878 added lineˇ»
3879
3880 «bbbb
3881 aaaaa
3882 added lineˇ»
3883 "});
3884
3885 // Removing lines on each selection
3886 cx.set_state(indoc! {"
3887 2«
3888 1ˇ»
3889
3890 bb«bb
3891 aaaˇ»aa
3892 "});
3893 cx.update_editor(|e, window, cx| {
3894 e.manipulate_lines(window, cx, |lines| {
3895 lines.pop();
3896 })
3897 });
3898 cx.assert_editor_state(indoc! {"
3899 «2ˇ»
3900
3901 «bbbbˇ»
3902 "});
3903}
3904
3905#[gpui::test]
3906async fn test_manipulate_text(cx: &mut TestAppContext) {
3907 init_test(cx, |_| {});
3908
3909 let mut cx = EditorTestContext::new(cx).await;
3910
3911 // Test convert_to_upper_case()
3912 cx.set_state(indoc! {"
3913 «hello worldˇ»
3914 "});
3915 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3916 cx.assert_editor_state(indoc! {"
3917 «HELLO WORLDˇ»
3918 "});
3919
3920 // Test convert_to_lower_case()
3921 cx.set_state(indoc! {"
3922 «HELLO WORLDˇ»
3923 "});
3924 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3925 cx.assert_editor_state(indoc! {"
3926 «hello worldˇ»
3927 "});
3928
3929 // Test multiple line, single selection case
3930 cx.set_state(indoc! {"
3931 «The quick brown
3932 fox jumps over
3933 the lazy dogˇ»
3934 "});
3935 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3936 cx.assert_editor_state(indoc! {"
3937 «The Quick Brown
3938 Fox Jumps Over
3939 The Lazy Dogˇ»
3940 "});
3941
3942 // Test multiple line, single selection case
3943 cx.set_state(indoc! {"
3944 «The quick brown
3945 fox jumps over
3946 the lazy dogˇ»
3947 "});
3948 cx.update_editor(|e, window, cx| {
3949 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3950 });
3951 cx.assert_editor_state(indoc! {"
3952 «TheQuickBrown
3953 FoxJumpsOver
3954 TheLazyDogˇ»
3955 "});
3956
3957 // From here on out, test more complex cases of manipulate_text()
3958
3959 // Test no selection case - should affect words cursors are in
3960 // Cursor at beginning, middle, and end of word
3961 cx.set_state(indoc! {"
3962 ˇhello big beauˇtiful worldˇ
3963 "});
3964 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3965 cx.assert_editor_state(indoc! {"
3966 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3967 "});
3968
3969 // Test multiple selections on a single line and across multiple lines
3970 cx.set_state(indoc! {"
3971 «Theˇ» quick «brown
3972 foxˇ» jumps «overˇ»
3973 the «lazyˇ» dog
3974 "});
3975 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3976 cx.assert_editor_state(indoc! {"
3977 «THEˇ» quick «BROWN
3978 FOXˇ» jumps «OVERˇ»
3979 the «LAZYˇ» dog
3980 "});
3981
3982 // Test case where text length grows
3983 cx.set_state(indoc! {"
3984 «tschüߡ»
3985 "});
3986 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3987 cx.assert_editor_state(indoc! {"
3988 «TSCHÜSSˇ»
3989 "});
3990
3991 // Test to make sure we don't crash when text shrinks
3992 cx.set_state(indoc! {"
3993 aaa_bbbˇ
3994 "});
3995 cx.update_editor(|e, window, cx| {
3996 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3997 });
3998 cx.assert_editor_state(indoc! {"
3999 «aaaBbbˇ»
4000 "});
4001
4002 // Test to make sure we all aware of the fact that each word can grow and shrink
4003 // Final selections should be aware of this fact
4004 cx.set_state(indoc! {"
4005 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4006 "});
4007 cx.update_editor(|e, window, cx| {
4008 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4009 });
4010 cx.assert_editor_state(indoc! {"
4011 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4012 "});
4013
4014 cx.set_state(indoc! {"
4015 «hElLo, WoRld!ˇ»
4016 "});
4017 cx.update_editor(|e, window, cx| {
4018 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4019 });
4020 cx.assert_editor_state(indoc! {"
4021 «HeLlO, wOrLD!ˇ»
4022 "});
4023}
4024
4025#[gpui::test]
4026fn test_duplicate_line(cx: &mut TestAppContext) {
4027 init_test(cx, |_| {});
4028
4029 let editor = cx.add_window(|window, cx| {
4030 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4031 build_editor(buffer, window, cx)
4032 });
4033 _ = editor.update(cx, |editor, window, cx| {
4034 editor.change_selections(None, window, cx, |s| {
4035 s.select_display_ranges([
4036 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4037 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4038 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4039 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4040 ])
4041 });
4042 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4043 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4044 assert_eq!(
4045 editor.selections.display_ranges(cx),
4046 vec![
4047 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4048 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4049 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4050 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4051 ]
4052 );
4053 });
4054
4055 let editor = cx.add_window(|window, cx| {
4056 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4057 build_editor(buffer, window, cx)
4058 });
4059 _ = editor.update(cx, |editor, window, cx| {
4060 editor.change_selections(None, window, cx, |s| {
4061 s.select_display_ranges([
4062 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4063 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4064 ])
4065 });
4066 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4067 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4068 assert_eq!(
4069 editor.selections.display_ranges(cx),
4070 vec![
4071 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4072 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4073 ]
4074 );
4075 });
4076
4077 // With `move_upwards` the selections stay in place, except for
4078 // the lines inserted above them
4079 let editor = cx.add_window(|window, cx| {
4080 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4081 build_editor(buffer, window, cx)
4082 });
4083 _ = editor.update(cx, |editor, window, cx| {
4084 editor.change_selections(None, window, cx, |s| {
4085 s.select_display_ranges([
4086 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4087 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4088 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4089 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4090 ])
4091 });
4092 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4093 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4094 assert_eq!(
4095 editor.selections.display_ranges(cx),
4096 vec![
4097 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4098 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4099 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4100 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4101 ]
4102 );
4103 });
4104
4105 let editor = cx.add_window(|window, cx| {
4106 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4107 build_editor(buffer, window, cx)
4108 });
4109 _ = editor.update(cx, |editor, window, cx| {
4110 editor.change_selections(None, window, cx, |s| {
4111 s.select_display_ranges([
4112 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4113 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4114 ])
4115 });
4116 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4117 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4118 assert_eq!(
4119 editor.selections.display_ranges(cx),
4120 vec![
4121 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4122 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4123 ]
4124 );
4125 });
4126
4127 let editor = cx.add_window(|window, cx| {
4128 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4129 build_editor(buffer, window, cx)
4130 });
4131 _ = editor.update(cx, |editor, window, cx| {
4132 editor.change_selections(None, window, cx, |s| {
4133 s.select_display_ranges([
4134 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4135 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4136 ])
4137 });
4138 editor.duplicate_selection(&DuplicateSelection, window, cx);
4139 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4140 assert_eq!(
4141 editor.selections.display_ranges(cx),
4142 vec![
4143 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4144 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4145 ]
4146 );
4147 });
4148}
4149
4150#[gpui::test]
4151fn test_move_line_up_down(cx: &mut TestAppContext) {
4152 init_test(cx, |_| {});
4153
4154 let editor = cx.add_window(|window, cx| {
4155 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4156 build_editor(buffer, window, cx)
4157 });
4158 _ = editor.update(cx, |editor, window, cx| {
4159 editor.fold_creases(
4160 vec![
4161 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4162 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4163 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4164 ],
4165 true,
4166 window,
4167 cx,
4168 );
4169 editor.change_selections(None, window, cx, |s| {
4170 s.select_display_ranges([
4171 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4172 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4173 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4174 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4175 ])
4176 });
4177 assert_eq!(
4178 editor.display_text(cx),
4179 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4180 );
4181
4182 editor.move_line_up(&MoveLineUp, window, cx);
4183 assert_eq!(
4184 editor.display_text(cx),
4185 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4186 );
4187 assert_eq!(
4188 editor.selections.display_ranges(cx),
4189 vec![
4190 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4191 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4192 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4193 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4194 ]
4195 );
4196 });
4197
4198 _ = editor.update(cx, |editor, window, cx| {
4199 editor.move_line_down(&MoveLineDown, window, cx);
4200 assert_eq!(
4201 editor.display_text(cx),
4202 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4203 );
4204 assert_eq!(
4205 editor.selections.display_ranges(cx),
4206 vec![
4207 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4208 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4209 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4210 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4211 ]
4212 );
4213 });
4214
4215 _ = editor.update(cx, |editor, window, cx| {
4216 editor.move_line_down(&MoveLineDown, window, cx);
4217 assert_eq!(
4218 editor.display_text(cx),
4219 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4220 );
4221 assert_eq!(
4222 editor.selections.display_ranges(cx),
4223 vec![
4224 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4225 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4226 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4227 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4228 ]
4229 );
4230 });
4231
4232 _ = editor.update(cx, |editor, window, cx| {
4233 editor.move_line_up(&MoveLineUp, window, cx);
4234 assert_eq!(
4235 editor.display_text(cx),
4236 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4237 );
4238 assert_eq!(
4239 editor.selections.display_ranges(cx),
4240 vec![
4241 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4242 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4243 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4244 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4245 ]
4246 );
4247 });
4248}
4249
4250#[gpui::test]
4251fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4252 init_test(cx, |_| {});
4253
4254 let editor = cx.add_window(|window, cx| {
4255 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4256 build_editor(buffer, window, cx)
4257 });
4258 _ = editor.update(cx, |editor, window, cx| {
4259 let snapshot = editor.buffer.read(cx).snapshot(cx);
4260 editor.insert_blocks(
4261 [BlockProperties {
4262 style: BlockStyle::Fixed,
4263 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4264 height: 1,
4265 render: Arc::new(|_| div().into_any()),
4266 priority: 0,
4267 }],
4268 Some(Autoscroll::fit()),
4269 cx,
4270 );
4271 editor.change_selections(None, window, cx, |s| {
4272 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4273 });
4274 editor.move_line_down(&MoveLineDown, window, cx);
4275 });
4276}
4277
4278#[gpui::test]
4279async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4280 init_test(cx, |_| {});
4281
4282 let mut cx = EditorTestContext::new(cx).await;
4283 cx.set_state(
4284 &"
4285 ˇzero
4286 one
4287 two
4288 three
4289 four
4290 five
4291 "
4292 .unindent(),
4293 );
4294
4295 // Create a four-line block that replaces three lines of text.
4296 cx.update_editor(|editor, window, cx| {
4297 let snapshot = editor.snapshot(window, cx);
4298 let snapshot = &snapshot.buffer_snapshot;
4299 let placement = BlockPlacement::Replace(
4300 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4301 );
4302 editor.insert_blocks(
4303 [BlockProperties {
4304 placement,
4305 height: 4,
4306 style: BlockStyle::Sticky,
4307 render: Arc::new(|_| gpui::div().into_any_element()),
4308 priority: 0,
4309 }],
4310 None,
4311 cx,
4312 );
4313 });
4314
4315 // Move down so that the cursor touches the block.
4316 cx.update_editor(|editor, window, cx| {
4317 editor.move_down(&Default::default(), window, cx);
4318 });
4319 cx.assert_editor_state(
4320 &"
4321 zero
4322 «one
4323 two
4324 threeˇ»
4325 four
4326 five
4327 "
4328 .unindent(),
4329 );
4330
4331 // Move down past the block.
4332 cx.update_editor(|editor, window, cx| {
4333 editor.move_down(&Default::default(), window, cx);
4334 });
4335 cx.assert_editor_state(
4336 &"
4337 zero
4338 one
4339 two
4340 three
4341 ˇfour
4342 five
4343 "
4344 .unindent(),
4345 );
4346}
4347
4348#[gpui::test]
4349fn test_transpose(cx: &mut TestAppContext) {
4350 init_test(cx, |_| {});
4351
4352 _ = cx.add_window(|window, cx| {
4353 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4354 editor.set_style(EditorStyle::default(), window, cx);
4355 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4356 editor.transpose(&Default::default(), window, cx);
4357 assert_eq!(editor.text(cx), "bac");
4358 assert_eq!(editor.selections.ranges(cx), [2..2]);
4359
4360 editor.transpose(&Default::default(), window, cx);
4361 assert_eq!(editor.text(cx), "bca");
4362 assert_eq!(editor.selections.ranges(cx), [3..3]);
4363
4364 editor.transpose(&Default::default(), window, cx);
4365 assert_eq!(editor.text(cx), "bac");
4366 assert_eq!(editor.selections.ranges(cx), [3..3]);
4367
4368 editor
4369 });
4370
4371 _ = cx.add_window(|window, cx| {
4372 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4373 editor.set_style(EditorStyle::default(), window, cx);
4374 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4375 editor.transpose(&Default::default(), window, cx);
4376 assert_eq!(editor.text(cx), "acb\nde");
4377 assert_eq!(editor.selections.ranges(cx), [3..3]);
4378
4379 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4380 editor.transpose(&Default::default(), window, cx);
4381 assert_eq!(editor.text(cx), "acbd\ne");
4382 assert_eq!(editor.selections.ranges(cx), [5..5]);
4383
4384 editor.transpose(&Default::default(), window, cx);
4385 assert_eq!(editor.text(cx), "acbde\n");
4386 assert_eq!(editor.selections.ranges(cx), [6..6]);
4387
4388 editor.transpose(&Default::default(), window, cx);
4389 assert_eq!(editor.text(cx), "acbd\ne");
4390 assert_eq!(editor.selections.ranges(cx), [6..6]);
4391
4392 editor
4393 });
4394
4395 _ = cx.add_window(|window, cx| {
4396 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4397 editor.set_style(EditorStyle::default(), window, cx);
4398 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4399 editor.transpose(&Default::default(), window, cx);
4400 assert_eq!(editor.text(cx), "bacd\ne");
4401 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4402
4403 editor.transpose(&Default::default(), window, cx);
4404 assert_eq!(editor.text(cx), "bcade\n");
4405 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4406
4407 editor.transpose(&Default::default(), window, cx);
4408 assert_eq!(editor.text(cx), "bcda\ne");
4409 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4410
4411 editor.transpose(&Default::default(), window, cx);
4412 assert_eq!(editor.text(cx), "bcade\n");
4413 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4414
4415 editor.transpose(&Default::default(), window, cx);
4416 assert_eq!(editor.text(cx), "bcaed\n");
4417 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4418
4419 editor
4420 });
4421
4422 _ = cx.add_window(|window, cx| {
4423 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4424 editor.set_style(EditorStyle::default(), window, cx);
4425 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4426 editor.transpose(&Default::default(), window, cx);
4427 assert_eq!(editor.text(cx), "🏀🍐✋");
4428 assert_eq!(editor.selections.ranges(cx), [8..8]);
4429
4430 editor.transpose(&Default::default(), window, cx);
4431 assert_eq!(editor.text(cx), "🏀✋🍐");
4432 assert_eq!(editor.selections.ranges(cx), [11..11]);
4433
4434 editor.transpose(&Default::default(), window, cx);
4435 assert_eq!(editor.text(cx), "🏀🍐✋");
4436 assert_eq!(editor.selections.ranges(cx), [11..11]);
4437
4438 editor
4439 });
4440}
4441
4442#[gpui::test]
4443async fn test_rewrap(cx: &mut TestAppContext) {
4444 init_test(cx, |settings| {
4445 settings.languages.extend([
4446 (
4447 "Markdown".into(),
4448 LanguageSettingsContent {
4449 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4450 ..Default::default()
4451 },
4452 ),
4453 (
4454 "Plain Text".into(),
4455 LanguageSettingsContent {
4456 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4457 ..Default::default()
4458 },
4459 ),
4460 ])
4461 });
4462
4463 let mut cx = EditorTestContext::new(cx).await;
4464
4465 let language_with_c_comments = Arc::new(Language::new(
4466 LanguageConfig {
4467 line_comments: vec!["// ".into()],
4468 ..LanguageConfig::default()
4469 },
4470 None,
4471 ));
4472 let language_with_pound_comments = Arc::new(Language::new(
4473 LanguageConfig {
4474 line_comments: vec!["# ".into()],
4475 ..LanguageConfig::default()
4476 },
4477 None,
4478 ));
4479 let markdown_language = Arc::new(Language::new(
4480 LanguageConfig {
4481 name: "Markdown".into(),
4482 ..LanguageConfig::default()
4483 },
4484 None,
4485 ));
4486 let language_with_doc_comments = Arc::new(Language::new(
4487 LanguageConfig {
4488 line_comments: vec!["// ".into(), "/// ".into()],
4489 ..LanguageConfig::default()
4490 },
4491 Some(tree_sitter_rust::LANGUAGE.into()),
4492 ));
4493
4494 let plaintext_language = Arc::new(Language::new(
4495 LanguageConfig {
4496 name: "Plain Text".into(),
4497 ..LanguageConfig::default()
4498 },
4499 None,
4500 ));
4501
4502 assert_rewrap(
4503 indoc! {"
4504 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. 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.
4505 "},
4506 indoc! {"
4507 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4508 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4509 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4510 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4511 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4512 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4513 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4514 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4515 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4516 // porttitor id. Aliquam id accumsan eros.
4517 "},
4518 language_with_c_comments.clone(),
4519 &mut cx,
4520 );
4521
4522 // Test that rewrapping works inside of a selection
4523 assert_rewrap(
4524 indoc! {"
4525 «// 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.ˇ»
4526 "},
4527 indoc! {"
4528 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4529 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4530 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4531 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4532 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4533 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4534 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4535 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4536 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4537 // porttitor id. Aliquam id accumsan eros.ˇ»
4538 "},
4539 language_with_c_comments.clone(),
4540 &mut cx,
4541 );
4542
4543 // Test that cursors that expand to the same region are collapsed.
4544 assert_rewrap(
4545 indoc! {"
4546 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4547 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4548 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4549 // ˇ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.
4550 "},
4551 indoc! {"
4552 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4553 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4554 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4555 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4556 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4557 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4558 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4559 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4560 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4561 // porttitor id. Aliquam id accumsan eros.
4562 "},
4563 language_with_c_comments.clone(),
4564 &mut cx,
4565 );
4566
4567 // Test that non-contiguous selections are treated separately.
4568 assert_rewrap(
4569 indoc! {"
4570 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4571 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4572 //
4573 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4574 // ˇ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.
4575 "},
4576 indoc! {"
4577 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4578 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4579 // auctor, eu lacinia sapien scelerisque.
4580 //
4581 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4582 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4583 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4584 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4585 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4586 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4587 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4588 "},
4589 language_with_c_comments.clone(),
4590 &mut cx,
4591 );
4592
4593 // Test that different comment prefixes are supported.
4594 assert_rewrap(
4595 indoc! {"
4596 # ˇ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.
4597 "},
4598 indoc! {"
4599 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4600 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4601 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4602 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4603 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4604 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4605 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4606 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4607 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4608 # accumsan eros.
4609 "},
4610 language_with_pound_comments.clone(),
4611 &mut cx,
4612 );
4613
4614 // Test that rewrapping is ignored outside of comments in most languages.
4615 assert_rewrap(
4616 indoc! {"
4617 /// Adds two numbers.
4618 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4619 fn add(a: u32, b: u32) -> u32 {
4620 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ˇ
4621 }
4622 "},
4623 indoc! {"
4624 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4625 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4626 fn add(a: u32, b: u32) -> u32 {
4627 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ˇ
4628 }
4629 "},
4630 language_with_doc_comments.clone(),
4631 &mut cx,
4632 );
4633
4634 // Test that rewrapping works in Markdown and Plain Text languages.
4635 assert_rewrap(
4636 indoc! {"
4637 # Hello
4638
4639 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.
4640 "},
4641 indoc! {"
4642 # Hello
4643
4644 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4645 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4646 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4647 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4648 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4649 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4650 Integer sit amet scelerisque nisi.
4651 "},
4652 markdown_language,
4653 &mut cx,
4654 );
4655
4656 assert_rewrap(
4657 indoc! {"
4658 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.
4659 "},
4660 indoc! {"
4661 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4662 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4663 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4664 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4665 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4666 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4667 Integer sit amet scelerisque nisi.
4668 "},
4669 plaintext_language,
4670 &mut cx,
4671 );
4672
4673 // Test rewrapping unaligned comments in a selection.
4674 assert_rewrap(
4675 indoc! {"
4676 fn foo() {
4677 if true {
4678 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4679 // Praesent semper egestas tellus id dignissim.ˇ»
4680 do_something();
4681 } else {
4682 //
4683 }
4684 }
4685 "},
4686 indoc! {"
4687 fn foo() {
4688 if true {
4689 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4690 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4691 // egestas tellus id dignissim.ˇ»
4692 do_something();
4693 } else {
4694 //
4695 }
4696 }
4697 "},
4698 language_with_doc_comments.clone(),
4699 &mut cx,
4700 );
4701
4702 assert_rewrap(
4703 indoc! {"
4704 fn foo() {
4705 if true {
4706 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4707 // Praesent semper egestas tellus id dignissim.»
4708 do_something();
4709 } else {
4710 //
4711 }
4712
4713 }
4714 "},
4715 indoc! {"
4716 fn foo() {
4717 if true {
4718 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4719 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4720 // egestas tellus id dignissim.»
4721 do_something();
4722 } else {
4723 //
4724 }
4725
4726 }
4727 "},
4728 language_with_doc_comments.clone(),
4729 &mut cx,
4730 );
4731
4732 #[track_caller]
4733 fn assert_rewrap(
4734 unwrapped_text: &str,
4735 wrapped_text: &str,
4736 language: Arc<Language>,
4737 cx: &mut EditorTestContext,
4738 ) {
4739 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4740 cx.set_state(unwrapped_text);
4741 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4742 cx.assert_editor_state(wrapped_text);
4743 }
4744}
4745
4746#[gpui::test]
4747async fn test_hard_wrap(cx: &mut TestAppContext) {
4748 init_test(cx, |_| {});
4749 let mut cx = EditorTestContext::new(cx).await;
4750
4751 cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
4752 cx.update_editor(|editor, _, cx| {
4753 editor.set_hard_wrap(Some(14), cx);
4754 });
4755
4756 cx.set_state(indoc!(
4757 "
4758 one two three ˇ
4759 "
4760 ));
4761 cx.simulate_input("four");
4762 cx.run_until_parked();
4763
4764 cx.assert_editor_state(indoc!(
4765 "
4766 one two three
4767 fourˇ
4768 "
4769 ));
4770
4771 cx.update_editor(|editor, window, cx| {
4772 editor.newline(&Default::default(), window, cx);
4773 });
4774 cx.run_until_parked();
4775 cx.assert_editor_state(indoc!(
4776 "
4777 one two three
4778 four
4779 ˇ
4780 "
4781 ));
4782
4783 cx.simulate_input("five");
4784 cx.run_until_parked();
4785 cx.assert_editor_state(indoc!(
4786 "
4787 one two three
4788 four
4789 fiveˇ
4790 "
4791 ));
4792
4793 cx.update_editor(|editor, window, cx| {
4794 editor.newline(&Default::default(), window, cx);
4795 });
4796 cx.run_until_parked();
4797 cx.simulate_input("# ");
4798 cx.run_until_parked();
4799 cx.assert_editor_state(indoc!(
4800 "
4801 one two three
4802 four
4803 five
4804 # ˇ
4805 "
4806 ));
4807
4808 cx.update_editor(|editor, window, cx| {
4809 editor.newline(&Default::default(), window, cx);
4810 });
4811 cx.run_until_parked();
4812 cx.assert_editor_state(indoc!(
4813 "
4814 one two three
4815 four
4816 five
4817 #\x20
4818 #ˇ
4819 "
4820 ));
4821
4822 cx.simulate_input(" 6");
4823 cx.run_until_parked();
4824 cx.assert_editor_state(indoc!(
4825 "
4826 one two three
4827 four
4828 five
4829 #
4830 # 6ˇ
4831 "
4832 ));
4833}
4834
4835#[gpui::test]
4836async fn test_clipboard(cx: &mut TestAppContext) {
4837 init_test(cx, |_| {});
4838
4839 let mut cx = EditorTestContext::new(cx).await;
4840
4841 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4842 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4843 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4844
4845 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4846 cx.set_state("two ˇfour ˇsix ˇ");
4847 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4848 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4849
4850 // Paste again but with only two cursors. Since the number of cursors doesn't
4851 // match the number of slices in the clipboard, the entire clipboard text
4852 // is pasted at each cursor.
4853 cx.set_state("ˇtwo one✅ four three six five ˇ");
4854 cx.update_editor(|e, window, cx| {
4855 e.handle_input("( ", window, cx);
4856 e.paste(&Paste, window, cx);
4857 e.handle_input(") ", window, cx);
4858 });
4859 cx.assert_editor_state(
4860 &([
4861 "( one✅ ",
4862 "three ",
4863 "five ) ˇtwo one✅ four three six five ( one✅ ",
4864 "three ",
4865 "five ) ˇ",
4866 ]
4867 .join("\n")),
4868 );
4869
4870 // Cut with three selections, one of which is full-line.
4871 cx.set_state(indoc! {"
4872 1«2ˇ»3
4873 4ˇ567
4874 «8ˇ»9"});
4875 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4876 cx.assert_editor_state(indoc! {"
4877 1ˇ3
4878 ˇ9"});
4879
4880 // Paste with three selections, noticing how the copied selection that was full-line
4881 // gets inserted before the second cursor.
4882 cx.set_state(indoc! {"
4883 1ˇ3
4884 9ˇ
4885 «oˇ»ne"});
4886 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4887 cx.assert_editor_state(indoc! {"
4888 12ˇ3
4889 4567
4890 9ˇ
4891 8ˇne"});
4892
4893 // Copy with a single cursor only, which writes the whole line into the clipboard.
4894 cx.set_state(indoc! {"
4895 The quick brown
4896 fox juˇmps over
4897 the lazy dog"});
4898 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4899 assert_eq!(
4900 cx.read_from_clipboard()
4901 .and_then(|item| item.text().as_deref().map(str::to_string)),
4902 Some("fox jumps over\n".to_string())
4903 );
4904
4905 // Paste with three selections, noticing how the copied full-line selection is inserted
4906 // before the empty selections but replaces the selection that is non-empty.
4907 cx.set_state(indoc! {"
4908 Tˇhe quick brown
4909 «foˇ»x jumps over
4910 tˇhe lazy dog"});
4911 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4912 cx.assert_editor_state(indoc! {"
4913 fox jumps over
4914 Tˇhe quick brown
4915 fox jumps over
4916 ˇx jumps over
4917 fox jumps over
4918 tˇhe lazy dog"});
4919}
4920
4921#[gpui::test]
4922async fn test_paste_multiline(cx: &mut TestAppContext) {
4923 init_test(cx, |_| {});
4924
4925 let mut cx = EditorTestContext::new(cx).await;
4926 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4927
4928 // Cut an indented block, without the leading whitespace.
4929 cx.set_state(indoc! {"
4930 const a: B = (
4931 c(),
4932 «d(
4933 e,
4934 f
4935 )ˇ»
4936 );
4937 "});
4938 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4939 cx.assert_editor_state(indoc! {"
4940 const a: B = (
4941 c(),
4942 ˇ
4943 );
4944 "});
4945
4946 // Paste it at the same position.
4947 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4948 cx.assert_editor_state(indoc! {"
4949 const a: B = (
4950 c(),
4951 d(
4952 e,
4953 f
4954 )ˇ
4955 );
4956 "});
4957
4958 // Paste it at a line with a lower indent level.
4959 cx.set_state(indoc! {"
4960 ˇ
4961 const a: B = (
4962 c(),
4963 );
4964 "});
4965 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4966 cx.assert_editor_state(indoc! {"
4967 d(
4968 e,
4969 f
4970 )ˇ
4971 const a: B = (
4972 c(),
4973 );
4974 "});
4975
4976 // Cut an indented block, with the leading whitespace.
4977 cx.set_state(indoc! {"
4978 const a: B = (
4979 c(),
4980 « d(
4981 e,
4982 f
4983 )
4984 ˇ»);
4985 "});
4986 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4987 cx.assert_editor_state(indoc! {"
4988 const a: B = (
4989 c(),
4990 ˇ);
4991 "});
4992
4993 // Paste it at the same position.
4994 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4995 cx.assert_editor_state(indoc! {"
4996 const a: B = (
4997 c(),
4998 d(
4999 e,
5000 f
5001 )
5002 ˇ);
5003 "});
5004
5005 // Paste it at a line with a higher indent level.
5006 cx.set_state(indoc! {"
5007 const a: B = (
5008 c(),
5009 d(
5010 e,
5011 fˇ
5012 )
5013 );
5014 "});
5015 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5016 cx.assert_editor_state(indoc! {"
5017 const a: B = (
5018 c(),
5019 d(
5020 e,
5021 f d(
5022 e,
5023 f
5024 )
5025 ˇ
5026 )
5027 );
5028 "});
5029
5030 // Copy an indented block, starting mid-line
5031 cx.set_state(indoc! {"
5032 const a: B = (
5033 c(),
5034 somethin«g(
5035 e,
5036 f
5037 )ˇ»
5038 );
5039 "});
5040 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
5041
5042 // Paste it on a line with a lower indent level
5043 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
5044 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5045 cx.assert_editor_state(indoc! {"
5046 const a: B = (
5047 c(),
5048 something(
5049 e,
5050 f
5051 )
5052 );
5053 g(
5054 e,
5055 f
5056 )ˇ"});
5057}
5058
5059#[gpui::test]
5060async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
5061 init_test(cx, |_| {});
5062
5063 cx.write_to_clipboard(ClipboardItem::new_string(
5064 " d(\n e\n );\n".into(),
5065 ));
5066
5067 let mut cx = EditorTestContext::new(cx).await;
5068 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5069
5070 cx.set_state(indoc! {"
5071 fn a() {
5072 b();
5073 if c() {
5074 ˇ
5075 }
5076 }
5077 "});
5078
5079 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5080 cx.assert_editor_state(indoc! {"
5081 fn a() {
5082 b();
5083 if c() {
5084 d(
5085 e
5086 );
5087 ˇ
5088 }
5089 }
5090 "});
5091
5092 cx.set_state(indoc! {"
5093 fn a() {
5094 b();
5095 ˇ
5096 }
5097 "});
5098
5099 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5100 cx.assert_editor_state(indoc! {"
5101 fn a() {
5102 b();
5103 d(
5104 e
5105 );
5106 ˇ
5107 }
5108 "});
5109}
5110
5111#[gpui::test]
5112fn test_select_all(cx: &mut TestAppContext) {
5113 init_test(cx, |_| {});
5114
5115 let editor = cx.add_window(|window, cx| {
5116 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5117 build_editor(buffer, window, cx)
5118 });
5119 _ = editor.update(cx, |editor, window, cx| {
5120 editor.select_all(&SelectAll, window, cx);
5121 assert_eq!(
5122 editor.selections.display_ranges(cx),
5123 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5124 );
5125 });
5126}
5127
5128#[gpui::test]
5129fn test_select_line(cx: &mut TestAppContext) {
5130 init_test(cx, |_| {});
5131
5132 let editor = cx.add_window(|window, cx| {
5133 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5134 build_editor(buffer, window, cx)
5135 });
5136 _ = editor.update(cx, |editor, window, cx| {
5137 editor.change_selections(None, window, cx, |s| {
5138 s.select_display_ranges([
5139 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5140 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5141 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5142 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5143 ])
5144 });
5145 editor.select_line(&SelectLine, window, cx);
5146 assert_eq!(
5147 editor.selections.display_ranges(cx),
5148 vec![
5149 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5150 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5151 ]
5152 );
5153 });
5154
5155 _ = editor.update(cx, |editor, window, cx| {
5156 editor.select_line(&SelectLine, window, cx);
5157 assert_eq!(
5158 editor.selections.display_ranges(cx),
5159 vec![
5160 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5161 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5162 ]
5163 );
5164 });
5165
5166 _ = editor.update(cx, |editor, window, cx| {
5167 editor.select_line(&SelectLine, window, cx);
5168 assert_eq!(
5169 editor.selections.display_ranges(cx),
5170 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5171 );
5172 });
5173}
5174
5175#[gpui::test]
5176async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5177 init_test(cx, |_| {});
5178 let mut cx = EditorTestContext::new(cx).await;
5179
5180 #[track_caller]
5181 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5182 cx.set_state(initial_state);
5183 cx.update_editor(|e, window, cx| {
5184 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5185 });
5186 cx.assert_editor_state(expected_state);
5187 }
5188
5189 // Selection starts and ends at the middle of lines, left-to-right
5190 test(
5191 &mut cx,
5192 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5193 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5194 );
5195 // Same thing, right-to-left
5196 test(
5197 &mut cx,
5198 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5199 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5200 );
5201
5202 // Whole buffer, left-to-right, last line *doesn't* end with newline
5203 test(
5204 &mut cx,
5205 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5206 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5207 );
5208 // Same thing, right-to-left
5209 test(
5210 &mut cx,
5211 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5212 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5213 );
5214
5215 // Whole buffer, left-to-right, last line ends with newline
5216 test(
5217 &mut cx,
5218 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5219 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5220 );
5221 // Same thing, right-to-left
5222 test(
5223 &mut cx,
5224 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5225 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5226 );
5227
5228 // Starts at the end of a line, ends at the start of another
5229 test(
5230 &mut cx,
5231 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5232 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5233 );
5234}
5235
5236#[gpui::test]
5237async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5238 init_test(cx, |_| {});
5239
5240 let editor = cx.add_window(|window, cx| {
5241 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5242 build_editor(buffer, window, cx)
5243 });
5244
5245 // setup
5246 _ = editor.update(cx, |editor, window, cx| {
5247 editor.fold_creases(
5248 vec![
5249 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5250 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5251 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5252 ],
5253 true,
5254 window,
5255 cx,
5256 );
5257 assert_eq!(
5258 editor.display_text(cx),
5259 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5260 );
5261 });
5262
5263 _ = editor.update(cx, |editor, window, cx| {
5264 editor.change_selections(None, window, cx, |s| {
5265 s.select_display_ranges([
5266 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5267 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5268 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5269 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5270 ])
5271 });
5272 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5273 assert_eq!(
5274 editor.display_text(cx),
5275 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5276 );
5277 });
5278 EditorTestContext::for_editor(editor, cx)
5279 .await
5280 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5281
5282 _ = editor.update(cx, |editor, window, cx| {
5283 editor.change_selections(None, window, cx, |s| {
5284 s.select_display_ranges([
5285 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5286 ])
5287 });
5288 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5289 assert_eq!(
5290 editor.display_text(cx),
5291 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5292 );
5293 assert_eq!(
5294 editor.selections.display_ranges(cx),
5295 [
5296 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5297 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5298 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5299 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5300 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5301 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5302 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5303 ]
5304 );
5305 });
5306 EditorTestContext::for_editor(editor, cx)
5307 .await
5308 .assert_editor_state(
5309 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5310 );
5311}
5312
5313#[gpui::test]
5314async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5315 init_test(cx, |_| {});
5316
5317 let mut cx = EditorTestContext::new(cx).await;
5318
5319 cx.set_state(indoc!(
5320 r#"abc
5321 defˇghi
5322
5323 jk
5324 nlmo
5325 "#
5326 ));
5327
5328 cx.update_editor(|editor, window, cx| {
5329 editor.add_selection_above(&Default::default(), window, cx);
5330 });
5331
5332 cx.assert_editor_state(indoc!(
5333 r#"abcˇ
5334 defˇghi
5335
5336 jk
5337 nlmo
5338 "#
5339 ));
5340
5341 cx.update_editor(|editor, window, cx| {
5342 editor.add_selection_above(&Default::default(), window, cx);
5343 });
5344
5345 cx.assert_editor_state(indoc!(
5346 r#"abcˇ
5347 defˇghi
5348
5349 jk
5350 nlmo
5351 "#
5352 ));
5353
5354 cx.update_editor(|editor, window, cx| {
5355 editor.add_selection_below(&Default::default(), window, cx);
5356 });
5357
5358 cx.assert_editor_state(indoc!(
5359 r#"abc
5360 defˇghi
5361
5362 jk
5363 nlmo
5364 "#
5365 ));
5366
5367 cx.update_editor(|editor, window, cx| {
5368 editor.undo_selection(&Default::default(), window, cx);
5369 });
5370
5371 cx.assert_editor_state(indoc!(
5372 r#"abcˇ
5373 defˇghi
5374
5375 jk
5376 nlmo
5377 "#
5378 ));
5379
5380 cx.update_editor(|editor, window, cx| {
5381 editor.redo_selection(&Default::default(), window, cx);
5382 });
5383
5384 cx.assert_editor_state(indoc!(
5385 r#"abc
5386 defˇghi
5387
5388 jk
5389 nlmo
5390 "#
5391 ));
5392
5393 cx.update_editor(|editor, window, cx| {
5394 editor.add_selection_below(&Default::default(), window, cx);
5395 });
5396
5397 cx.assert_editor_state(indoc!(
5398 r#"abc
5399 defˇghi
5400
5401 jk
5402 nlmˇo
5403 "#
5404 ));
5405
5406 cx.update_editor(|editor, window, cx| {
5407 editor.add_selection_below(&Default::default(), window, cx);
5408 });
5409
5410 cx.assert_editor_state(indoc!(
5411 r#"abc
5412 defˇghi
5413
5414 jk
5415 nlmˇo
5416 "#
5417 ));
5418
5419 // change selections
5420 cx.set_state(indoc!(
5421 r#"abc
5422 def«ˇg»hi
5423
5424 jk
5425 nlmo
5426 "#
5427 ));
5428
5429 cx.update_editor(|editor, window, cx| {
5430 editor.add_selection_below(&Default::default(), window, cx);
5431 });
5432
5433 cx.assert_editor_state(indoc!(
5434 r#"abc
5435 def«ˇg»hi
5436
5437 jk
5438 nlm«ˇo»
5439 "#
5440 ));
5441
5442 cx.update_editor(|editor, window, cx| {
5443 editor.add_selection_below(&Default::default(), window, cx);
5444 });
5445
5446 cx.assert_editor_state(indoc!(
5447 r#"abc
5448 def«ˇg»hi
5449
5450 jk
5451 nlm«ˇo»
5452 "#
5453 ));
5454
5455 cx.update_editor(|editor, window, cx| {
5456 editor.add_selection_above(&Default::default(), window, cx);
5457 });
5458
5459 cx.assert_editor_state(indoc!(
5460 r#"abc
5461 def«ˇg»hi
5462
5463 jk
5464 nlmo
5465 "#
5466 ));
5467
5468 cx.update_editor(|editor, window, cx| {
5469 editor.add_selection_above(&Default::default(), window, cx);
5470 });
5471
5472 cx.assert_editor_state(indoc!(
5473 r#"abc
5474 def«ˇg»hi
5475
5476 jk
5477 nlmo
5478 "#
5479 ));
5480
5481 // Change selections again
5482 cx.set_state(indoc!(
5483 r#"a«bc
5484 defgˇ»hi
5485
5486 jk
5487 nlmo
5488 "#
5489 ));
5490
5491 cx.update_editor(|editor, window, cx| {
5492 editor.add_selection_below(&Default::default(), window, cx);
5493 });
5494
5495 cx.assert_editor_state(indoc!(
5496 r#"a«bcˇ»
5497 d«efgˇ»hi
5498
5499 j«kˇ»
5500 nlmo
5501 "#
5502 ));
5503
5504 cx.update_editor(|editor, window, cx| {
5505 editor.add_selection_below(&Default::default(), window, cx);
5506 });
5507 cx.assert_editor_state(indoc!(
5508 r#"a«bcˇ»
5509 d«efgˇ»hi
5510
5511 j«kˇ»
5512 n«lmoˇ»
5513 "#
5514 ));
5515 cx.update_editor(|editor, window, cx| {
5516 editor.add_selection_above(&Default::default(), window, cx);
5517 });
5518
5519 cx.assert_editor_state(indoc!(
5520 r#"a«bcˇ»
5521 d«efgˇ»hi
5522
5523 j«kˇ»
5524 nlmo
5525 "#
5526 ));
5527
5528 // Change selections again
5529 cx.set_state(indoc!(
5530 r#"abc
5531 d«ˇefghi
5532
5533 jk
5534 nlm»o
5535 "#
5536 ));
5537
5538 cx.update_editor(|editor, window, cx| {
5539 editor.add_selection_above(&Default::default(), window, cx);
5540 });
5541
5542 cx.assert_editor_state(indoc!(
5543 r#"a«ˇbc»
5544 d«ˇef»ghi
5545
5546 j«ˇk»
5547 n«ˇlm»o
5548 "#
5549 ));
5550
5551 cx.update_editor(|editor, window, cx| {
5552 editor.add_selection_below(&Default::default(), window, cx);
5553 });
5554
5555 cx.assert_editor_state(indoc!(
5556 r#"abc
5557 d«ˇef»ghi
5558
5559 j«ˇk»
5560 n«ˇlm»o
5561 "#
5562 ));
5563}
5564
5565#[gpui::test]
5566async fn test_select_next(cx: &mut TestAppContext) {
5567 init_test(cx, |_| {});
5568
5569 let mut cx = EditorTestContext::new(cx).await;
5570 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5571
5572 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5573 .unwrap();
5574 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5575
5576 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5577 .unwrap();
5578 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5579
5580 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5581 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5582
5583 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5584 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5585
5586 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5587 .unwrap();
5588 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5589
5590 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5591 .unwrap();
5592 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5593}
5594
5595#[gpui::test]
5596async fn test_select_all_matches(cx: &mut TestAppContext) {
5597 init_test(cx, |_| {});
5598
5599 let mut cx = EditorTestContext::new(cx).await;
5600
5601 // Test caret-only selections
5602 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5603 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5604 .unwrap();
5605 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5606
5607 // Test left-to-right selections
5608 cx.set_state("abc\n«abcˇ»\nabc");
5609 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5610 .unwrap();
5611 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5612
5613 // Test right-to-left selections
5614 cx.set_state("abc\n«ˇabc»\nabc");
5615 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5616 .unwrap();
5617 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5618
5619 // Test selecting whitespace with caret selection
5620 cx.set_state("abc\nˇ abc\nabc");
5621 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5622 .unwrap();
5623 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5624
5625 // Test selecting whitespace with left-to-right selection
5626 cx.set_state("abc\n«ˇ »abc\nabc");
5627 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5628 .unwrap();
5629 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5630
5631 // Test no matches with right-to-left selection
5632 cx.set_state("abc\n« ˇ»abc\nabc");
5633 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5634 .unwrap();
5635 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5636}
5637
5638#[gpui::test]
5639async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5640 init_test(cx, |_| {});
5641
5642 let mut cx = EditorTestContext::new(cx).await;
5643 cx.set_state(
5644 r#"let foo = 2;
5645lˇet foo = 2;
5646let fooˇ = 2;
5647let foo = 2;
5648let foo = ˇ2;"#,
5649 );
5650
5651 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5652 .unwrap();
5653 cx.assert_editor_state(
5654 r#"let foo = 2;
5655«letˇ» foo = 2;
5656let «fooˇ» = 2;
5657let foo = 2;
5658let foo = «2ˇ»;"#,
5659 );
5660
5661 // noop for multiple selections with different contents
5662 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5663 .unwrap();
5664 cx.assert_editor_state(
5665 r#"let foo = 2;
5666«letˇ» foo = 2;
5667let «fooˇ» = 2;
5668let foo = 2;
5669let foo = «2ˇ»;"#,
5670 );
5671}
5672
5673#[gpui::test]
5674async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5675 init_test(cx, |_| {});
5676
5677 let mut cx =
5678 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5679
5680 cx.assert_editor_state(indoc! {"
5681 ˇbbb
5682 ccc
5683
5684 bbb
5685 ccc
5686 "});
5687 cx.dispatch_action(SelectPrevious::default());
5688 cx.assert_editor_state(indoc! {"
5689 «bbbˇ»
5690 ccc
5691
5692 bbb
5693 ccc
5694 "});
5695 cx.dispatch_action(SelectPrevious::default());
5696 cx.assert_editor_state(indoc! {"
5697 «bbbˇ»
5698 ccc
5699
5700 «bbbˇ»
5701 ccc
5702 "});
5703}
5704
5705#[gpui::test]
5706async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5707 init_test(cx, |_| {});
5708
5709 let mut cx = EditorTestContext::new(cx).await;
5710 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5711
5712 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5713 .unwrap();
5714 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5715
5716 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5717 .unwrap();
5718 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5719
5720 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5721 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5722
5723 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5724 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5725
5726 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5727 .unwrap();
5728 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5729
5730 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5731 .unwrap();
5732 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5733
5734 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5735 .unwrap();
5736 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5737}
5738
5739#[gpui::test]
5740async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5741 init_test(cx, |_| {});
5742
5743 let mut cx = EditorTestContext::new(cx).await;
5744 cx.set_state("aˇ");
5745
5746 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5747 .unwrap();
5748 cx.assert_editor_state("«aˇ»");
5749 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5750 .unwrap();
5751 cx.assert_editor_state("«aˇ»");
5752}
5753
5754#[gpui::test]
5755async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5756 init_test(cx, |_| {});
5757
5758 let mut cx = EditorTestContext::new(cx).await;
5759 cx.set_state(
5760 r#"let foo = 2;
5761lˇet foo = 2;
5762let fooˇ = 2;
5763let foo = 2;
5764let foo = ˇ2;"#,
5765 );
5766
5767 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5768 .unwrap();
5769 cx.assert_editor_state(
5770 r#"let foo = 2;
5771«letˇ» foo = 2;
5772let «fooˇ» = 2;
5773let foo = 2;
5774let foo = «2ˇ»;"#,
5775 );
5776
5777 // noop for multiple selections with different contents
5778 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5779 .unwrap();
5780 cx.assert_editor_state(
5781 r#"let foo = 2;
5782«letˇ» foo = 2;
5783let «fooˇ» = 2;
5784let foo = 2;
5785let foo = «2ˇ»;"#,
5786 );
5787}
5788
5789#[gpui::test]
5790async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5791 init_test(cx, |_| {});
5792
5793 let mut cx = EditorTestContext::new(cx).await;
5794 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5795
5796 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5797 .unwrap();
5798 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5799
5800 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5801 .unwrap();
5802 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5803
5804 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5805 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5806
5807 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5808 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5809
5810 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5811 .unwrap();
5812 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5813
5814 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5815 .unwrap();
5816 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5817}
5818
5819#[gpui::test]
5820async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5821 init_test(cx, |_| {});
5822
5823 let language = Arc::new(Language::new(
5824 LanguageConfig::default(),
5825 Some(tree_sitter_rust::LANGUAGE.into()),
5826 ));
5827
5828 let text = r#"
5829 use mod1::mod2::{mod3, mod4};
5830
5831 fn fn_1(param1: bool, param2: &str) {
5832 let var1 = "text";
5833 }
5834 "#
5835 .unindent();
5836
5837 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5838 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5839 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5840
5841 editor
5842 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5843 .await;
5844
5845 editor.update_in(cx, |editor, window, cx| {
5846 editor.change_selections(None, window, cx, |s| {
5847 s.select_display_ranges([
5848 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5849 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5850 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5851 ]);
5852 });
5853 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5854 });
5855 editor.update(cx, |editor, cx| {
5856 assert_text_with_selections(
5857 editor,
5858 indoc! {r#"
5859 use mod1::mod2::{mod3, «mod4ˇ»};
5860
5861 fn fn_1«ˇ(param1: bool, param2: &str)» {
5862 let var1 = "«textˇ»";
5863 }
5864 "#},
5865 cx,
5866 );
5867 });
5868
5869 editor.update_in(cx, |editor, window, cx| {
5870 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5871 });
5872 editor.update(cx, |editor, cx| {
5873 assert_text_with_selections(
5874 editor,
5875 indoc! {r#"
5876 use mod1::mod2::«{mod3, mod4}ˇ»;
5877
5878 «ˇfn fn_1(param1: bool, param2: &str) {
5879 let var1 = "text";
5880 }»
5881 "#},
5882 cx,
5883 );
5884 });
5885
5886 editor.update_in(cx, |editor, window, cx| {
5887 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5888 });
5889 assert_eq!(
5890 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5891 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5892 );
5893
5894 // Trying to expand the selected syntax node one more time has no effect.
5895 editor.update_in(cx, |editor, window, cx| {
5896 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5897 });
5898 assert_eq!(
5899 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5900 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5901 );
5902
5903 editor.update_in(cx, |editor, window, cx| {
5904 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5905 });
5906 editor.update(cx, |editor, cx| {
5907 assert_text_with_selections(
5908 editor,
5909 indoc! {r#"
5910 use mod1::mod2::«{mod3, mod4}ˇ»;
5911
5912 «ˇfn fn_1(param1: bool, param2: &str) {
5913 let var1 = "text";
5914 }»
5915 "#},
5916 cx,
5917 );
5918 });
5919
5920 editor.update_in(cx, |editor, window, cx| {
5921 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, 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 editor.update_in(cx, |editor, window, cx| {
5938 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5939 });
5940 editor.update(cx, |editor, cx| {
5941 assert_text_with_selections(
5942 editor,
5943 indoc! {r#"
5944 use mod1::mod2::{mod3, mo«ˇ»d4};
5945
5946 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5947 let var1 = "te«ˇ»xt";
5948 }
5949 "#},
5950 cx,
5951 );
5952 });
5953
5954 // Trying to shrink the selected syntax node one more time has no effect.
5955 editor.update_in(cx, |editor, window, cx| {
5956 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5957 });
5958 editor.update_in(cx, |editor, _, cx| {
5959 assert_text_with_selections(
5960 editor,
5961 indoc! {r#"
5962 use mod1::mod2::{mod3, mo«ˇ»d4};
5963
5964 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5965 let var1 = "te«ˇ»xt";
5966 }
5967 "#},
5968 cx,
5969 );
5970 });
5971
5972 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5973 // a fold.
5974 editor.update_in(cx, |editor, window, cx| {
5975 editor.fold_creases(
5976 vec![
5977 Crease::simple(
5978 Point::new(0, 21)..Point::new(0, 24),
5979 FoldPlaceholder::test(),
5980 ),
5981 Crease::simple(
5982 Point::new(3, 20)..Point::new(3, 22),
5983 FoldPlaceholder::test(),
5984 ),
5985 ],
5986 true,
5987 window,
5988 cx,
5989 );
5990 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5991 });
5992 editor.update(cx, |editor, cx| {
5993 assert_text_with_selections(
5994 editor,
5995 indoc! {r#"
5996 use mod1::mod2::«{mod3, mod4}ˇ»;
5997
5998 fn fn_1«ˇ(param1: bool, param2: &str)» {
5999 «let var1 = "text";ˇ»
6000 }
6001 "#},
6002 cx,
6003 );
6004 });
6005}
6006
6007#[gpui::test]
6008async fn test_fold_function_bodies(cx: &mut TestAppContext) {
6009 init_test(cx, |_| {});
6010
6011 let base_text = r#"
6012 impl A {
6013 // this is an uncommitted comment
6014
6015 fn b() {
6016 c();
6017 }
6018
6019 // this is another uncommitted comment
6020
6021 fn d() {
6022 // e
6023 // f
6024 }
6025 }
6026
6027 fn g() {
6028 // h
6029 }
6030 "#
6031 .unindent();
6032
6033 let text = r#"
6034 ˇimpl A {
6035
6036 fn b() {
6037 c();
6038 }
6039
6040 fn d() {
6041 // e
6042 // f
6043 }
6044 }
6045
6046 fn g() {
6047 // h
6048 }
6049 "#
6050 .unindent();
6051
6052 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6053 cx.set_state(&text);
6054 cx.set_head_text(&base_text);
6055 cx.update_editor(|editor, window, cx| {
6056 editor.expand_all_diff_hunks(&Default::default(), window, cx);
6057 });
6058
6059 cx.assert_state_with_diff(
6060 "
6061 ˇimpl A {
6062 - // this is an uncommitted comment
6063
6064 fn b() {
6065 c();
6066 }
6067
6068 - // this is another uncommitted comment
6069 -
6070 fn d() {
6071 // e
6072 // f
6073 }
6074 }
6075
6076 fn g() {
6077 // h
6078 }
6079 "
6080 .unindent(),
6081 );
6082
6083 let expected_display_text = "
6084 impl A {
6085 // this is an uncommitted comment
6086
6087 fn b() {
6088 ⋯
6089 }
6090
6091 // this is another uncommitted comment
6092
6093 fn d() {
6094 ⋯
6095 }
6096 }
6097
6098 fn g() {
6099 ⋯
6100 }
6101 "
6102 .unindent();
6103
6104 cx.update_editor(|editor, window, cx| {
6105 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6106 assert_eq!(editor.display_text(cx), expected_display_text);
6107 });
6108}
6109
6110#[gpui::test]
6111async fn test_autoindent(cx: &mut TestAppContext) {
6112 init_test(cx, |_| {});
6113
6114 let language = Arc::new(
6115 Language::new(
6116 LanguageConfig {
6117 brackets: BracketPairConfig {
6118 pairs: vec![
6119 BracketPair {
6120 start: "{".to_string(),
6121 end: "}".to_string(),
6122 close: false,
6123 surround: false,
6124 newline: true,
6125 },
6126 BracketPair {
6127 start: "(".to_string(),
6128 end: ")".to_string(),
6129 close: false,
6130 surround: false,
6131 newline: true,
6132 },
6133 ],
6134 ..Default::default()
6135 },
6136 ..Default::default()
6137 },
6138 Some(tree_sitter_rust::LANGUAGE.into()),
6139 )
6140 .with_indents_query(
6141 r#"
6142 (_ "(" ")" @end) @indent
6143 (_ "{" "}" @end) @indent
6144 "#,
6145 )
6146 .unwrap(),
6147 );
6148
6149 let text = "fn a() {}";
6150
6151 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6152 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6153 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6154 editor
6155 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6156 .await;
6157
6158 editor.update_in(cx, |editor, window, cx| {
6159 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6160 editor.newline(&Newline, window, cx);
6161 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6162 assert_eq!(
6163 editor.selections.ranges(cx),
6164 &[
6165 Point::new(1, 4)..Point::new(1, 4),
6166 Point::new(3, 4)..Point::new(3, 4),
6167 Point::new(5, 0)..Point::new(5, 0)
6168 ]
6169 );
6170 });
6171}
6172
6173#[gpui::test]
6174async fn test_autoindent_selections(cx: &mut TestAppContext) {
6175 init_test(cx, |_| {});
6176
6177 {
6178 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6179 cx.set_state(indoc! {"
6180 impl A {
6181
6182 fn b() {}
6183
6184 «fn c() {
6185
6186 }ˇ»
6187 }
6188 "});
6189
6190 cx.update_editor(|editor, window, cx| {
6191 editor.autoindent(&Default::default(), window, cx);
6192 });
6193
6194 cx.assert_editor_state(indoc! {"
6195 impl A {
6196
6197 fn b() {}
6198
6199 «fn c() {
6200
6201 }ˇ»
6202 }
6203 "});
6204 }
6205
6206 {
6207 let mut cx = EditorTestContext::new_multibuffer(
6208 cx,
6209 [indoc! { "
6210 impl A {
6211 «
6212 // a
6213 fn b(){}
6214 »
6215 «
6216 }
6217 fn c(){}
6218 »
6219 "}],
6220 );
6221
6222 let buffer = cx.update_editor(|editor, _, cx| {
6223 let buffer = editor.buffer().update(cx, |buffer, _| {
6224 buffer.all_buffers().iter().next().unwrap().clone()
6225 });
6226 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6227 buffer
6228 });
6229
6230 cx.run_until_parked();
6231 cx.update_editor(|editor, window, cx| {
6232 editor.select_all(&Default::default(), window, cx);
6233 editor.autoindent(&Default::default(), window, cx)
6234 });
6235 cx.run_until_parked();
6236
6237 cx.update(|_, cx| {
6238 pretty_assertions::assert_eq!(
6239 buffer.read(cx).text(),
6240 indoc! { "
6241 impl A {
6242
6243 // a
6244 fn b(){}
6245
6246
6247 }
6248 fn c(){}
6249
6250 " }
6251 )
6252 });
6253 }
6254}
6255
6256#[gpui::test]
6257async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6258 init_test(cx, |_| {});
6259
6260 let mut cx = EditorTestContext::new(cx).await;
6261
6262 let language = Arc::new(Language::new(
6263 LanguageConfig {
6264 brackets: BracketPairConfig {
6265 pairs: vec![
6266 BracketPair {
6267 start: "{".to_string(),
6268 end: "}".to_string(),
6269 close: true,
6270 surround: true,
6271 newline: true,
6272 },
6273 BracketPair {
6274 start: "(".to_string(),
6275 end: ")".to_string(),
6276 close: true,
6277 surround: true,
6278 newline: true,
6279 },
6280 BracketPair {
6281 start: "/*".to_string(),
6282 end: " */".to_string(),
6283 close: true,
6284 surround: true,
6285 newline: true,
6286 },
6287 BracketPair {
6288 start: "[".to_string(),
6289 end: "]".to_string(),
6290 close: false,
6291 surround: false,
6292 newline: true,
6293 },
6294 BracketPair {
6295 start: "\"".to_string(),
6296 end: "\"".to_string(),
6297 close: true,
6298 surround: true,
6299 newline: false,
6300 },
6301 BracketPair {
6302 start: "<".to_string(),
6303 end: ">".to_string(),
6304 close: false,
6305 surround: true,
6306 newline: true,
6307 },
6308 ],
6309 ..Default::default()
6310 },
6311 autoclose_before: "})]".to_string(),
6312 ..Default::default()
6313 },
6314 Some(tree_sitter_rust::LANGUAGE.into()),
6315 ));
6316
6317 cx.language_registry().add(language.clone());
6318 cx.update_buffer(|buffer, cx| {
6319 buffer.set_language(Some(language), cx);
6320 });
6321
6322 cx.set_state(
6323 &r#"
6324 🏀ˇ
6325 εˇ
6326 ❤️ˇ
6327 "#
6328 .unindent(),
6329 );
6330
6331 // autoclose multiple nested brackets at multiple cursors
6332 cx.update_editor(|editor, window, cx| {
6333 editor.handle_input("{", window, cx);
6334 editor.handle_input("{", window, cx);
6335 editor.handle_input("{", window, cx);
6336 });
6337 cx.assert_editor_state(
6338 &"
6339 🏀{{{ˇ}}}
6340 ε{{{ˇ}}}
6341 ❤️{{{ˇ}}}
6342 "
6343 .unindent(),
6344 );
6345
6346 // insert a different closing bracket
6347 cx.update_editor(|editor, window, cx| {
6348 editor.handle_input(")", window, cx);
6349 });
6350 cx.assert_editor_state(
6351 &"
6352 🏀{{{)ˇ}}}
6353 ε{{{)ˇ}}}
6354 ❤️{{{)ˇ}}}
6355 "
6356 .unindent(),
6357 );
6358
6359 // skip over the auto-closed brackets when typing a closing bracket
6360 cx.update_editor(|editor, window, cx| {
6361 editor.move_right(&MoveRight, window, cx);
6362 editor.handle_input("}", window, cx);
6363 editor.handle_input("}", window, cx);
6364 editor.handle_input("}", window, cx);
6365 });
6366 cx.assert_editor_state(
6367 &"
6368 🏀{{{)}}}}ˇ
6369 ε{{{)}}}}ˇ
6370 ❤️{{{)}}}}ˇ
6371 "
6372 .unindent(),
6373 );
6374
6375 // autoclose multi-character pairs
6376 cx.set_state(
6377 &"
6378 ˇ
6379 ˇ
6380 "
6381 .unindent(),
6382 );
6383 cx.update_editor(|editor, window, cx| {
6384 editor.handle_input("/", window, cx);
6385 editor.handle_input("*", window, cx);
6386 });
6387 cx.assert_editor_state(
6388 &"
6389 /*ˇ */
6390 /*ˇ */
6391 "
6392 .unindent(),
6393 );
6394
6395 // one cursor autocloses a multi-character pair, one cursor
6396 // does not autoclose.
6397 cx.set_state(
6398 &"
6399 /ˇ
6400 ˇ
6401 "
6402 .unindent(),
6403 );
6404 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6405 cx.assert_editor_state(
6406 &"
6407 /*ˇ */
6408 *ˇ
6409 "
6410 .unindent(),
6411 );
6412
6413 // Don't autoclose if the next character isn't whitespace and isn't
6414 // listed in the language's "autoclose_before" section.
6415 cx.set_state("ˇa b");
6416 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6417 cx.assert_editor_state("{ˇa b");
6418
6419 // Don't autoclose if `close` is false for the bracket pair
6420 cx.set_state("ˇ");
6421 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6422 cx.assert_editor_state("[ˇ");
6423
6424 // Surround with brackets if text is selected
6425 cx.set_state("«aˇ» b");
6426 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6427 cx.assert_editor_state("{«aˇ»} b");
6428
6429 // Autoclose when not immediately after a word character
6430 cx.set_state("a ˇ");
6431 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6432 cx.assert_editor_state("a \"ˇ\"");
6433
6434 // Autoclose pair where the start and end characters are the same
6435 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6436 cx.assert_editor_state("a \"\"ˇ");
6437
6438 // Don't autoclose when immediately after a word character
6439 cx.set_state("aˇ");
6440 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6441 cx.assert_editor_state("a\"ˇ");
6442
6443 // Do autoclose when after a non-word character
6444 cx.set_state("{ˇ");
6445 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6446 cx.assert_editor_state("{\"ˇ\"");
6447
6448 // Non identical pairs autoclose regardless of preceding character
6449 cx.set_state("aˇ");
6450 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6451 cx.assert_editor_state("a{ˇ}");
6452
6453 // Don't autoclose pair if autoclose is disabled
6454 cx.set_state("ˇ");
6455 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6456 cx.assert_editor_state("<ˇ");
6457
6458 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6459 cx.set_state("«aˇ» b");
6460 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6461 cx.assert_editor_state("<«aˇ»> b");
6462}
6463
6464#[gpui::test]
6465async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6466 init_test(cx, |settings| {
6467 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6468 });
6469
6470 let mut cx = EditorTestContext::new(cx).await;
6471
6472 let language = Arc::new(Language::new(
6473 LanguageConfig {
6474 brackets: BracketPairConfig {
6475 pairs: vec![
6476 BracketPair {
6477 start: "{".to_string(),
6478 end: "}".to_string(),
6479 close: true,
6480 surround: true,
6481 newline: true,
6482 },
6483 BracketPair {
6484 start: "(".to_string(),
6485 end: ")".to_string(),
6486 close: true,
6487 surround: true,
6488 newline: true,
6489 },
6490 BracketPair {
6491 start: "[".to_string(),
6492 end: "]".to_string(),
6493 close: false,
6494 surround: false,
6495 newline: true,
6496 },
6497 ],
6498 ..Default::default()
6499 },
6500 autoclose_before: "})]".to_string(),
6501 ..Default::default()
6502 },
6503 Some(tree_sitter_rust::LANGUAGE.into()),
6504 ));
6505
6506 cx.language_registry().add(language.clone());
6507 cx.update_buffer(|buffer, cx| {
6508 buffer.set_language(Some(language), cx);
6509 });
6510
6511 cx.set_state(
6512 &"
6513 ˇ
6514 ˇ
6515 ˇ
6516 "
6517 .unindent(),
6518 );
6519
6520 // ensure only matching closing brackets are skipped over
6521 cx.update_editor(|editor, window, cx| {
6522 editor.handle_input("}", window, cx);
6523 editor.move_left(&MoveLeft, window, cx);
6524 editor.handle_input(")", window, cx);
6525 editor.move_left(&MoveLeft, window, cx);
6526 });
6527 cx.assert_editor_state(
6528 &"
6529 ˇ)}
6530 ˇ)}
6531 ˇ)}
6532 "
6533 .unindent(),
6534 );
6535
6536 // skip-over closing brackets at multiple cursors
6537 cx.update_editor(|editor, window, cx| {
6538 editor.handle_input(")", window, cx);
6539 editor.handle_input("}", window, cx);
6540 });
6541 cx.assert_editor_state(
6542 &"
6543 )}ˇ
6544 )}ˇ
6545 )}ˇ
6546 "
6547 .unindent(),
6548 );
6549
6550 // ignore non-close brackets
6551 cx.update_editor(|editor, window, cx| {
6552 editor.handle_input("]", window, cx);
6553 editor.move_left(&MoveLeft, window, cx);
6554 editor.handle_input("]", window, cx);
6555 });
6556 cx.assert_editor_state(
6557 &"
6558 )}]ˇ]
6559 )}]ˇ]
6560 )}]ˇ]
6561 "
6562 .unindent(),
6563 );
6564}
6565
6566#[gpui::test]
6567async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6568 init_test(cx, |_| {});
6569
6570 let mut cx = EditorTestContext::new(cx).await;
6571
6572 let html_language = Arc::new(
6573 Language::new(
6574 LanguageConfig {
6575 name: "HTML".into(),
6576 brackets: BracketPairConfig {
6577 pairs: vec![
6578 BracketPair {
6579 start: "<".into(),
6580 end: ">".into(),
6581 close: true,
6582 ..Default::default()
6583 },
6584 BracketPair {
6585 start: "{".into(),
6586 end: "}".into(),
6587 close: true,
6588 ..Default::default()
6589 },
6590 BracketPair {
6591 start: "(".into(),
6592 end: ")".into(),
6593 close: true,
6594 ..Default::default()
6595 },
6596 ],
6597 ..Default::default()
6598 },
6599 autoclose_before: "})]>".into(),
6600 ..Default::default()
6601 },
6602 Some(tree_sitter_html::LANGUAGE.into()),
6603 )
6604 .with_injection_query(
6605 r#"
6606 (script_element
6607 (raw_text) @injection.content
6608 (#set! injection.language "javascript"))
6609 "#,
6610 )
6611 .unwrap(),
6612 );
6613
6614 let javascript_language = Arc::new(Language::new(
6615 LanguageConfig {
6616 name: "JavaScript".into(),
6617 brackets: BracketPairConfig {
6618 pairs: vec![
6619 BracketPair {
6620 start: "/*".into(),
6621 end: " */".into(),
6622 close: true,
6623 ..Default::default()
6624 },
6625 BracketPair {
6626 start: "{".into(),
6627 end: "}".into(),
6628 close: true,
6629 ..Default::default()
6630 },
6631 BracketPair {
6632 start: "(".into(),
6633 end: ")".into(),
6634 close: true,
6635 ..Default::default()
6636 },
6637 ],
6638 ..Default::default()
6639 },
6640 autoclose_before: "})]>".into(),
6641 ..Default::default()
6642 },
6643 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6644 ));
6645
6646 cx.language_registry().add(html_language.clone());
6647 cx.language_registry().add(javascript_language.clone());
6648
6649 cx.update_buffer(|buffer, cx| {
6650 buffer.set_language(Some(html_language), cx);
6651 });
6652
6653 cx.set_state(
6654 &r#"
6655 <body>ˇ
6656 <script>
6657 var x = 1;ˇ
6658 </script>
6659 </body>ˇ
6660 "#
6661 .unindent(),
6662 );
6663
6664 // Precondition: different languages are active at different locations.
6665 cx.update_editor(|editor, window, cx| {
6666 let snapshot = editor.snapshot(window, cx);
6667 let cursors = editor.selections.ranges::<usize>(cx);
6668 let languages = cursors
6669 .iter()
6670 .map(|c| snapshot.language_at(c.start).unwrap().name())
6671 .collect::<Vec<_>>();
6672 assert_eq!(
6673 languages,
6674 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6675 );
6676 });
6677
6678 // Angle brackets autoclose in HTML, but not JavaScript.
6679 cx.update_editor(|editor, window, cx| {
6680 editor.handle_input("<", window, cx);
6681 editor.handle_input("a", window, cx);
6682 });
6683 cx.assert_editor_state(
6684 &r#"
6685 <body><aˇ>
6686 <script>
6687 var x = 1;<aˇ
6688 </script>
6689 </body><aˇ>
6690 "#
6691 .unindent(),
6692 );
6693
6694 // Curly braces and parens autoclose in both HTML and JavaScript.
6695 cx.update_editor(|editor, window, cx| {
6696 editor.handle_input(" b=", window, cx);
6697 editor.handle_input("{", window, cx);
6698 editor.handle_input("c", window, cx);
6699 editor.handle_input("(", window, cx);
6700 });
6701 cx.assert_editor_state(
6702 &r#"
6703 <body><a b={c(ˇ)}>
6704 <script>
6705 var x = 1;<a b={c(ˇ)}
6706 </script>
6707 </body><a b={c(ˇ)}>
6708 "#
6709 .unindent(),
6710 );
6711
6712 // Brackets that were already autoclosed are skipped.
6713 cx.update_editor(|editor, window, cx| {
6714 editor.handle_input(")", window, cx);
6715 editor.handle_input("d", window, cx);
6716 editor.handle_input("}", window, cx);
6717 });
6718 cx.assert_editor_state(
6719 &r#"
6720 <body><a b={c()d}ˇ>
6721 <script>
6722 var x = 1;<a b={c()d}ˇ
6723 </script>
6724 </body><a b={c()d}ˇ>
6725 "#
6726 .unindent(),
6727 );
6728 cx.update_editor(|editor, window, cx| {
6729 editor.handle_input(">", window, cx);
6730 });
6731 cx.assert_editor_state(
6732 &r#"
6733 <body><a b={c()d}>ˇ
6734 <script>
6735 var x = 1;<a b={c()d}>ˇ
6736 </script>
6737 </body><a b={c()d}>ˇ
6738 "#
6739 .unindent(),
6740 );
6741
6742 // Reset
6743 cx.set_state(
6744 &r#"
6745 <body>ˇ
6746 <script>
6747 var x = 1;ˇ
6748 </script>
6749 </body>ˇ
6750 "#
6751 .unindent(),
6752 );
6753
6754 cx.update_editor(|editor, window, cx| {
6755 editor.handle_input("<", window, cx);
6756 });
6757 cx.assert_editor_state(
6758 &r#"
6759 <body><ˇ>
6760 <script>
6761 var x = 1;<ˇ
6762 </script>
6763 </body><ˇ>
6764 "#
6765 .unindent(),
6766 );
6767
6768 // When backspacing, the closing angle brackets are removed.
6769 cx.update_editor(|editor, window, cx| {
6770 editor.backspace(&Backspace, window, cx);
6771 });
6772 cx.assert_editor_state(
6773 &r#"
6774 <body>ˇ
6775 <script>
6776 var x = 1;ˇ
6777 </script>
6778 </body>ˇ
6779 "#
6780 .unindent(),
6781 );
6782
6783 // Block comments autoclose in JavaScript, but not HTML.
6784 cx.update_editor(|editor, window, cx| {
6785 editor.handle_input("/", window, cx);
6786 editor.handle_input("*", window, cx);
6787 });
6788 cx.assert_editor_state(
6789 &r#"
6790 <body>/*ˇ
6791 <script>
6792 var x = 1;/*ˇ */
6793 </script>
6794 </body>/*ˇ
6795 "#
6796 .unindent(),
6797 );
6798}
6799
6800#[gpui::test]
6801async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6802 init_test(cx, |_| {});
6803
6804 let mut cx = EditorTestContext::new(cx).await;
6805
6806 let rust_language = Arc::new(
6807 Language::new(
6808 LanguageConfig {
6809 name: "Rust".into(),
6810 brackets: serde_json::from_value(json!([
6811 { "start": "{", "end": "}", "close": true, "newline": true },
6812 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6813 ]))
6814 .unwrap(),
6815 autoclose_before: "})]>".into(),
6816 ..Default::default()
6817 },
6818 Some(tree_sitter_rust::LANGUAGE.into()),
6819 )
6820 .with_override_query("(string_literal) @string")
6821 .unwrap(),
6822 );
6823
6824 cx.language_registry().add(rust_language.clone());
6825 cx.update_buffer(|buffer, cx| {
6826 buffer.set_language(Some(rust_language), cx);
6827 });
6828
6829 cx.set_state(
6830 &r#"
6831 let x = ˇ
6832 "#
6833 .unindent(),
6834 );
6835
6836 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6837 cx.update_editor(|editor, window, cx| {
6838 editor.handle_input("\"", window, cx);
6839 });
6840 cx.assert_editor_state(
6841 &r#"
6842 let x = "ˇ"
6843 "#
6844 .unindent(),
6845 );
6846
6847 // Inserting another quotation mark. The cursor moves across the existing
6848 // automatically-inserted quotation mark.
6849 cx.update_editor(|editor, window, cx| {
6850 editor.handle_input("\"", window, cx);
6851 });
6852 cx.assert_editor_state(
6853 &r#"
6854 let x = ""ˇ
6855 "#
6856 .unindent(),
6857 );
6858
6859 // Reset
6860 cx.set_state(
6861 &r#"
6862 let x = ˇ
6863 "#
6864 .unindent(),
6865 );
6866
6867 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6868 cx.update_editor(|editor, window, cx| {
6869 editor.handle_input("\"", window, cx);
6870 editor.handle_input(" ", window, cx);
6871 editor.move_left(&Default::default(), window, cx);
6872 editor.handle_input("\\", window, cx);
6873 editor.handle_input("\"", window, cx);
6874 });
6875 cx.assert_editor_state(
6876 &r#"
6877 let x = "\"ˇ "
6878 "#
6879 .unindent(),
6880 );
6881
6882 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6883 // mark. Nothing is inserted.
6884 cx.update_editor(|editor, window, cx| {
6885 editor.move_right(&Default::default(), window, cx);
6886 editor.handle_input("\"", window, cx);
6887 });
6888 cx.assert_editor_state(
6889 &r#"
6890 let x = "\" "ˇ
6891 "#
6892 .unindent(),
6893 );
6894}
6895
6896#[gpui::test]
6897async fn test_surround_with_pair(cx: &mut TestAppContext) {
6898 init_test(cx, |_| {});
6899
6900 let language = Arc::new(Language::new(
6901 LanguageConfig {
6902 brackets: BracketPairConfig {
6903 pairs: vec![
6904 BracketPair {
6905 start: "{".to_string(),
6906 end: "}".to_string(),
6907 close: true,
6908 surround: true,
6909 newline: true,
6910 },
6911 BracketPair {
6912 start: "/* ".to_string(),
6913 end: "*/".to_string(),
6914 close: true,
6915 surround: true,
6916 ..Default::default()
6917 },
6918 ],
6919 ..Default::default()
6920 },
6921 ..Default::default()
6922 },
6923 Some(tree_sitter_rust::LANGUAGE.into()),
6924 ));
6925
6926 let text = r#"
6927 a
6928 b
6929 c
6930 "#
6931 .unindent();
6932
6933 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6934 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6935 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6936 editor
6937 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6938 .await;
6939
6940 editor.update_in(cx, |editor, window, cx| {
6941 editor.change_selections(None, window, cx, |s| {
6942 s.select_display_ranges([
6943 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6944 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6945 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6946 ])
6947 });
6948
6949 editor.handle_input("{", window, cx);
6950 editor.handle_input("{", window, cx);
6951 editor.handle_input("{", window, cx);
6952 assert_eq!(
6953 editor.text(cx),
6954 "
6955 {{{a}}}
6956 {{{b}}}
6957 {{{c}}}
6958 "
6959 .unindent()
6960 );
6961 assert_eq!(
6962 editor.selections.display_ranges(cx),
6963 [
6964 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6965 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6966 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6967 ]
6968 );
6969
6970 editor.undo(&Undo, window, cx);
6971 editor.undo(&Undo, window, cx);
6972 editor.undo(&Undo, window, cx);
6973 assert_eq!(
6974 editor.text(cx),
6975 "
6976 a
6977 b
6978 c
6979 "
6980 .unindent()
6981 );
6982 assert_eq!(
6983 editor.selections.display_ranges(cx),
6984 [
6985 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6986 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6987 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6988 ]
6989 );
6990
6991 // Ensure inserting the first character of a multi-byte bracket pair
6992 // doesn't surround the selections with the bracket.
6993 editor.handle_input("/", window, cx);
6994 assert_eq!(
6995 editor.text(cx),
6996 "
6997 /
6998 /
6999 /
7000 "
7001 .unindent()
7002 );
7003 assert_eq!(
7004 editor.selections.display_ranges(cx),
7005 [
7006 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7007 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7008 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7009 ]
7010 );
7011
7012 editor.undo(&Undo, window, cx);
7013 assert_eq!(
7014 editor.text(cx),
7015 "
7016 a
7017 b
7018 c
7019 "
7020 .unindent()
7021 );
7022 assert_eq!(
7023 editor.selections.display_ranges(cx),
7024 [
7025 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
7026 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
7027 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
7028 ]
7029 );
7030
7031 // Ensure inserting the last character of a multi-byte bracket pair
7032 // doesn't surround the selections with the bracket.
7033 editor.handle_input("*", window, cx);
7034 assert_eq!(
7035 editor.text(cx),
7036 "
7037 *
7038 *
7039 *
7040 "
7041 .unindent()
7042 );
7043 assert_eq!(
7044 editor.selections.display_ranges(cx),
7045 [
7046 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
7047 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
7048 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
7049 ]
7050 );
7051 });
7052}
7053
7054#[gpui::test]
7055async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
7056 init_test(cx, |_| {});
7057
7058 let language = Arc::new(Language::new(
7059 LanguageConfig {
7060 brackets: BracketPairConfig {
7061 pairs: vec![BracketPair {
7062 start: "{".to_string(),
7063 end: "}".to_string(),
7064 close: true,
7065 surround: true,
7066 newline: true,
7067 }],
7068 ..Default::default()
7069 },
7070 autoclose_before: "}".to_string(),
7071 ..Default::default()
7072 },
7073 Some(tree_sitter_rust::LANGUAGE.into()),
7074 ));
7075
7076 let text = r#"
7077 a
7078 b
7079 c
7080 "#
7081 .unindent();
7082
7083 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7084 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7085 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7086 editor
7087 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7088 .await;
7089
7090 editor.update_in(cx, |editor, window, cx| {
7091 editor.change_selections(None, window, cx, |s| {
7092 s.select_ranges([
7093 Point::new(0, 1)..Point::new(0, 1),
7094 Point::new(1, 1)..Point::new(1, 1),
7095 Point::new(2, 1)..Point::new(2, 1),
7096 ])
7097 });
7098
7099 editor.handle_input("{", window, cx);
7100 editor.handle_input("{", window, cx);
7101 editor.handle_input("_", window, cx);
7102 assert_eq!(
7103 editor.text(cx),
7104 "
7105 a{{_}}
7106 b{{_}}
7107 c{{_}}
7108 "
7109 .unindent()
7110 );
7111 assert_eq!(
7112 editor.selections.ranges::<Point>(cx),
7113 [
7114 Point::new(0, 4)..Point::new(0, 4),
7115 Point::new(1, 4)..Point::new(1, 4),
7116 Point::new(2, 4)..Point::new(2, 4)
7117 ]
7118 );
7119
7120 editor.backspace(&Default::default(), window, cx);
7121 editor.backspace(&Default::default(), window, cx);
7122 assert_eq!(
7123 editor.text(cx),
7124 "
7125 a{}
7126 b{}
7127 c{}
7128 "
7129 .unindent()
7130 );
7131 assert_eq!(
7132 editor.selections.ranges::<Point>(cx),
7133 [
7134 Point::new(0, 2)..Point::new(0, 2),
7135 Point::new(1, 2)..Point::new(1, 2),
7136 Point::new(2, 2)..Point::new(2, 2)
7137 ]
7138 );
7139
7140 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7141 assert_eq!(
7142 editor.text(cx),
7143 "
7144 a
7145 b
7146 c
7147 "
7148 .unindent()
7149 );
7150 assert_eq!(
7151 editor.selections.ranges::<Point>(cx),
7152 [
7153 Point::new(0, 1)..Point::new(0, 1),
7154 Point::new(1, 1)..Point::new(1, 1),
7155 Point::new(2, 1)..Point::new(2, 1)
7156 ]
7157 );
7158 });
7159}
7160
7161#[gpui::test]
7162async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7163 init_test(cx, |settings| {
7164 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7165 });
7166
7167 let mut cx = EditorTestContext::new(cx).await;
7168
7169 let language = Arc::new(Language::new(
7170 LanguageConfig {
7171 brackets: BracketPairConfig {
7172 pairs: vec![
7173 BracketPair {
7174 start: "{".to_string(),
7175 end: "}".to_string(),
7176 close: true,
7177 surround: true,
7178 newline: true,
7179 },
7180 BracketPair {
7181 start: "(".to_string(),
7182 end: ")".to_string(),
7183 close: true,
7184 surround: true,
7185 newline: true,
7186 },
7187 BracketPair {
7188 start: "[".to_string(),
7189 end: "]".to_string(),
7190 close: false,
7191 surround: true,
7192 newline: true,
7193 },
7194 ],
7195 ..Default::default()
7196 },
7197 autoclose_before: "})]".to_string(),
7198 ..Default::default()
7199 },
7200 Some(tree_sitter_rust::LANGUAGE.into()),
7201 ));
7202
7203 cx.language_registry().add(language.clone());
7204 cx.update_buffer(|buffer, cx| {
7205 buffer.set_language(Some(language), cx);
7206 });
7207
7208 cx.set_state(
7209 &"
7210 {(ˇ)}
7211 [[ˇ]]
7212 {(ˇ)}
7213 "
7214 .unindent(),
7215 );
7216
7217 cx.update_editor(|editor, window, cx| {
7218 editor.backspace(&Default::default(), window, cx);
7219 editor.backspace(&Default::default(), window, cx);
7220 });
7221
7222 cx.assert_editor_state(
7223 &"
7224 ˇ
7225 ˇ]]
7226 ˇ
7227 "
7228 .unindent(),
7229 );
7230
7231 cx.update_editor(|editor, window, cx| {
7232 editor.handle_input("{", window, cx);
7233 editor.handle_input("{", window, cx);
7234 editor.move_right(&MoveRight, window, cx);
7235 editor.move_right(&MoveRight, window, cx);
7236 editor.move_left(&MoveLeft, window, cx);
7237 editor.move_left(&MoveLeft, window, cx);
7238 editor.backspace(&Default::default(), window, cx);
7239 });
7240
7241 cx.assert_editor_state(
7242 &"
7243 {ˇ}
7244 {ˇ}]]
7245 {ˇ}
7246 "
7247 .unindent(),
7248 );
7249
7250 cx.update_editor(|editor, window, cx| {
7251 editor.backspace(&Default::default(), window, cx);
7252 });
7253
7254 cx.assert_editor_state(
7255 &"
7256 ˇ
7257 ˇ]]
7258 ˇ
7259 "
7260 .unindent(),
7261 );
7262}
7263
7264#[gpui::test]
7265async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7266 init_test(cx, |_| {});
7267
7268 let language = Arc::new(Language::new(
7269 LanguageConfig::default(),
7270 Some(tree_sitter_rust::LANGUAGE.into()),
7271 ));
7272
7273 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7274 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7275 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7276 editor
7277 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7278 .await;
7279
7280 editor.update_in(cx, |editor, window, cx| {
7281 editor.set_auto_replace_emoji_shortcode(true);
7282
7283 editor.handle_input("Hello ", window, cx);
7284 editor.handle_input(":wave", window, cx);
7285 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7286
7287 editor.handle_input(":", window, cx);
7288 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7289
7290 editor.handle_input(" :smile", window, cx);
7291 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7292
7293 editor.handle_input(":", window, cx);
7294 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7295
7296 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7297 editor.handle_input(":wave", window, cx);
7298 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7299
7300 editor.handle_input(":", window, cx);
7301 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7302
7303 editor.handle_input(":1", window, cx);
7304 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7305
7306 editor.handle_input(":", window, cx);
7307 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7308
7309 // Ensure shortcode does not get replaced when it is part of a word
7310 editor.handle_input(" Test:wave", window, cx);
7311 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7312
7313 editor.handle_input(":", window, cx);
7314 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7315
7316 editor.set_auto_replace_emoji_shortcode(false);
7317
7318 // Ensure shortcode does not get replaced when auto replace is off
7319 editor.handle_input(" :wave", window, cx);
7320 assert_eq!(
7321 editor.text(cx),
7322 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7323 );
7324
7325 editor.handle_input(":", window, cx);
7326 assert_eq!(
7327 editor.text(cx),
7328 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7329 );
7330 });
7331}
7332
7333#[gpui::test]
7334async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7335 init_test(cx, |_| {});
7336
7337 let (text, insertion_ranges) = marked_text_ranges(
7338 indoc! {"
7339 ˇ
7340 "},
7341 false,
7342 );
7343
7344 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7345 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7346
7347 _ = editor.update_in(cx, |editor, window, cx| {
7348 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7349
7350 editor
7351 .insert_snippet(&insertion_ranges, snippet, window, cx)
7352 .unwrap();
7353
7354 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7355 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7356 assert_eq!(editor.text(cx), expected_text);
7357 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7358 }
7359
7360 assert(
7361 editor,
7362 cx,
7363 indoc! {"
7364 type «» =•
7365 "},
7366 );
7367
7368 assert!(editor.context_menu_visible(), "There should be a matches");
7369 });
7370}
7371
7372#[gpui::test]
7373async fn test_snippets(cx: &mut TestAppContext) {
7374 init_test(cx, |_| {});
7375
7376 let (text, insertion_ranges) = marked_text_ranges(
7377 indoc! {"
7378 a.ˇ b
7379 a.ˇ b
7380 a.ˇ b
7381 "},
7382 false,
7383 );
7384
7385 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7386 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7387
7388 editor.update_in(cx, |editor, window, cx| {
7389 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7390
7391 editor
7392 .insert_snippet(&insertion_ranges, snippet, window, cx)
7393 .unwrap();
7394
7395 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7396 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7397 assert_eq!(editor.text(cx), expected_text);
7398 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7399 }
7400
7401 assert(
7402 editor,
7403 cx,
7404 indoc! {"
7405 a.f(«one», two, «three») b
7406 a.f(«one», two, «three») b
7407 a.f(«one», two, «three») b
7408 "},
7409 );
7410
7411 // Can't move earlier than the first tab stop
7412 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7413 assert(
7414 editor,
7415 cx,
7416 indoc! {"
7417 a.f(«one», two, «three») b
7418 a.f(«one», two, «three») b
7419 a.f(«one», two, «three») b
7420 "},
7421 );
7422
7423 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7424 assert(
7425 editor,
7426 cx,
7427 indoc! {"
7428 a.f(one, «two», three) b
7429 a.f(one, «two», three) b
7430 a.f(one, «two», three) b
7431 "},
7432 );
7433
7434 editor.move_to_prev_snippet_tabstop(window, cx);
7435 assert(
7436 editor,
7437 cx,
7438 indoc! {"
7439 a.f(«one», two, «three») b
7440 a.f(«one», two, «three») b
7441 a.f(«one», two, «three») b
7442 "},
7443 );
7444
7445 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7446 assert(
7447 editor,
7448 cx,
7449 indoc! {"
7450 a.f(one, «two», three) b
7451 a.f(one, «two», three) b
7452 a.f(one, «two», three) b
7453 "},
7454 );
7455 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7456 assert(
7457 editor,
7458 cx,
7459 indoc! {"
7460 a.f(one, two, three)ˇ b
7461 a.f(one, two, three)ˇ b
7462 a.f(one, two, three)ˇ b
7463 "},
7464 );
7465
7466 // As soon as the last tab stop is reached, snippet state is gone
7467 editor.move_to_prev_snippet_tabstop(window, cx);
7468 assert(
7469 editor,
7470 cx,
7471 indoc! {"
7472 a.f(one, two, three)ˇ b
7473 a.f(one, two, three)ˇ b
7474 a.f(one, two, three)ˇ b
7475 "},
7476 );
7477 });
7478}
7479
7480#[gpui::test]
7481async fn test_document_format_during_save(cx: &mut TestAppContext) {
7482 init_test(cx, |_| {});
7483
7484 let fs = FakeFs::new(cx.executor());
7485 fs.insert_file(path!("/file.rs"), Default::default()).await;
7486
7487 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7488
7489 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7490 language_registry.add(rust_lang());
7491 let mut fake_servers = language_registry.register_fake_lsp(
7492 "Rust",
7493 FakeLspAdapter {
7494 capabilities: lsp::ServerCapabilities {
7495 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7496 ..Default::default()
7497 },
7498 ..Default::default()
7499 },
7500 );
7501
7502 let buffer = project
7503 .update(cx, |project, cx| {
7504 project.open_local_buffer(path!("/file.rs"), cx)
7505 })
7506 .await
7507 .unwrap();
7508
7509 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7510 let (editor, cx) = cx.add_window_view(|window, cx| {
7511 build_editor_with_project(project.clone(), buffer, window, cx)
7512 });
7513 editor.update_in(cx, |editor, window, cx| {
7514 editor.set_text("one\ntwo\nthree\n", window, cx)
7515 });
7516 assert!(cx.read(|cx| editor.is_dirty(cx)));
7517
7518 cx.executor().start_waiting();
7519 let fake_server = fake_servers.next().await.unwrap();
7520
7521 let save = editor
7522 .update_in(cx, |editor, window, cx| {
7523 editor.save(true, project.clone(), window, cx)
7524 })
7525 .unwrap();
7526 fake_server
7527 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7528 assert_eq!(
7529 params.text_document.uri,
7530 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7531 );
7532 assert_eq!(params.options.tab_size, 4);
7533 Ok(Some(vec![lsp::TextEdit::new(
7534 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7535 ", ".to_string(),
7536 )]))
7537 })
7538 .next()
7539 .await;
7540 cx.executor().start_waiting();
7541 save.await;
7542
7543 assert_eq!(
7544 editor.update(cx, |editor, cx| editor.text(cx)),
7545 "one, two\nthree\n"
7546 );
7547 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7548
7549 editor.update_in(cx, |editor, window, cx| {
7550 editor.set_text("one\ntwo\nthree\n", window, cx)
7551 });
7552 assert!(cx.read(|cx| editor.is_dirty(cx)));
7553
7554 // Ensure we can still save even if formatting hangs.
7555 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7556 assert_eq!(
7557 params.text_document.uri,
7558 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7559 );
7560 futures::future::pending::<()>().await;
7561 unreachable!()
7562 });
7563 let save = editor
7564 .update_in(cx, |editor, window, cx| {
7565 editor.save(true, project.clone(), window, cx)
7566 })
7567 .unwrap();
7568 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7569 cx.executor().start_waiting();
7570 save.await;
7571 assert_eq!(
7572 editor.update(cx, |editor, cx| editor.text(cx)),
7573 "one\ntwo\nthree\n"
7574 );
7575 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7576
7577 // For non-dirty buffer, no formatting request should be sent
7578 let save = editor
7579 .update_in(cx, |editor, window, cx| {
7580 editor.save(true, project.clone(), window, cx)
7581 })
7582 .unwrap();
7583 let _pending_format_request = fake_server
7584 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7585 panic!("Should not be invoked on non-dirty buffer");
7586 })
7587 .next();
7588 cx.executor().start_waiting();
7589 save.await;
7590
7591 // Set rust language override and assert overridden tabsize is sent to language server
7592 update_test_language_settings(cx, |settings| {
7593 settings.languages.insert(
7594 "Rust".into(),
7595 LanguageSettingsContent {
7596 tab_size: NonZeroU32::new(8),
7597 ..Default::default()
7598 },
7599 );
7600 });
7601
7602 editor.update_in(cx, |editor, window, cx| {
7603 editor.set_text("somehting_new\n", window, cx)
7604 });
7605 assert!(cx.read(|cx| editor.is_dirty(cx)));
7606 let save = editor
7607 .update_in(cx, |editor, window, cx| {
7608 editor.save(true, project.clone(), window, cx)
7609 })
7610 .unwrap();
7611 fake_server
7612 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7613 assert_eq!(
7614 params.text_document.uri,
7615 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7616 );
7617 assert_eq!(params.options.tab_size, 8);
7618 Ok(Some(vec![]))
7619 })
7620 .next()
7621 .await;
7622 cx.executor().start_waiting();
7623 save.await;
7624}
7625
7626#[gpui::test]
7627async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7628 init_test(cx, |_| {});
7629
7630 let cols = 4;
7631 let rows = 10;
7632 let sample_text_1 = sample_text(rows, cols, 'a');
7633 assert_eq!(
7634 sample_text_1,
7635 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7636 );
7637 let sample_text_2 = sample_text(rows, cols, 'l');
7638 assert_eq!(
7639 sample_text_2,
7640 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7641 );
7642 let sample_text_3 = sample_text(rows, cols, 'v');
7643 assert_eq!(
7644 sample_text_3,
7645 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7646 );
7647
7648 let fs = FakeFs::new(cx.executor());
7649 fs.insert_tree(
7650 path!("/a"),
7651 json!({
7652 "main.rs": sample_text_1,
7653 "other.rs": sample_text_2,
7654 "lib.rs": sample_text_3,
7655 }),
7656 )
7657 .await;
7658
7659 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7660 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7661 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7662
7663 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7664 language_registry.add(rust_lang());
7665 let mut fake_servers = language_registry.register_fake_lsp(
7666 "Rust",
7667 FakeLspAdapter {
7668 capabilities: lsp::ServerCapabilities {
7669 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7670 ..Default::default()
7671 },
7672 ..Default::default()
7673 },
7674 );
7675
7676 let worktree = project.update(cx, |project, cx| {
7677 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7678 assert_eq!(worktrees.len(), 1);
7679 worktrees.pop().unwrap()
7680 });
7681 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7682
7683 let buffer_1 = project
7684 .update(cx, |project, cx| {
7685 project.open_buffer((worktree_id, "main.rs"), cx)
7686 })
7687 .await
7688 .unwrap();
7689 let buffer_2 = project
7690 .update(cx, |project, cx| {
7691 project.open_buffer((worktree_id, "other.rs"), cx)
7692 })
7693 .await
7694 .unwrap();
7695 let buffer_3 = project
7696 .update(cx, |project, cx| {
7697 project.open_buffer((worktree_id, "lib.rs"), cx)
7698 })
7699 .await
7700 .unwrap();
7701
7702 let multi_buffer = cx.new(|cx| {
7703 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7704 multi_buffer.push_excerpts(
7705 buffer_1.clone(),
7706 [
7707 ExcerptRange {
7708 context: Point::new(0, 0)..Point::new(3, 0),
7709 primary: None,
7710 },
7711 ExcerptRange {
7712 context: Point::new(5, 0)..Point::new(7, 0),
7713 primary: None,
7714 },
7715 ExcerptRange {
7716 context: Point::new(9, 0)..Point::new(10, 4),
7717 primary: None,
7718 },
7719 ],
7720 cx,
7721 );
7722 multi_buffer.push_excerpts(
7723 buffer_2.clone(),
7724 [
7725 ExcerptRange {
7726 context: Point::new(0, 0)..Point::new(3, 0),
7727 primary: None,
7728 },
7729 ExcerptRange {
7730 context: Point::new(5, 0)..Point::new(7, 0),
7731 primary: None,
7732 },
7733 ExcerptRange {
7734 context: Point::new(9, 0)..Point::new(10, 4),
7735 primary: None,
7736 },
7737 ],
7738 cx,
7739 );
7740 multi_buffer.push_excerpts(
7741 buffer_3.clone(),
7742 [
7743 ExcerptRange {
7744 context: Point::new(0, 0)..Point::new(3, 0),
7745 primary: None,
7746 },
7747 ExcerptRange {
7748 context: Point::new(5, 0)..Point::new(7, 0),
7749 primary: None,
7750 },
7751 ExcerptRange {
7752 context: Point::new(9, 0)..Point::new(10, 4),
7753 primary: None,
7754 },
7755 ],
7756 cx,
7757 );
7758 multi_buffer
7759 });
7760 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7761 Editor::new(
7762 EditorMode::Full,
7763 multi_buffer,
7764 Some(project.clone()),
7765 window,
7766 cx,
7767 )
7768 });
7769
7770 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7771 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7772 s.select_ranges(Some(1..2))
7773 });
7774 editor.insert("|one|two|three|", window, cx);
7775 });
7776 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7777 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7778 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7779 s.select_ranges(Some(60..70))
7780 });
7781 editor.insert("|four|five|six|", window, cx);
7782 });
7783 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7784
7785 // First two buffers should be edited, but not the third one.
7786 assert_eq!(
7787 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7788 "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}",
7789 );
7790 buffer_1.update(cx, |buffer, _| {
7791 assert!(buffer.is_dirty());
7792 assert_eq!(
7793 buffer.text(),
7794 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7795 )
7796 });
7797 buffer_2.update(cx, |buffer, _| {
7798 assert!(buffer.is_dirty());
7799 assert_eq!(
7800 buffer.text(),
7801 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7802 )
7803 });
7804 buffer_3.update(cx, |buffer, _| {
7805 assert!(!buffer.is_dirty());
7806 assert_eq!(buffer.text(), sample_text_3,)
7807 });
7808 cx.executor().run_until_parked();
7809
7810 cx.executor().start_waiting();
7811 let save = multi_buffer_editor
7812 .update_in(cx, |editor, window, cx| {
7813 editor.save(true, project.clone(), window, cx)
7814 })
7815 .unwrap();
7816
7817 let fake_server = fake_servers.next().await.unwrap();
7818 fake_server
7819 .server
7820 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7821 Ok(Some(vec![lsp::TextEdit::new(
7822 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7823 format!("[{} formatted]", params.text_document.uri),
7824 )]))
7825 })
7826 .detach();
7827 save.await;
7828
7829 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7830 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7831 assert_eq!(
7832 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7833 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}"),
7834 );
7835 buffer_1.update(cx, |buffer, _| {
7836 assert!(!buffer.is_dirty());
7837 assert_eq!(
7838 buffer.text(),
7839 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7840 )
7841 });
7842 buffer_2.update(cx, |buffer, _| {
7843 assert!(!buffer.is_dirty());
7844 assert_eq!(
7845 buffer.text(),
7846 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7847 )
7848 });
7849 buffer_3.update(cx, |buffer, _| {
7850 assert!(!buffer.is_dirty());
7851 assert_eq!(buffer.text(), sample_text_3,)
7852 });
7853}
7854
7855#[gpui::test]
7856async fn test_range_format_during_save(cx: &mut TestAppContext) {
7857 init_test(cx, |_| {});
7858
7859 let fs = FakeFs::new(cx.executor());
7860 fs.insert_file(path!("/file.rs"), Default::default()).await;
7861
7862 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7863
7864 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7865 language_registry.add(rust_lang());
7866 let mut fake_servers = language_registry.register_fake_lsp(
7867 "Rust",
7868 FakeLspAdapter {
7869 capabilities: lsp::ServerCapabilities {
7870 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7871 ..Default::default()
7872 },
7873 ..Default::default()
7874 },
7875 );
7876
7877 let buffer = project
7878 .update(cx, |project, cx| {
7879 project.open_local_buffer(path!("/file.rs"), cx)
7880 })
7881 .await
7882 .unwrap();
7883
7884 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7885 let (editor, cx) = cx.add_window_view(|window, cx| {
7886 build_editor_with_project(project.clone(), buffer, window, cx)
7887 });
7888 editor.update_in(cx, |editor, window, cx| {
7889 editor.set_text("one\ntwo\nthree\n", window, cx)
7890 });
7891 assert!(cx.read(|cx| editor.is_dirty(cx)));
7892
7893 cx.executor().start_waiting();
7894 let fake_server = fake_servers.next().await.unwrap();
7895
7896 let save = editor
7897 .update_in(cx, |editor, window, cx| {
7898 editor.save(true, project.clone(), window, cx)
7899 })
7900 .unwrap();
7901 fake_server
7902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7903 assert_eq!(
7904 params.text_document.uri,
7905 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7906 );
7907 assert_eq!(params.options.tab_size, 4);
7908 Ok(Some(vec![lsp::TextEdit::new(
7909 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7910 ", ".to_string(),
7911 )]))
7912 })
7913 .next()
7914 .await;
7915 cx.executor().start_waiting();
7916 save.await;
7917 assert_eq!(
7918 editor.update(cx, |editor, cx| editor.text(cx)),
7919 "one, two\nthree\n"
7920 );
7921 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7922
7923 editor.update_in(cx, |editor, window, cx| {
7924 editor.set_text("one\ntwo\nthree\n", window, cx)
7925 });
7926 assert!(cx.read(|cx| editor.is_dirty(cx)));
7927
7928 // Ensure we can still save even if formatting hangs.
7929 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7930 move |params, _| async move {
7931 assert_eq!(
7932 params.text_document.uri,
7933 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7934 );
7935 futures::future::pending::<()>().await;
7936 unreachable!()
7937 },
7938 );
7939 let save = editor
7940 .update_in(cx, |editor, window, cx| {
7941 editor.save(true, project.clone(), window, cx)
7942 })
7943 .unwrap();
7944 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7945 cx.executor().start_waiting();
7946 save.await;
7947 assert_eq!(
7948 editor.update(cx, |editor, cx| editor.text(cx)),
7949 "one\ntwo\nthree\n"
7950 );
7951 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7952
7953 // For non-dirty buffer, no formatting request should be sent
7954 let save = editor
7955 .update_in(cx, |editor, window, cx| {
7956 editor.save(true, project.clone(), window, cx)
7957 })
7958 .unwrap();
7959 let _pending_format_request = fake_server
7960 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7961 panic!("Should not be invoked on non-dirty buffer");
7962 })
7963 .next();
7964 cx.executor().start_waiting();
7965 save.await;
7966
7967 // Set Rust language override and assert overridden tabsize is sent to language server
7968 update_test_language_settings(cx, |settings| {
7969 settings.languages.insert(
7970 "Rust".into(),
7971 LanguageSettingsContent {
7972 tab_size: NonZeroU32::new(8),
7973 ..Default::default()
7974 },
7975 );
7976 });
7977
7978 editor.update_in(cx, |editor, window, cx| {
7979 editor.set_text("somehting_new\n", window, cx)
7980 });
7981 assert!(cx.read(|cx| editor.is_dirty(cx)));
7982 let save = editor
7983 .update_in(cx, |editor, window, cx| {
7984 editor.save(true, project.clone(), window, cx)
7985 })
7986 .unwrap();
7987 fake_server
7988 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7989 assert_eq!(
7990 params.text_document.uri,
7991 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7992 );
7993 assert_eq!(params.options.tab_size, 8);
7994 Ok(Some(vec![]))
7995 })
7996 .next()
7997 .await;
7998 cx.executor().start_waiting();
7999 save.await;
8000}
8001
8002#[gpui::test]
8003async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
8004 init_test(cx, |settings| {
8005 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8006 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8007 ))
8008 });
8009
8010 let fs = FakeFs::new(cx.executor());
8011 fs.insert_file(path!("/file.rs"), Default::default()).await;
8012
8013 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8014
8015 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8016 language_registry.add(Arc::new(Language::new(
8017 LanguageConfig {
8018 name: "Rust".into(),
8019 matcher: LanguageMatcher {
8020 path_suffixes: vec!["rs".to_string()],
8021 ..Default::default()
8022 },
8023 ..LanguageConfig::default()
8024 },
8025 Some(tree_sitter_rust::LANGUAGE.into()),
8026 )));
8027 update_test_language_settings(cx, |settings| {
8028 // Enable Prettier formatting for the same buffer, and ensure
8029 // LSP is called instead of Prettier.
8030 settings.defaults.prettier = Some(PrettierSettings {
8031 allowed: true,
8032 ..PrettierSettings::default()
8033 });
8034 });
8035 let mut fake_servers = language_registry.register_fake_lsp(
8036 "Rust",
8037 FakeLspAdapter {
8038 capabilities: lsp::ServerCapabilities {
8039 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8040 ..Default::default()
8041 },
8042 ..Default::default()
8043 },
8044 );
8045
8046 let buffer = project
8047 .update(cx, |project, cx| {
8048 project.open_local_buffer(path!("/file.rs"), cx)
8049 })
8050 .await
8051 .unwrap();
8052
8053 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8054 let (editor, cx) = cx.add_window_view(|window, cx| {
8055 build_editor_with_project(project.clone(), buffer, window, cx)
8056 });
8057 editor.update_in(cx, |editor, window, cx| {
8058 editor.set_text("one\ntwo\nthree\n", window, cx)
8059 });
8060
8061 cx.executor().start_waiting();
8062 let fake_server = fake_servers.next().await.unwrap();
8063
8064 let format = editor
8065 .update_in(cx, |editor, window, cx| {
8066 editor.perform_format(
8067 project.clone(),
8068 FormatTrigger::Manual,
8069 FormatTarget::Buffers,
8070 window,
8071 cx,
8072 )
8073 })
8074 .unwrap();
8075 fake_server
8076 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8077 assert_eq!(
8078 params.text_document.uri,
8079 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8080 );
8081 assert_eq!(params.options.tab_size, 4);
8082 Ok(Some(vec![lsp::TextEdit::new(
8083 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8084 ", ".to_string(),
8085 )]))
8086 })
8087 .next()
8088 .await;
8089 cx.executor().start_waiting();
8090 format.await;
8091 assert_eq!(
8092 editor.update(cx, |editor, cx| editor.text(cx)),
8093 "one, two\nthree\n"
8094 );
8095
8096 editor.update_in(cx, |editor, window, cx| {
8097 editor.set_text("one\ntwo\nthree\n", window, cx)
8098 });
8099 // Ensure we don't lock if formatting hangs.
8100 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8101 assert_eq!(
8102 params.text_document.uri,
8103 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8104 );
8105 futures::future::pending::<()>().await;
8106 unreachable!()
8107 });
8108 let format = editor
8109 .update_in(cx, |editor, window, cx| {
8110 editor.perform_format(
8111 project,
8112 FormatTrigger::Manual,
8113 FormatTarget::Buffers,
8114 window,
8115 cx,
8116 )
8117 })
8118 .unwrap();
8119 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8120 cx.executor().start_waiting();
8121 format.await;
8122 assert_eq!(
8123 editor.update(cx, |editor, cx| editor.text(cx)),
8124 "one\ntwo\nthree\n"
8125 );
8126}
8127
8128#[gpui::test]
8129async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8130 init_test(cx, |settings| {
8131 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8132 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8133 ))
8134 });
8135
8136 let fs = FakeFs::new(cx.executor());
8137 fs.insert_file(path!("/file.ts"), Default::default()).await;
8138
8139 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8140
8141 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8142 language_registry.add(Arc::new(Language::new(
8143 LanguageConfig {
8144 name: "TypeScript".into(),
8145 matcher: LanguageMatcher {
8146 path_suffixes: vec!["ts".to_string()],
8147 ..Default::default()
8148 },
8149 ..LanguageConfig::default()
8150 },
8151 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8152 )));
8153 update_test_language_settings(cx, |settings| {
8154 settings.defaults.prettier = Some(PrettierSettings {
8155 allowed: true,
8156 ..PrettierSettings::default()
8157 });
8158 });
8159 let mut fake_servers = language_registry.register_fake_lsp(
8160 "TypeScript",
8161 FakeLspAdapter {
8162 capabilities: lsp::ServerCapabilities {
8163 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8164 ..Default::default()
8165 },
8166 ..Default::default()
8167 },
8168 );
8169
8170 let buffer = project
8171 .update(cx, |project, cx| {
8172 project.open_local_buffer(path!("/file.ts"), cx)
8173 })
8174 .await
8175 .unwrap();
8176
8177 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8178 let (editor, cx) = cx.add_window_view(|window, cx| {
8179 build_editor_with_project(project.clone(), buffer, window, cx)
8180 });
8181 editor.update_in(cx, |editor, window, cx| {
8182 editor.set_text(
8183 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8184 window,
8185 cx,
8186 )
8187 });
8188
8189 cx.executor().start_waiting();
8190 let fake_server = fake_servers.next().await.unwrap();
8191
8192 let format = editor
8193 .update_in(cx, |editor, window, cx| {
8194 editor.perform_code_action_kind(
8195 project.clone(),
8196 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8197 window,
8198 cx,
8199 )
8200 })
8201 .unwrap();
8202 fake_server
8203 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8204 assert_eq!(
8205 params.text_document.uri,
8206 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8207 );
8208 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8209 lsp::CodeAction {
8210 title: "Organize Imports".to_string(),
8211 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8212 edit: Some(lsp::WorkspaceEdit {
8213 changes: Some(
8214 [(
8215 params.text_document.uri.clone(),
8216 vec![lsp::TextEdit::new(
8217 lsp::Range::new(
8218 lsp::Position::new(1, 0),
8219 lsp::Position::new(2, 0),
8220 ),
8221 "".to_string(),
8222 )],
8223 )]
8224 .into_iter()
8225 .collect(),
8226 ),
8227 ..Default::default()
8228 }),
8229 ..Default::default()
8230 },
8231 )]))
8232 })
8233 .next()
8234 .await;
8235 cx.executor().start_waiting();
8236 format.await;
8237 assert_eq!(
8238 editor.update(cx, |editor, cx| editor.text(cx)),
8239 "import { a } from 'module';\n\nconst x = a;\n"
8240 );
8241
8242 editor.update_in(cx, |editor, window, cx| {
8243 editor.set_text(
8244 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8245 window,
8246 cx,
8247 )
8248 });
8249 // Ensure we don't lock if code action hangs.
8250 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8251 move |params, _| async move {
8252 assert_eq!(
8253 params.text_document.uri,
8254 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8255 );
8256 futures::future::pending::<()>().await;
8257 unreachable!()
8258 },
8259 );
8260 let format = editor
8261 .update_in(cx, |editor, window, cx| {
8262 editor.perform_code_action_kind(
8263 project,
8264 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8265 window,
8266 cx,
8267 )
8268 })
8269 .unwrap();
8270 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8271 cx.executor().start_waiting();
8272 format.await;
8273 assert_eq!(
8274 editor.update(cx, |editor, cx| editor.text(cx)),
8275 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8276 );
8277}
8278
8279#[gpui::test]
8280async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8281 init_test(cx, |_| {});
8282
8283 let mut cx = EditorLspTestContext::new_rust(
8284 lsp::ServerCapabilities {
8285 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8286 ..Default::default()
8287 },
8288 cx,
8289 )
8290 .await;
8291
8292 cx.set_state(indoc! {"
8293 one.twoˇ
8294 "});
8295
8296 // The format request takes a long time. When it completes, it inserts
8297 // a newline and an indent before the `.`
8298 cx.lsp
8299 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8300 let executor = cx.background_executor().clone();
8301 async move {
8302 executor.timer(Duration::from_millis(100)).await;
8303 Ok(Some(vec![lsp::TextEdit {
8304 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8305 new_text: "\n ".into(),
8306 }]))
8307 }
8308 });
8309
8310 // Submit a format request.
8311 let format_1 = cx
8312 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8313 .unwrap();
8314 cx.executor().run_until_parked();
8315
8316 // Submit a second format request.
8317 let format_2 = cx
8318 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8319 .unwrap();
8320 cx.executor().run_until_parked();
8321
8322 // Wait for both format requests to complete
8323 cx.executor().advance_clock(Duration::from_millis(200));
8324 cx.executor().start_waiting();
8325 format_1.await.unwrap();
8326 cx.executor().start_waiting();
8327 format_2.await.unwrap();
8328
8329 // The formatting edits only happens once.
8330 cx.assert_editor_state(indoc! {"
8331 one
8332 .twoˇ
8333 "});
8334}
8335
8336#[gpui::test]
8337async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8338 init_test(cx, |settings| {
8339 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8340 });
8341
8342 let mut cx = EditorLspTestContext::new_rust(
8343 lsp::ServerCapabilities {
8344 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8345 ..Default::default()
8346 },
8347 cx,
8348 )
8349 .await;
8350
8351 // Set up a buffer white some trailing whitespace and no trailing newline.
8352 cx.set_state(
8353 &[
8354 "one ", //
8355 "twoˇ", //
8356 "three ", //
8357 "four", //
8358 ]
8359 .join("\n"),
8360 );
8361
8362 // Submit a format request.
8363 let format = cx
8364 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8365 .unwrap();
8366
8367 // Record which buffer changes have been sent to the language server
8368 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8369 cx.lsp
8370 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8371 let buffer_changes = buffer_changes.clone();
8372 move |params, _| {
8373 buffer_changes.lock().extend(
8374 params
8375 .content_changes
8376 .into_iter()
8377 .map(|e| (e.range.unwrap(), e.text)),
8378 );
8379 }
8380 });
8381
8382 // Handle formatting requests to the language server.
8383 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8384 let buffer_changes = buffer_changes.clone();
8385 move |_, _| {
8386 // When formatting is requested, trailing whitespace has already been stripped,
8387 // and the trailing newline has already been added.
8388 assert_eq!(
8389 &buffer_changes.lock()[1..],
8390 &[
8391 (
8392 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8393 "".into()
8394 ),
8395 (
8396 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8397 "".into()
8398 ),
8399 (
8400 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8401 "\n".into()
8402 ),
8403 ]
8404 );
8405
8406 // Insert blank lines between each line of the buffer.
8407 async move {
8408 Ok(Some(vec![
8409 lsp::TextEdit {
8410 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8411 new_text: "\n".into(),
8412 },
8413 lsp::TextEdit {
8414 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8415 new_text: "\n".into(),
8416 },
8417 ]))
8418 }
8419 }
8420 });
8421
8422 // After formatting the buffer, the trailing whitespace is stripped,
8423 // a newline is appended, and the edits provided by the language server
8424 // have been applied.
8425 format.await.unwrap();
8426 cx.assert_editor_state(
8427 &[
8428 "one", //
8429 "", //
8430 "twoˇ", //
8431 "", //
8432 "three", //
8433 "four", //
8434 "", //
8435 ]
8436 .join("\n"),
8437 );
8438
8439 // Undoing the formatting undoes the trailing whitespace removal, the
8440 // trailing newline, and the LSP edits.
8441 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8442 cx.assert_editor_state(
8443 &[
8444 "one ", //
8445 "twoˇ", //
8446 "three ", //
8447 "four", //
8448 ]
8449 .join("\n"),
8450 );
8451}
8452
8453#[gpui::test]
8454async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8455 cx: &mut TestAppContext,
8456) {
8457 init_test(cx, |_| {});
8458
8459 cx.update(|cx| {
8460 cx.update_global::<SettingsStore, _>(|settings, cx| {
8461 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8462 settings.auto_signature_help = Some(true);
8463 });
8464 });
8465 });
8466
8467 let mut cx = EditorLspTestContext::new_rust(
8468 lsp::ServerCapabilities {
8469 signature_help_provider: Some(lsp::SignatureHelpOptions {
8470 ..Default::default()
8471 }),
8472 ..Default::default()
8473 },
8474 cx,
8475 )
8476 .await;
8477
8478 let language = Language::new(
8479 LanguageConfig {
8480 name: "Rust".into(),
8481 brackets: BracketPairConfig {
8482 pairs: vec![
8483 BracketPair {
8484 start: "{".to_string(),
8485 end: "}".to_string(),
8486 close: true,
8487 surround: true,
8488 newline: true,
8489 },
8490 BracketPair {
8491 start: "(".to_string(),
8492 end: ")".to_string(),
8493 close: true,
8494 surround: true,
8495 newline: true,
8496 },
8497 BracketPair {
8498 start: "/*".to_string(),
8499 end: " */".to_string(),
8500 close: true,
8501 surround: true,
8502 newline: true,
8503 },
8504 BracketPair {
8505 start: "[".to_string(),
8506 end: "]".to_string(),
8507 close: false,
8508 surround: false,
8509 newline: true,
8510 },
8511 BracketPair {
8512 start: "\"".to_string(),
8513 end: "\"".to_string(),
8514 close: true,
8515 surround: true,
8516 newline: false,
8517 },
8518 BracketPair {
8519 start: "<".to_string(),
8520 end: ">".to_string(),
8521 close: false,
8522 surround: true,
8523 newline: true,
8524 },
8525 ],
8526 ..Default::default()
8527 },
8528 autoclose_before: "})]".to_string(),
8529 ..Default::default()
8530 },
8531 Some(tree_sitter_rust::LANGUAGE.into()),
8532 );
8533 let language = Arc::new(language);
8534
8535 cx.language_registry().add(language.clone());
8536 cx.update_buffer(|buffer, cx| {
8537 buffer.set_language(Some(language), cx);
8538 });
8539
8540 cx.set_state(
8541 &r#"
8542 fn main() {
8543 sampleˇ
8544 }
8545 "#
8546 .unindent(),
8547 );
8548
8549 cx.update_editor(|editor, window, cx| {
8550 editor.handle_input("(", window, cx);
8551 });
8552 cx.assert_editor_state(
8553 &"
8554 fn main() {
8555 sample(ˇ)
8556 }
8557 "
8558 .unindent(),
8559 );
8560
8561 let mocked_response = lsp::SignatureHelp {
8562 signatures: vec![lsp::SignatureInformation {
8563 label: "fn sample(param1: u8, param2: u8)".to_string(),
8564 documentation: None,
8565 parameters: Some(vec![
8566 lsp::ParameterInformation {
8567 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8568 documentation: None,
8569 },
8570 lsp::ParameterInformation {
8571 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8572 documentation: None,
8573 },
8574 ]),
8575 active_parameter: None,
8576 }],
8577 active_signature: Some(0),
8578 active_parameter: Some(0),
8579 };
8580 handle_signature_help_request(&mut cx, mocked_response).await;
8581
8582 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8583 .await;
8584
8585 cx.editor(|editor, _, _| {
8586 let signature_help_state = editor.signature_help_state.popover().cloned();
8587 assert_eq!(
8588 signature_help_state.unwrap().label,
8589 "param1: u8, param2: u8"
8590 );
8591 });
8592}
8593
8594#[gpui::test]
8595async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8596 init_test(cx, |_| {});
8597
8598 cx.update(|cx| {
8599 cx.update_global::<SettingsStore, _>(|settings, cx| {
8600 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8601 settings.auto_signature_help = Some(false);
8602 settings.show_signature_help_after_edits = Some(false);
8603 });
8604 });
8605 });
8606
8607 let mut cx = EditorLspTestContext::new_rust(
8608 lsp::ServerCapabilities {
8609 signature_help_provider: Some(lsp::SignatureHelpOptions {
8610 ..Default::default()
8611 }),
8612 ..Default::default()
8613 },
8614 cx,
8615 )
8616 .await;
8617
8618 let language = Language::new(
8619 LanguageConfig {
8620 name: "Rust".into(),
8621 brackets: BracketPairConfig {
8622 pairs: vec![
8623 BracketPair {
8624 start: "{".to_string(),
8625 end: "}".to_string(),
8626 close: true,
8627 surround: true,
8628 newline: true,
8629 },
8630 BracketPair {
8631 start: "(".to_string(),
8632 end: ")".to_string(),
8633 close: true,
8634 surround: true,
8635 newline: true,
8636 },
8637 BracketPair {
8638 start: "/*".to_string(),
8639 end: " */".to_string(),
8640 close: true,
8641 surround: true,
8642 newline: true,
8643 },
8644 BracketPair {
8645 start: "[".to_string(),
8646 end: "]".to_string(),
8647 close: false,
8648 surround: false,
8649 newline: true,
8650 },
8651 BracketPair {
8652 start: "\"".to_string(),
8653 end: "\"".to_string(),
8654 close: true,
8655 surround: true,
8656 newline: false,
8657 },
8658 BracketPair {
8659 start: "<".to_string(),
8660 end: ">".to_string(),
8661 close: false,
8662 surround: true,
8663 newline: true,
8664 },
8665 ],
8666 ..Default::default()
8667 },
8668 autoclose_before: "})]".to_string(),
8669 ..Default::default()
8670 },
8671 Some(tree_sitter_rust::LANGUAGE.into()),
8672 );
8673 let language = Arc::new(language);
8674
8675 cx.language_registry().add(language.clone());
8676 cx.update_buffer(|buffer, cx| {
8677 buffer.set_language(Some(language), cx);
8678 });
8679
8680 // Ensure that signature_help is not called when no signature help is enabled.
8681 cx.set_state(
8682 &r#"
8683 fn main() {
8684 sampleˇ
8685 }
8686 "#
8687 .unindent(),
8688 );
8689 cx.update_editor(|editor, window, cx| {
8690 editor.handle_input("(", window, cx);
8691 });
8692 cx.assert_editor_state(
8693 &"
8694 fn main() {
8695 sample(ˇ)
8696 }
8697 "
8698 .unindent(),
8699 );
8700 cx.editor(|editor, _, _| {
8701 assert!(editor.signature_help_state.task().is_none());
8702 });
8703
8704 let mocked_response = lsp::SignatureHelp {
8705 signatures: vec![lsp::SignatureInformation {
8706 label: "fn sample(param1: u8, param2: u8)".to_string(),
8707 documentation: None,
8708 parameters: Some(vec![
8709 lsp::ParameterInformation {
8710 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8711 documentation: None,
8712 },
8713 lsp::ParameterInformation {
8714 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8715 documentation: None,
8716 },
8717 ]),
8718 active_parameter: None,
8719 }],
8720 active_signature: Some(0),
8721 active_parameter: Some(0),
8722 };
8723
8724 // Ensure that signature_help is called when enabled afte edits
8725 cx.update(|_, cx| {
8726 cx.update_global::<SettingsStore, _>(|settings, cx| {
8727 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8728 settings.auto_signature_help = Some(false);
8729 settings.show_signature_help_after_edits = Some(true);
8730 });
8731 });
8732 });
8733 cx.set_state(
8734 &r#"
8735 fn main() {
8736 sampleˇ
8737 }
8738 "#
8739 .unindent(),
8740 );
8741 cx.update_editor(|editor, window, cx| {
8742 editor.handle_input("(", window, cx);
8743 });
8744 cx.assert_editor_state(
8745 &"
8746 fn main() {
8747 sample(ˇ)
8748 }
8749 "
8750 .unindent(),
8751 );
8752 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8753 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8754 .await;
8755 cx.update_editor(|editor, _, _| {
8756 let signature_help_state = editor.signature_help_state.popover().cloned();
8757 assert!(signature_help_state.is_some());
8758 assert_eq!(
8759 signature_help_state.unwrap().label,
8760 "param1: u8, param2: u8"
8761 );
8762 editor.signature_help_state = SignatureHelpState::default();
8763 });
8764
8765 // Ensure that signature_help is called when auto signature help override is enabled
8766 cx.update(|_, cx| {
8767 cx.update_global::<SettingsStore, _>(|settings, cx| {
8768 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8769 settings.auto_signature_help = Some(true);
8770 settings.show_signature_help_after_edits = Some(false);
8771 });
8772 });
8773 });
8774 cx.set_state(
8775 &r#"
8776 fn main() {
8777 sampleˇ
8778 }
8779 "#
8780 .unindent(),
8781 );
8782 cx.update_editor(|editor, window, cx| {
8783 editor.handle_input("(", window, cx);
8784 });
8785 cx.assert_editor_state(
8786 &"
8787 fn main() {
8788 sample(ˇ)
8789 }
8790 "
8791 .unindent(),
8792 );
8793 handle_signature_help_request(&mut cx, mocked_response).await;
8794 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8795 .await;
8796 cx.editor(|editor, _, _| {
8797 let signature_help_state = editor.signature_help_state.popover().cloned();
8798 assert!(signature_help_state.is_some());
8799 assert_eq!(
8800 signature_help_state.unwrap().label,
8801 "param1: u8, param2: u8"
8802 );
8803 });
8804}
8805
8806#[gpui::test]
8807async fn test_signature_help(cx: &mut TestAppContext) {
8808 init_test(cx, |_| {});
8809 cx.update(|cx| {
8810 cx.update_global::<SettingsStore, _>(|settings, cx| {
8811 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8812 settings.auto_signature_help = Some(true);
8813 });
8814 });
8815 });
8816
8817 let mut cx = EditorLspTestContext::new_rust(
8818 lsp::ServerCapabilities {
8819 signature_help_provider: Some(lsp::SignatureHelpOptions {
8820 ..Default::default()
8821 }),
8822 ..Default::default()
8823 },
8824 cx,
8825 )
8826 .await;
8827
8828 // A test that directly calls `show_signature_help`
8829 cx.update_editor(|editor, window, cx| {
8830 editor.show_signature_help(&ShowSignatureHelp, window, cx);
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).await;
8853
8854 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8855 .await;
8856
8857 cx.editor(|editor, _, _| {
8858 let signature_help_state = editor.signature_help_state.popover().cloned();
8859 assert!(signature_help_state.is_some());
8860 assert_eq!(
8861 signature_help_state.unwrap().label,
8862 "param1: u8, param2: u8"
8863 );
8864 });
8865
8866 // When exiting outside from inside the brackets, `signature_help` is closed.
8867 cx.set_state(indoc! {"
8868 fn main() {
8869 sample(ˇ);
8870 }
8871
8872 fn sample(param1: u8, param2: u8) {}
8873 "});
8874
8875 cx.update_editor(|editor, window, cx| {
8876 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8877 });
8878
8879 let mocked_response = lsp::SignatureHelp {
8880 signatures: Vec::new(),
8881 active_signature: None,
8882 active_parameter: None,
8883 };
8884 handle_signature_help_request(&mut cx, mocked_response).await;
8885
8886 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8887 .await;
8888
8889 cx.editor(|editor, _, _| {
8890 assert!(!editor.signature_help_state.is_shown());
8891 });
8892
8893 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8894 cx.set_state(indoc! {"
8895 fn main() {
8896 sample(ˇ);
8897 }
8898
8899 fn sample(param1: u8, param2: u8) {}
8900 "});
8901
8902 let mocked_response = lsp::SignatureHelp {
8903 signatures: vec![lsp::SignatureInformation {
8904 label: "fn sample(param1: u8, param2: u8)".to_string(),
8905 documentation: None,
8906 parameters: Some(vec![
8907 lsp::ParameterInformation {
8908 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8909 documentation: None,
8910 },
8911 lsp::ParameterInformation {
8912 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8913 documentation: None,
8914 },
8915 ]),
8916 active_parameter: None,
8917 }],
8918 active_signature: Some(0),
8919 active_parameter: Some(0),
8920 };
8921 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8922 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8923 .await;
8924 cx.editor(|editor, _, _| {
8925 assert!(editor.signature_help_state.is_shown());
8926 });
8927
8928 // Restore the popover with more parameter input
8929 cx.set_state(indoc! {"
8930 fn main() {
8931 sample(param1, param2ˇ);
8932 }
8933
8934 fn sample(param1: u8, param2: u8) {}
8935 "});
8936
8937 let mocked_response = lsp::SignatureHelp {
8938 signatures: vec![lsp::SignatureInformation {
8939 label: "fn sample(param1: u8, param2: u8)".to_string(),
8940 documentation: None,
8941 parameters: Some(vec![
8942 lsp::ParameterInformation {
8943 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8944 documentation: None,
8945 },
8946 lsp::ParameterInformation {
8947 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8948 documentation: None,
8949 },
8950 ]),
8951 active_parameter: None,
8952 }],
8953 active_signature: Some(0),
8954 active_parameter: Some(1),
8955 };
8956 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8957 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8958 .await;
8959
8960 // When selecting a range, the popover is gone.
8961 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8962 cx.update_editor(|editor, window, cx| {
8963 editor.change_selections(None, window, cx, |s| {
8964 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8965 })
8966 });
8967 cx.assert_editor_state(indoc! {"
8968 fn main() {
8969 sample(param1, «ˇparam2»);
8970 }
8971
8972 fn sample(param1: u8, param2: u8) {}
8973 "});
8974 cx.editor(|editor, _, _| {
8975 assert!(!editor.signature_help_state.is_shown());
8976 });
8977
8978 // When unselecting again, the popover is back if within the brackets.
8979 cx.update_editor(|editor, window, cx| {
8980 editor.change_selections(None, window, cx, |s| {
8981 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8982 })
8983 });
8984 cx.assert_editor_state(indoc! {"
8985 fn main() {
8986 sample(param1, ˇparam2);
8987 }
8988
8989 fn sample(param1: u8, param2: u8) {}
8990 "});
8991 handle_signature_help_request(&mut cx, mocked_response).await;
8992 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8993 .await;
8994 cx.editor(|editor, _, _| {
8995 assert!(editor.signature_help_state.is_shown());
8996 });
8997
8998 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8999 cx.update_editor(|editor, window, cx| {
9000 editor.change_selections(None, window, cx, |s| {
9001 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
9002 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9003 })
9004 });
9005 cx.assert_editor_state(indoc! {"
9006 fn main() {
9007 sample(param1, ˇparam2);
9008 }
9009
9010 fn sample(param1: u8, param2: u8) {}
9011 "});
9012
9013 let mocked_response = lsp::SignatureHelp {
9014 signatures: vec![lsp::SignatureInformation {
9015 label: "fn sample(param1: u8, param2: u8)".to_string(),
9016 documentation: None,
9017 parameters: Some(vec![
9018 lsp::ParameterInformation {
9019 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
9020 documentation: None,
9021 },
9022 lsp::ParameterInformation {
9023 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
9024 documentation: None,
9025 },
9026 ]),
9027 active_parameter: None,
9028 }],
9029 active_signature: Some(0),
9030 active_parameter: Some(1),
9031 };
9032 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
9033 cx.condition(|editor, _| editor.signature_help_state.is_shown())
9034 .await;
9035 cx.update_editor(|editor, _, cx| {
9036 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
9037 });
9038 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
9039 .await;
9040 cx.update_editor(|editor, window, cx| {
9041 editor.change_selections(None, window, cx, |s| {
9042 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
9043 })
9044 });
9045 cx.assert_editor_state(indoc! {"
9046 fn main() {
9047 sample(param1, «ˇparam2»);
9048 }
9049
9050 fn sample(param1: u8, param2: u8) {}
9051 "});
9052 cx.update_editor(|editor, window, cx| {
9053 editor.change_selections(None, window, cx, |s| {
9054 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
9055 })
9056 });
9057 cx.assert_editor_state(indoc! {"
9058 fn main() {
9059 sample(param1, ˇparam2);
9060 }
9061
9062 fn sample(param1: u8, param2: u8) {}
9063 "});
9064 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
9065 .await;
9066}
9067
9068#[gpui::test]
9069async fn test_completion(cx: &mut TestAppContext) {
9070 init_test(cx, |_| {});
9071
9072 let mut cx = EditorLspTestContext::new_rust(
9073 lsp::ServerCapabilities {
9074 completion_provider: Some(lsp::CompletionOptions {
9075 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9076 resolve_provider: Some(true),
9077 ..Default::default()
9078 }),
9079 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9080 ..Default::default()
9081 },
9082 cx,
9083 )
9084 .await;
9085 let counter = Arc::new(AtomicUsize::new(0));
9086
9087 cx.set_state(indoc! {"
9088 oneˇ
9089 two
9090 three
9091 "});
9092 cx.simulate_keystroke(".");
9093 handle_completion_request(
9094 &mut cx,
9095 indoc! {"
9096 one.|<>
9097 two
9098 three
9099 "},
9100 vec!["first_completion", "second_completion"],
9101 counter.clone(),
9102 )
9103 .await;
9104 cx.condition(|editor, _| editor.context_menu_visible())
9105 .await;
9106 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9107
9108 let _handler = handle_signature_help_request(
9109 &mut cx,
9110 lsp::SignatureHelp {
9111 signatures: vec![lsp::SignatureInformation {
9112 label: "test signature".to_string(),
9113 documentation: None,
9114 parameters: Some(vec![lsp::ParameterInformation {
9115 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9116 documentation: None,
9117 }]),
9118 active_parameter: None,
9119 }],
9120 active_signature: None,
9121 active_parameter: None,
9122 },
9123 );
9124 cx.update_editor(|editor, window, cx| {
9125 assert!(
9126 !editor.signature_help_state.is_shown(),
9127 "No signature help was called for"
9128 );
9129 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9130 });
9131 cx.run_until_parked();
9132 cx.update_editor(|editor, _, _| {
9133 assert!(
9134 !editor.signature_help_state.is_shown(),
9135 "No signature help should be shown when completions menu is open"
9136 );
9137 });
9138
9139 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9140 editor.context_menu_next(&Default::default(), window, cx);
9141 editor
9142 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9143 .unwrap()
9144 });
9145 cx.assert_editor_state(indoc! {"
9146 one.second_completionˇ
9147 two
9148 three
9149 "});
9150
9151 handle_resolve_completion_request(
9152 &mut cx,
9153 Some(vec![
9154 (
9155 //This overlaps with the primary completion edit which is
9156 //misbehavior from the LSP spec, test that we filter it out
9157 indoc! {"
9158 one.second_ˇcompletion
9159 two
9160 threeˇ
9161 "},
9162 "overlapping additional edit",
9163 ),
9164 (
9165 indoc! {"
9166 one.second_completion
9167 two
9168 threeˇ
9169 "},
9170 "\nadditional edit",
9171 ),
9172 ]),
9173 )
9174 .await;
9175 apply_additional_edits.await.unwrap();
9176 cx.assert_editor_state(indoc! {"
9177 one.second_completionˇ
9178 two
9179 three
9180 additional edit
9181 "});
9182
9183 cx.set_state(indoc! {"
9184 one.second_completion
9185 twoˇ
9186 threeˇ
9187 additional edit
9188 "});
9189 cx.simulate_keystroke(" ");
9190 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9191 cx.simulate_keystroke("s");
9192 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9193
9194 cx.assert_editor_state(indoc! {"
9195 one.second_completion
9196 two sˇ
9197 three sˇ
9198 additional edit
9199 "});
9200 handle_completion_request(
9201 &mut cx,
9202 indoc! {"
9203 one.second_completion
9204 two s
9205 three <s|>
9206 additional edit
9207 "},
9208 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9209 counter.clone(),
9210 )
9211 .await;
9212 cx.condition(|editor, _| editor.context_menu_visible())
9213 .await;
9214 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9215
9216 cx.simulate_keystroke("i");
9217
9218 handle_completion_request(
9219 &mut cx,
9220 indoc! {"
9221 one.second_completion
9222 two si
9223 three <si|>
9224 additional edit
9225 "},
9226 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9227 counter.clone(),
9228 )
9229 .await;
9230 cx.condition(|editor, _| editor.context_menu_visible())
9231 .await;
9232 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9233
9234 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9235 editor
9236 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9237 .unwrap()
9238 });
9239 cx.assert_editor_state(indoc! {"
9240 one.second_completion
9241 two sixth_completionˇ
9242 three sixth_completionˇ
9243 additional edit
9244 "});
9245
9246 apply_additional_edits.await.unwrap();
9247
9248 update_test_language_settings(&mut cx, |settings| {
9249 settings.defaults.show_completions_on_input = Some(false);
9250 });
9251 cx.set_state("editorˇ");
9252 cx.simulate_keystroke(".");
9253 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9254 cx.simulate_keystroke("c");
9255 cx.simulate_keystroke("l");
9256 cx.simulate_keystroke("o");
9257 cx.assert_editor_state("editor.cloˇ");
9258 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9259 cx.update_editor(|editor, window, cx| {
9260 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9261 });
9262 handle_completion_request(
9263 &mut cx,
9264 "editor.<clo|>",
9265 vec!["close", "clobber"],
9266 counter.clone(),
9267 )
9268 .await;
9269 cx.condition(|editor, _| editor.context_menu_visible())
9270 .await;
9271 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9272
9273 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9274 editor
9275 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9276 .unwrap()
9277 });
9278 cx.assert_editor_state("editor.closeˇ");
9279 handle_resolve_completion_request(&mut cx, None).await;
9280 apply_additional_edits.await.unwrap();
9281}
9282
9283#[gpui::test]
9284async fn test_word_completion(cx: &mut TestAppContext) {
9285 let lsp_fetch_timeout_ms = 10;
9286 init_test(cx, |language_settings| {
9287 language_settings.defaults.completions = Some(CompletionSettings {
9288 words: WordsCompletionMode::Fallback,
9289 lsp: true,
9290 lsp_fetch_timeout_ms: 10,
9291 });
9292 });
9293
9294 let mut cx = EditorLspTestContext::new_rust(
9295 lsp::ServerCapabilities {
9296 completion_provider: Some(lsp::CompletionOptions {
9297 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9298 ..lsp::CompletionOptions::default()
9299 }),
9300 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9301 ..lsp::ServerCapabilities::default()
9302 },
9303 cx,
9304 )
9305 .await;
9306
9307 let throttle_completions = Arc::new(AtomicBool::new(false));
9308
9309 let lsp_throttle_completions = throttle_completions.clone();
9310 let _completion_requests_handler =
9311 cx.lsp
9312 .server
9313 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9314 let lsp_throttle_completions = lsp_throttle_completions.clone();
9315 let cx = cx.clone();
9316 async move {
9317 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9318 cx.background_executor()
9319 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9320 .await;
9321 }
9322 Ok(Some(lsp::CompletionResponse::Array(vec![
9323 lsp::CompletionItem {
9324 label: "first".into(),
9325 ..lsp::CompletionItem::default()
9326 },
9327 lsp::CompletionItem {
9328 label: "last".into(),
9329 ..lsp::CompletionItem::default()
9330 },
9331 ])))
9332 }
9333 });
9334
9335 cx.set_state(indoc! {"
9336 oneˇ
9337 two
9338 three
9339 "});
9340 cx.simulate_keystroke(".");
9341 cx.executor().run_until_parked();
9342 cx.condition(|editor, _| editor.context_menu_visible())
9343 .await;
9344 cx.update_editor(|editor, window, cx| {
9345 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9346 {
9347 assert_eq!(
9348 completion_menu_entries(&menu),
9349 &["first", "last"],
9350 "When LSP server is fast to reply, no fallback word completions are used"
9351 );
9352 } else {
9353 panic!("expected completion menu to be open");
9354 }
9355 editor.cancel(&Cancel, window, cx);
9356 });
9357 cx.executor().run_until_parked();
9358 cx.condition(|editor, _| !editor.context_menu_visible())
9359 .await;
9360
9361 throttle_completions.store(true, atomic::Ordering::Release);
9362 cx.simulate_keystroke(".");
9363 cx.executor()
9364 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9365 cx.executor().run_until_parked();
9366 cx.condition(|editor, _| editor.context_menu_visible())
9367 .await;
9368 cx.update_editor(|editor, _, _| {
9369 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9370 {
9371 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9372 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9373 } else {
9374 panic!("expected completion menu to be open");
9375 }
9376 });
9377}
9378
9379#[gpui::test]
9380async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9381 init_test(cx, |language_settings| {
9382 language_settings.defaults.completions = Some(CompletionSettings {
9383 words: WordsCompletionMode::Enabled,
9384 lsp: true,
9385 lsp_fetch_timeout_ms: 0,
9386 });
9387 });
9388
9389 let mut cx = EditorLspTestContext::new_rust(
9390 lsp::ServerCapabilities {
9391 completion_provider: Some(lsp::CompletionOptions {
9392 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9393 ..lsp::CompletionOptions::default()
9394 }),
9395 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9396 ..lsp::ServerCapabilities::default()
9397 },
9398 cx,
9399 )
9400 .await;
9401
9402 let _completion_requests_handler =
9403 cx.lsp
9404 .server
9405 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9406 Ok(Some(lsp::CompletionResponse::Array(vec![
9407 lsp::CompletionItem {
9408 label: "first".into(),
9409 ..lsp::CompletionItem::default()
9410 },
9411 lsp::CompletionItem {
9412 label: "last".into(),
9413 ..lsp::CompletionItem::default()
9414 },
9415 ])))
9416 });
9417
9418 cx.set_state(indoc! {"ˇ
9419 first
9420 last
9421 second
9422 "});
9423 cx.simulate_keystroke(".");
9424 cx.executor().run_until_parked();
9425 cx.condition(|editor, _| editor.context_menu_visible())
9426 .await;
9427 cx.update_editor(|editor, _, _| {
9428 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9429 {
9430 assert_eq!(
9431 completion_menu_entries(&menu),
9432 &["first", "last", "second"],
9433 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9434 );
9435 } else {
9436 panic!("expected completion menu to be open");
9437 }
9438 });
9439}
9440
9441#[gpui::test]
9442async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
9443 init_test(cx, |language_settings| {
9444 language_settings.defaults.completions = Some(CompletionSettings {
9445 words: WordsCompletionMode::Disabled,
9446 lsp: true,
9447 lsp_fetch_timeout_ms: 0,
9448 });
9449 });
9450
9451 let mut cx = EditorLspTestContext::new_rust(
9452 lsp::ServerCapabilities {
9453 completion_provider: Some(lsp::CompletionOptions {
9454 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9455 ..lsp::CompletionOptions::default()
9456 }),
9457 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9458 ..lsp::ServerCapabilities::default()
9459 },
9460 cx,
9461 )
9462 .await;
9463
9464 let _completion_requests_handler =
9465 cx.lsp
9466 .server
9467 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9468 panic!("LSP completions should not be queried when dealing with word completions")
9469 });
9470
9471 cx.set_state(indoc! {"ˇ
9472 first
9473 last
9474 second
9475 "});
9476 cx.update_editor(|editor, window, cx| {
9477 editor.show_word_completions(&ShowWordCompletions, window, cx);
9478 });
9479 cx.executor().run_until_parked();
9480 cx.condition(|editor, _| editor.context_menu_visible())
9481 .await;
9482 cx.update_editor(|editor, _, _| {
9483 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9484 {
9485 assert_eq!(
9486 completion_menu_entries(&menu),
9487 &["first", "last", "second"],
9488 "`ShowWordCompletions` action should show word completions"
9489 );
9490 } else {
9491 panic!("expected completion menu to be open");
9492 }
9493 });
9494
9495 cx.simulate_keystroke("s");
9496 cx.executor().run_until_parked();
9497 cx.condition(|editor, _| editor.context_menu_visible())
9498 .await;
9499 cx.update_editor(|editor, _, _| {
9500 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9501 {
9502 assert_eq!(
9503 completion_menu_entries(&menu),
9504 &["second"],
9505 "After showing word completions, further editing should filter them and not query the LSP"
9506 );
9507 } else {
9508 panic!("expected completion menu to be open");
9509 }
9510 });
9511}
9512
9513#[gpui::test]
9514async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
9515 init_test(cx, |language_settings| {
9516 language_settings.defaults.completions = Some(CompletionSettings {
9517 words: WordsCompletionMode::Fallback,
9518 lsp: false,
9519 lsp_fetch_timeout_ms: 0,
9520 });
9521 });
9522
9523 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9524
9525 cx.set_state(indoc! {"ˇ
9526 0_usize
9527 let
9528 33
9529 4.5f32
9530 "});
9531 cx.update_editor(|editor, window, cx| {
9532 editor.show_completions(&ShowCompletions::default(), window, cx);
9533 });
9534 cx.executor().run_until_parked();
9535 cx.condition(|editor, _| editor.context_menu_visible())
9536 .await;
9537 cx.update_editor(|editor, window, cx| {
9538 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9539 {
9540 assert_eq!(
9541 completion_menu_entries(&menu),
9542 &["let"],
9543 "With no digits in the completion query, no digits should be in the word completions"
9544 );
9545 } else {
9546 panic!("expected completion menu to be open");
9547 }
9548 editor.cancel(&Cancel, window, cx);
9549 });
9550
9551 cx.set_state(indoc! {"3ˇ
9552 0_usize
9553 let
9554 3
9555 33.35f32
9556 "});
9557 cx.update_editor(|editor, window, cx| {
9558 editor.show_completions(&ShowCompletions::default(), window, cx);
9559 });
9560 cx.executor().run_until_parked();
9561 cx.condition(|editor, _| editor.context_menu_visible())
9562 .await;
9563 cx.update_editor(|editor, _, _| {
9564 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9565 {
9566 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
9567 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
9568 } else {
9569 panic!("expected completion menu to be open");
9570 }
9571 });
9572}
9573
9574#[gpui::test]
9575async fn test_multiline_completion(cx: &mut TestAppContext) {
9576 init_test(cx, |_| {});
9577
9578 let fs = FakeFs::new(cx.executor());
9579 fs.insert_tree(
9580 path!("/a"),
9581 json!({
9582 "main.ts": "a",
9583 }),
9584 )
9585 .await;
9586
9587 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9588 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9589 let typescript_language = Arc::new(Language::new(
9590 LanguageConfig {
9591 name: "TypeScript".into(),
9592 matcher: LanguageMatcher {
9593 path_suffixes: vec!["ts".to_string()],
9594 ..LanguageMatcher::default()
9595 },
9596 line_comments: vec!["// ".into()],
9597 ..LanguageConfig::default()
9598 },
9599 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9600 ));
9601 language_registry.add(typescript_language.clone());
9602 let mut fake_servers = language_registry.register_fake_lsp(
9603 "TypeScript",
9604 FakeLspAdapter {
9605 capabilities: lsp::ServerCapabilities {
9606 completion_provider: Some(lsp::CompletionOptions {
9607 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9608 ..lsp::CompletionOptions::default()
9609 }),
9610 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9611 ..lsp::ServerCapabilities::default()
9612 },
9613 // Emulate vtsls label generation
9614 label_for_completion: Some(Box::new(|item, _| {
9615 let text = if let Some(description) = item
9616 .label_details
9617 .as_ref()
9618 .and_then(|label_details| label_details.description.as_ref())
9619 {
9620 format!("{} {}", item.label, description)
9621 } else if let Some(detail) = &item.detail {
9622 format!("{} {}", item.label, detail)
9623 } else {
9624 item.label.clone()
9625 };
9626 let len = text.len();
9627 Some(language::CodeLabel {
9628 text,
9629 runs: Vec::new(),
9630 filter_range: 0..len,
9631 })
9632 })),
9633 ..FakeLspAdapter::default()
9634 },
9635 );
9636 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9637 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9638 let worktree_id = workspace
9639 .update(cx, |workspace, _window, cx| {
9640 workspace.project().update(cx, |project, cx| {
9641 project.worktrees(cx).next().unwrap().read(cx).id()
9642 })
9643 })
9644 .unwrap();
9645 let _buffer = project
9646 .update(cx, |project, cx| {
9647 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9648 })
9649 .await
9650 .unwrap();
9651 let editor = workspace
9652 .update(cx, |workspace, window, cx| {
9653 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9654 })
9655 .unwrap()
9656 .await
9657 .unwrap()
9658 .downcast::<Editor>()
9659 .unwrap();
9660 let fake_server = fake_servers.next().await.unwrap();
9661
9662 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9663 let multiline_label_2 = "a\nb\nc\n";
9664 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9665 let multiline_description = "d\ne\nf\n";
9666 let multiline_detail_2 = "g\nh\ni\n";
9667
9668 let mut completion_handle =
9669 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9670 Ok(Some(lsp::CompletionResponse::Array(vec![
9671 lsp::CompletionItem {
9672 label: multiline_label.to_string(),
9673 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9674 range: lsp::Range {
9675 start: lsp::Position {
9676 line: params.text_document_position.position.line,
9677 character: params.text_document_position.position.character,
9678 },
9679 end: lsp::Position {
9680 line: params.text_document_position.position.line,
9681 character: params.text_document_position.position.character,
9682 },
9683 },
9684 new_text: "new_text_1".to_string(),
9685 })),
9686 ..lsp::CompletionItem::default()
9687 },
9688 lsp::CompletionItem {
9689 label: "single line label 1".to_string(),
9690 detail: Some(multiline_detail.to_string()),
9691 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9692 range: lsp::Range {
9693 start: lsp::Position {
9694 line: params.text_document_position.position.line,
9695 character: params.text_document_position.position.character,
9696 },
9697 end: lsp::Position {
9698 line: params.text_document_position.position.line,
9699 character: params.text_document_position.position.character,
9700 },
9701 },
9702 new_text: "new_text_2".to_string(),
9703 })),
9704 ..lsp::CompletionItem::default()
9705 },
9706 lsp::CompletionItem {
9707 label: "single line label 2".to_string(),
9708 label_details: Some(lsp::CompletionItemLabelDetails {
9709 description: Some(multiline_description.to_string()),
9710 detail: None,
9711 }),
9712 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9713 range: lsp::Range {
9714 start: lsp::Position {
9715 line: params.text_document_position.position.line,
9716 character: params.text_document_position.position.character,
9717 },
9718 end: lsp::Position {
9719 line: params.text_document_position.position.line,
9720 character: params.text_document_position.position.character,
9721 },
9722 },
9723 new_text: "new_text_2".to_string(),
9724 })),
9725 ..lsp::CompletionItem::default()
9726 },
9727 lsp::CompletionItem {
9728 label: multiline_label_2.to_string(),
9729 detail: Some(multiline_detail_2.to_string()),
9730 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9731 range: lsp::Range {
9732 start: lsp::Position {
9733 line: params.text_document_position.position.line,
9734 character: params.text_document_position.position.character,
9735 },
9736 end: lsp::Position {
9737 line: params.text_document_position.position.line,
9738 character: params.text_document_position.position.character,
9739 },
9740 },
9741 new_text: "new_text_3".to_string(),
9742 })),
9743 ..lsp::CompletionItem::default()
9744 },
9745 lsp::CompletionItem {
9746 label: "Label with many spaces and \t but without newlines".to_string(),
9747 detail: Some(
9748 "Details with many spaces and \t but without newlines".to_string(),
9749 ),
9750 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9751 range: lsp::Range {
9752 start: lsp::Position {
9753 line: params.text_document_position.position.line,
9754 character: params.text_document_position.position.character,
9755 },
9756 end: lsp::Position {
9757 line: params.text_document_position.position.line,
9758 character: params.text_document_position.position.character,
9759 },
9760 },
9761 new_text: "new_text_4".to_string(),
9762 })),
9763 ..lsp::CompletionItem::default()
9764 },
9765 ])))
9766 });
9767
9768 editor.update_in(cx, |editor, window, cx| {
9769 cx.focus_self(window);
9770 editor.move_to_end(&MoveToEnd, window, cx);
9771 editor.handle_input(".", window, cx);
9772 });
9773 cx.run_until_parked();
9774 completion_handle.next().await.unwrap();
9775
9776 editor.update(cx, |editor, _| {
9777 assert!(editor.context_menu_visible());
9778 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9779 {
9780 let completion_labels = menu
9781 .completions
9782 .borrow()
9783 .iter()
9784 .map(|c| c.label.text.clone())
9785 .collect::<Vec<_>>();
9786 assert_eq!(
9787 completion_labels,
9788 &[
9789 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9790 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9791 "single line label 2 d e f ",
9792 "a b c g h i ",
9793 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9794 ],
9795 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9796 );
9797
9798 for completion in menu
9799 .completions
9800 .borrow()
9801 .iter() {
9802 assert_eq!(
9803 completion.label.filter_range,
9804 0..completion.label.text.len(),
9805 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9806 );
9807 }
9808
9809 } else {
9810 panic!("expected completion menu to be open");
9811 }
9812 });
9813}
9814
9815#[gpui::test]
9816async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9817 init_test(cx, |_| {});
9818 let mut cx = EditorLspTestContext::new_rust(
9819 lsp::ServerCapabilities {
9820 completion_provider: Some(lsp::CompletionOptions {
9821 trigger_characters: Some(vec![".".to_string()]),
9822 ..Default::default()
9823 }),
9824 ..Default::default()
9825 },
9826 cx,
9827 )
9828 .await;
9829 cx.lsp
9830 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9831 Ok(Some(lsp::CompletionResponse::Array(vec![
9832 lsp::CompletionItem {
9833 label: "first".into(),
9834 ..Default::default()
9835 },
9836 lsp::CompletionItem {
9837 label: "last".into(),
9838 ..Default::default()
9839 },
9840 ])))
9841 });
9842 cx.set_state("variableˇ");
9843 cx.simulate_keystroke(".");
9844 cx.executor().run_until_parked();
9845
9846 cx.update_editor(|editor, _, _| {
9847 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9848 {
9849 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9850 } else {
9851 panic!("expected completion menu to be open");
9852 }
9853 });
9854
9855 cx.update_editor(|editor, window, cx| {
9856 editor.move_page_down(&MovePageDown::default(), window, cx);
9857 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9858 {
9859 assert!(
9860 menu.selected_item == 1,
9861 "expected PageDown to select the last item from the context menu"
9862 );
9863 } else {
9864 panic!("expected completion menu to stay open after PageDown");
9865 }
9866 });
9867
9868 cx.update_editor(|editor, window, cx| {
9869 editor.move_page_up(&MovePageUp::default(), window, cx);
9870 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9871 {
9872 assert!(
9873 menu.selected_item == 0,
9874 "expected PageUp to select the first item from the context menu"
9875 );
9876 } else {
9877 panic!("expected completion menu to stay open after PageUp");
9878 }
9879 });
9880}
9881
9882#[gpui::test]
9883async fn test_completion_sort(cx: &mut TestAppContext) {
9884 init_test(cx, |_| {});
9885 let mut cx = EditorLspTestContext::new_rust(
9886 lsp::ServerCapabilities {
9887 completion_provider: Some(lsp::CompletionOptions {
9888 trigger_characters: Some(vec![".".to_string()]),
9889 ..Default::default()
9890 }),
9891 ..Default::default()
9892 },
9893 cx,
9894 )
9895 .await;
9896 cx.lsp
9897 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9898 Ok(Some(lsp::CompletionResponse::Array(vec![
9899 lsp::CompletionItem {
9900 label: "Range".into(),
9901 sort_text: Some("a".into()),
9902 ..Default::default()
9903 },
9904 lsp::CompletionItem {
9905 label: "r".into(),
9906 sort_text: Some("b".into()),
9907 ..Default::default()
9908 },
9909 lsp::CompletionItem {
9910 label: "ret".into(),
9911 sort_text: Some("c".into()),
9912 ..Default::default()
9913 },
9914 lsp::CompletionItem {
9915 label: "return".into(),
9916 sort_text: Some("d".into()),
9917 ..Default::default()
9918 },
9919 lsp::CompletionItem {
9920 label: "slice".into(),
9921 sort_text: Some("d".into()),
9922 ..Default::default()
9923 },
9924 ])))
9925 });
9926 cx.set_state("rˇ");
9927 cx.executor().run_until_parked();
9928 cx.update_editor(|editor, window, cx| {
9929 editor.show_completions(
9930 &ShowCompletions {
9931 trigger: Some("r".into()),
9932 },
9933 window,
9934 cx,
9935 );
9936 });
9937 cx.executor().run_until_parked();
9938
9939 cx.update_editor(|editor, _, _| {
9940 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9941 {
9942 assert_eq!(
9943 completion_menu_entries(&menu),
9944 &["r", "ret", "Range", "return"]
9945 );
9946 } else {
9947 panic!("expected completion menu to be open");
9948 }
9949 });
9950}
9951
9952#[gpui::test]
9953async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9954 init_test(cx, |_| {});
9955
9956 let mut cx = EditorLspTestContext::new_rust(
9957 lsp::ServerCapabilities {
9958 completion_provider: Some(lsp::CompletionOptions {
9959 trigger_characters: Some(vec![".".to_string()]),
9960 resolve_provider: Some(true),
9961 ..Default::default()
9962 }),
9963 ..Default::default()
9964 },
9965 cx,
9966 )
9967 .await;
9968
9969 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9970 cx.simulate_keystroke(".");
9971 let completion_item = lsp::CompletionItem {
9972 label: "Some".into(),
9973 kind: Some(lsp::CompletionItemKind::SNIPPET),
9974 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9975 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9976 kind: lsp::MarkupKind::Markdown,
9977 value: "```rust\nSome(2)\n```".to_string(),
9978 })),
9979 deprecated: Some(false),
9980 sort_text: Some("Some".to_string()),
9981 filter_text: Some("Some".to_string()),
9982 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9983 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9984 range: lsp::Range {
9985 start: lsp::Position {
9986 line: 0,
9987 character: 22,
9988 },
9989 end: lsp::Position {
9990 line: 0,
9991 character: 22,
9992 },
9993 },
9994 new_text: "Some(2)".to_string(),
9995 })),
9996 additional_text_edits: Some(vec![lsp::TextEdit {
9997 range: lsp::Range {
9998 start: lsp::Position {
9999 line: 0,
10000 character: 20,
10001 },
10002 end: lsp::Position {
10003 line: 0,
10004 character: 22,
10005 },
10006 },
10007 new_text: "".to_string(),
10008 }]),
10009 ..Default::default()
10010 };
10011
10012 let closure_completion_item = completion_item.clone();
10013 let counter = Arc::new(AtomicUsize::new(0));
10014 let counter_clone = counter.clone();
10015 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10016 let task_completion_item = closure_completion_item.clone();
10017 counter_clone.fetch_add(1, atomic::Ordering::Release);
10018 async move {
10019 Ok(Some(lsp::CompletionResponse::Array(vec![
10020 task_completion_item,
10021 ])))
10022 }
10023 });
10024
10025 cx.condition(|editor, _| editor.context_menu_visible())
10026 .await;
10027 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
10028 assert!(request.next().await.is_some());
10029 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10030
10031 cx.simulate_keystroke("S");
10032 cx.simulate_keystroke("o");
10033 cx.simulate_keystroke("m");
10034 cx.condition(|editor, _| editor.context_menu_visible())
10035 .await;
10036 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
10037 assert!(request.next().await.is_some());
10038 assert!(request.next().await.is_some());
10039 assert!(request.next().await.is_some());
10040 request.close();
10041 assert!(request.next().await.is_none());
10042 assert_eq!(
10043 counter.load(atomic::Ordering::Acquire),
10044 4,
10045 "With the completions menu open, only one LSP request should happen per input"
10046 );
10047}
10048
10049#[gpui::test]
10050async fn test_toggle_comment(cx: &mut TestAppContext) {
10051 init_test(cx, |_| {});
10052 let mut cx = EditorTestContext::new(cx).await;
10053 let language = Arc::new(Language::new(
10054 LanguageConfig {
10055 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10056 ..Default::default()
10057 },
10058 Some(tree_sitter_rust::LANGUAGE.into()),
10059 ));
10060 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10061
10062 // If multiple selections intersect a line, the line is only toggled once.
10063 cx.set_state(indoc! {"
10064 fn a() {
10065 «//b();
10066 ˇ»// «c();
10067 //ˇ» d();
10068 }
10069 "});
10070
10071 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10072
10073 cx.assert_editor_state(indoc! {"
10074 fn a() {
10075 «b();
10076 c();
10077 ˇ» d();
10078 }
10079 "});
10080
10081 // The comment prefix is inserted at the same column for every line in a
10082 // selection.
10083 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10084
10085 cx.assert_editor_state(indoc! {"
10086 fn a() {
10087 // «b();
10088 // c();
10089 ˇ»// d();
10090 }
10091 "});
10092
10093 // If a selection ends at the beginning of a line, that line is not toggled.
10094 cx.set_selections_state(indoc! {"
10095 fn a() {
10096 // b();
10097 «// c();
10098 ˇ» // d();
10099 }
10100 "});
10101
10102 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10103
10104 cx.assert_editor_state(indoc! {"
10105 fn a() {
10106 // b();
10107 «c();
10108 ˇ» // d();
10109 }
10110 "});
10111
10112 // If a selection span a single line and is empty, the line is toggled.
10113 cx.set_state(indoc! {"
10114 fn a() {
10115 a();
10116 b();
10117 ˇ
10118 }
10119 "});
10120
10121 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10122
10123 cx.assert_editor_state(indoc! {"
10124 fn a() {
10125 a();
10126 b();
10127 //•ˇ
10128 }
10129 "});
10130
10131 // If a selection span multiple lines, empty lines are not toggled.
10132 cx.set_state(indoc! {"
10133 fn a() {
10134 «a();
10135
10136 c();ˇ»
10137 }
10138 "});
10139
10140 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10141
10142 cx.assert_editor_state(indoc! {"
10143 fn a() {
10144 // «a();
10145
10146 // c();ˇ»
10147 }
10148 "});
10149
10150 // If a selection includes multiple comment prefixes, all lines are uncommented.
10151 cx.set_state(indoc! {"
10152 fn a() {
10153 «// a();
10154 /// b();
10155 //! c();ˇ»
10156 }
10157 "});
10158
10159 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10160
10161 cx.assert_editor_state(indoc! {"
10162 fn a() {
10163 «a();
10164 b();
10165 c();ˇ»
10166 }
10167 "});
10168}
10169
10170#[gpui::test]
10171async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10172 init_test(cx, |_| {});
10173 let mut cx = EditorTestContext::new(cx).await;
10174 let language = Arc::new(Language::new(
10175 LanguageConfig {
10176 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10177 ..Default::default()
10178 },
10179 Some(tree_sitter_rust::LANGUAGE.into()),
10180 ));
10181 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10182
10183 let toggle_comments = &ToggleComments {
10184 advance_downwards: false,
10185 ignore_indent: true,
10186 };
10187
10188 // If multiple selections intersect a line, the line is only toggled once.
10189 cx.set_state(indoc! {"
10190 fn a() {
10191 // «b();
10192 // c();
10193 // ˇ» d();
10194 }
10195 "});
10196
10197 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10198
10199 cx.assert_editor_state(indoc! {"
10200 fn a() {
10201 «b();
10202 c();
10203 ˇ» d();
10204 }
10205 "});
10206
10207 // The comment prefix is inserted at the beginning of each line
10208 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10209
10210 cx.assert_editor_state(indoc! {"
10211 fn a() {
10212 // «b();
10213 // c();
10214 // ˇ» d();
10215 }
10216 "});
10217
10218 // If a selection ends at the beginning of a line, that line is not toggled.
10219 cx.set_selections_state(indoc! {"
10220 fn a() {
10221 // b();
10222 // «c();
10223 ˇ»// d();
10224 }
10225 "});
10226
10227 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10228
10229 cx.assert_editor_state(indoc! {"
10230 fn a() {
10231 // b();
10232 «c();
10233 ˇ»// d();
10234 }
10235 "});
10236
10237 // If a selection span a single line and is empty, the line is toggled.
10238 cx.set_state(indoc! {"
10239 fn a() {
10240 a();
10241 b();
10242 ˇ
10243 }
10244 "});
10245
10246 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10247
10248 cx.assert_editor_state(indoc! {"
10249 fn a() {
10250 a();
10251 b();
10252 //ˇ
10253 }
10254 "});
10255
10256 // If a selection span multiple lines, empty lines are not toggled.
10257 cx.set_state(indoc! {"
10258 fn a() {
10259 «a();
10260
10261 c();ˇ»
10262 }
10263 "});
10264
10265 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10266
10267 cx.assert_editor_state(indoc! {"
10268 fn a() {
10269 // «a();
10270
10271 // c();ˇ»
10272 }
10273 "});
10274
10275 // If a selection includes multiple comment prefixes, all lines are uncommented.
10276 cx.set_state(indoc! {"
10277 fn a() {
10278 // «a();
10279 /// b();
10280 //! c();ˇ»
10281 }
10282 "});
10283
10284 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10285
10286 cx.assert_editor_state(indoc! {"
10287 fn a() {
10288 «a();
10289 b();
10290 c();ˇ»
10291 }
10292 "});
10293}
10294
10295#[gpui::test]
10296async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10297 init_test(cx, |_| {});
10298
10299 let language = Arc::new(Language::new(
10300 LanguageConfig {
10301 line_comments: vec!["// ".into()],
10302 ..Default::default()
10303 },
10304 Some(tree_sitter_rust::LANGUAGE.into()),
10305 ));
10306
10307 let mut cx = EditorTestContext::new(cx).await;
10308
10309 cx.language_registry().add(language.clone());
10310 cx.update_buffer(|buffer, cx| {
10311 buffer.set_language(Some(language), cx);
10312 });
10313
10314 let toggle_comments = &ToggleComments {
10315 advance_downwards: true,
10316 ignore_indent: false,
10317 };
10318
10319 // Single cursor on one line -> advance
10320 // Cursor moves horizontally 3 characters as well on non-blank line
10321 cx.set_state(indoc!(
10322 "fn a() {
10323 ˇdog();
10324 cat();
10325 }"
10326 ));
10327 cx.update_editor(|editor, window, cx| {
10328 editor.toggle_comments(toggle_comments, window, cx);
10329 });
10330 cx.assert_editor_state(indoc!(
10331 "fn a() {
10332 // dog();
10333 catˇ();
10334 }"
10335 ));
10336
10337 // Single selection on one line -> don't advance
10338 cx.set_state(indoc!(
10339 "fn a() {
10340 «dog()ˇ»;
10341 cat();
10342 }"
10343 ));
10344 cx.update_editor(|editor, window, cx| {
10345 editor.toggle_comments(toggle_comments, window, cx);
10346 });
10347 cx.assert_editor_state(indoc!(
10348 "fn a() {
10349 // «dog()ˇ»;
10350 cat();
10351 }"
10352 ));
10353
10354 // Multiple cursors on one line -> advance
10355 cx.set_state(indoc!(
10356 "fn a() {
10357 ˇdˇog();
10358 cat();
10359 }"
10360 ));
10361 cx.update_editor(|editor, window, cx| {
10362 editor.toggle_comments(toggle_comments, window, cx);
10363 });
10364 cx.assert_editor_state(indoc!(
10365 "fn a() {
10366 // dog();
10367 catˇ(ˇ);
10368 }"
10369 ));
10370
10371 // Multiple cursors on one line, with selection -> don't advance
10372 cx.set_state(indoc!(
10373 "fn a() {
10374 ˇdˇog«()ˇ»;
10375 cat();
10376 }"
10377 ));
10378 cx.update_editor(|editor, window, cx| {
10379 editor.toggle_comments(toggle_comments, window, cx);
10380 });
10381 cx.assert_editor_state(indoc!(
10382 "fn a() {
10383 // ˇdˇog«()ˇ»;
10384 cat();
10385 }"
10386 ));
10387
10388 // Single cursor on one line -> advance
10389 // Cursor moves to column 0 on blank line
10390 cx.set_state(indoc!(
10391 "fn a() {
10392 ˇdog();
10393
10394 cat();
10395 }"
10396 ));
10397 cx.update_editor(|editor, window, cx| {
10398 editor.toggle_comments(toggle_comments, window, cx);
10399 });
10400 cx.assert_editor_state(indoc!(
10401 "fn a() {
10402 // dog();
10403 ˇ
10404 cat();
10405 }"
10406 ));
10407
10408 // Single cursor on one line -> advance
10409 // Cursor starts and ends at column 0
10410 cx.set_state(indoc!(
10411 "fn a() {
10412 ˇ dog();
10413 cat();
10414 }"
10415 ));
10416 cx.update_editor(|editor, window, cx| {
10417 editor.toggle_comments(toggle_comments, window, cx);
10418 });
10419 cx.assert_editor_state(indoc!(
10420 "fn a() {
10421 // dog();
10422 ˇ cat();
10423 }"
10424 ));
10425}
10426
10427#[gpui::test]
10428async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10429 init_test(cx, |_| {});
10430
10431 let mut cx = EditorTestContext::new(cx).await;
10432
10433 let html_language = Arc::new(
10434 Language::new(
10435 LanguageConfig {
10436 name: "HTML".into(),
10437 block_comment: Some(("<!-- ".into(), " -->".into())),
10438 ..Default::default()
10439 },
10440 Some(tree_sitter_html::LANGUAGE.into()),
10441 )
10442 .with_injection_query(
10443 r#"
10444 (script_element
10445 (raw_text) @injection.content
10446 (#set! injection.language "javascript"))
10447 "#,
10448 )
10449 .unwrap(),
10450 );
10451
10452 let javascript_language = Arc::new(Language::new(
10453 LanguageConfig {
10454 name: "JavaScript".into(),
10455 line_comments: vec!["// ".into()],
10456 ..Default::default()
10457 },
10458 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10459 ));
10460
10461 cx.language_registry().add(html_language.clone());
10462 cx.language_registry().add(javascript_language.clone());
10463 cx.update_buffer(|buffer, cx| {
10464 buffer.set_language(Some(html_language), cx);
10465 });
10466
10467 // Toggle comments for empty selections
10468 cx.set_state(
10469 &r#"
10470 <p>A</p>ˇ
10471 <p>B</p>ˇ
10472 <p>C</p>ˇ
10473 "#
10474 .unindent(),
10475 );
10476 cx.update_editor(|editor, window, cx| {
10477 editor.toggle_comments(&ToggleComments::default(), window, cx)
10478 });
10479 cx.assert_editor_state(
10480 &r#"
10481 <!-- <p>A</p>ˇ -->
10482 <!-- <p>B</p>ˇ -->
10483 <!-- <p>C</p>ˇ -->
10484 "#
10485 .unindent(),
10486 );
10487 cx.update_editor(|editor, window, cx| {
10488 editor.toggle_comments(&ToggleComments::default(), window, cx)
10489 });
10490 cx.assert_editor_state(
10491 &r#"
10492 <p>A</p>ˇ
10493 <p>B</p>ˇ
10494 <p>C</p>ˇ
10495 "#
10496 .unindent(),
10497 );
10498
10499 // Toggle comments for mixture of empty and non-empty selections, where
10500 // multiple selections occupy a given line.
10501 cx.set_state(
10502 &r#"
10503 <p>A«</p>
10504 <p>ˇ»B</p>ˇ
10505 <p>C«</p>
10506 <p>ˇ»D</p>ˇ
10507 "#
10508 .unindent(),
10509 );
10510
10511 cx.update_editor(|editor, window, cx| {
10512 editor.toggle_comments(&ToggleComments::default(), window, cx)
10513 });
10514 cx.assert_editor_state(
10515 &r#"
10516 <!-- <p>A«</p>
10517 <p>ˇ»B</p>ˇ -->
10518 <!-- <p>C«</p>
10519 <p>ˇ»D</p>ˇ -->
10520 "#
10521 .unindent(),
10522 );
10523 cx.update_editor(|editor, window, cx| {
10524 editor.toggle_comments(&ToggleComments::default(), window, cx)
10525 });
10526 cx.assert_editor_state(
10527 &r#"
10528 <p>A«</p>
10529 <p>ˇ»B</p>ˇ
10530 <p>C«</p>
10531 <p>ˇ»D</p>ˇ
10532 "#
10533 .unindent(),
10534 );
10535
10536 // Toggle comments when different languages are active for different
10537 // selections.
10538 cx.set_state(
10539 &r#"
10540 ˇ<script>
10541 ˇvar x = new Y();
10542 ˇ</script>
10543 "#
10544 .unindent(),
10545 );
10546 cx.executor().run_until_parked();
10547 cx.update_editor(|editor, window, cx| {
10548 editor.toggle_comments(&ToggleComments::default(), window, cx)
10549 });
10550 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10551 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10552 cx.assert_editor_state(
10553 &r#"
10554 <!-- ˇ<script> -->
10555 // ˇvar x = new Y();
10556 <!-- ˇ</script> -->
10557 "#
10558 .unindent(),
10559 );
10560}
10561
10562#[gpui::test]
10563fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10564 init_test(cx, |_| {});
10565
10566 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10567 let multibuffer = cx.new(|cx| {
10568 let mut multibuffer = MultiBuffer::new(ReadWrite);
10569 multibuffer.push_excerpts(
10570 buffer.clone(),
10571 [
10572 ExcerptRange {
10573 context: Point::new(0, 0)..Point::new(0, 4),
10574 primary: None,
10575 },
10576 ExcerptRange {
10577 context: Point::new(1, 0)..Point::new(1, 4),
10578 primary: None,
10579 },
10580 ],
10581 cx,
10582 );
10583 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10584 multibuffer
10585 });
10586
10587 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10588 editor.update_in(cx, |editor, window, cx| {
10589 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10590 editor.change_selections(None, window, cx, |s| {
10591 s.select_ranges([
10592 Point::new(0, 0)..Point::new(0, 0),
10593 Point::new(1, 0)..Point::new(1, 0),
10594 ])
10595 });
10596
10597 editor.handle_input("X", window, cx);
10598 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10599 assert_eq!(
10600 editor.selections.ranges(cx),
10601 [
10602 Point::new(0, 1)..Point::new(0, 1),
10603 Point::new(1, 1)..Point::new(1, 1),
10604 ]
10605 );
10606
10607 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10608 editor.change_selections(None, window, cx, |s| {
10609 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10610 });
10611 editor.backspace(&Default::default(), window, cx);
10612 assert_eq!(editor.text(cx), "Xa\nbbb");
10613 assert_eq!(
10614 editor.selections.ranges(cx),
10615 [Point::new(1, 0)..Point::new(1, 0)]
10616 );
10617
10618 editor.change_selections(None, window, cx, |s| {
10619 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10620 });
10621 editor.backspace(&Default::default(), window, cx);
10622 assert_eq!(editor.text(cx), "X\nbb");
10623 assert_eq!(
10624 editor.selections.ranges(cx),
10625 [Point::new(0, 1)..Point::new(0, 1)]
10626 );
10627 });
10628}
10629
10630#[gpui::test]
10631fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10632 init_test(cx, |_| {});
10633
10634 let markers = vec![('[', ']').into(), ('(', ')').into()];
10635 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10636 indoc! {"
10637 [aaaa
10638 (bbbb]
10639 cccc)",
10640 },
10641 markers.clone(),
10642 );
10643 let excerpt_ranges = markers.into_iter().map(|marker| {
10644 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10645 ExcerptRange {
10646 context,
10647 primary: None,
10648 }
10649 });
10650 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10651 let multibuffer = cx.new(|cx| {
10652 let mut multibuffer = MultiBuffer::new(ReadWrite);
10653 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10654 multibuffer
10655 });
10656
10657 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10658 editor.update_in(cx, |editor, window, cx| {
10659 let (expected_text, selection_ranges) = marked_text_ranges(
10660 indoc! {"
10661 aaaa
10662 bˇbbb
10663 bˇbbˇb
10664 cccc"
10665 },
10666 true,
10667 );
10668 assert_eq!(editor.text(cx), expected_text);
10669 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10670
10671 editor.handle_input("X", window, cx);
10672
10673 let (expected_text, expected_selections) = marked_text_ranges(
10674 indoc! {"
10675 aaaa
10676 bXˇbbXb
10677 bXˇbbXˇb
10678 cccc"
10679 },
10680 false,
10681 );
10682 assert_eq!(editor.text(cx), expected_text);
10683 assert_eq!(editor.selections.ranges(cx), expected_selections);
10684
10685 editor.newline(&Newline, window, cx);
10686 let (expected_text, expected_selections) = marked_text_ranges(
10687 indoc! {"
10688 aaaa
10689 bX
10690 ˇbbX
10691 b
10692 bX
10693 ˇbbX
10694 ˇb
10695 cccc"
10696 },
10697 false,
10698 );
10699 assert_eq!(editor.text(cx), expected_text);
10700 assert_eq!(editor.selections.ranges(cx), expected_selections);
10701 });
10702}
10703
10704#[gpui::test]
10705fn test_refresh_selections(cx: &mut TestAppContext) {
10706 init_test(cx, |_| {});
10707
10708 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10709 let mut excerpt1_id = None;
10710 let multibuffer = cx.new(|cx| {
10711 let mut multibuffer = MultiBuffer::new(ReadWrite);
10712 excerpt1_id = multibuffer
10713 .push_excerpts(
10714 buffer.clone(),
10715 [
10716 ExcerptRange {
10717 context: Point::new(0, 0)..Point::new(1, 4),
10718 primary: None,
10719 },
10720 ExcerptRange {
10721 context: Point::new(1, 0)..Point::new(2, 4),
10722 primary: None,
10723 },
10724 ],
10725 cx,
10726 )
10727 .into_iter()
10728 .next();
10729 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10730 multibuffer
10731 });
10732
10733 let editor = cx.add_window(|window, cx| {
10734 let mut editor = build_editor(multibuffer.clone(), window, cx);
10735 let snapshot = editor.snapshot(window, cx);
10736 editor.change_selections(None, window, cx, |s| {
10737 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10738 });
10739 editor.begin_selection(
10740 Point::new(2, 1).to_display_point(&snapshot),
10741 true,
10742 1,
10743 window,
10744 cx,
10745 );
10746 assert_eq!(
10747 editor.selections.ranges(cx),
10748 [
10749 Point::new(1, 3)..Point::new(1, 3),
10750 Point::new(2, 1)..Point::new(2, 1),
10751 ]
10752 );
10753 editor
10754 });
10755
10756 // Refreshing selections is a no-op when excerpts haven't changed.
10757 _ = editor.update(cx, |editor, window, cx| {
10758 editor.change_selections(None, window, cx, |s| s.refresh());
10759 assert_eq!(
10760 editor.selections.ranges(cx),
10761 [
10762 Point::new(1, 3)..Point::new(1, 3),
10763 Point::new(2, 1)..Point::new(2, 1),
10764 ]
10765 );
10766 });
10767
10768 multibuffer.update(cx, |multibuffer, cx| {
10769 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10770 });
10771 _ = editor.update(cx, |editor, window, cx| {
10772 // Removing an excerpt causes the first selection to become degenerate.
10773 assert_eq!(
10774 editor.selections.ranges(cx),
10775 [
10776 Point::new(0, 0)..Point::new(0, 0),
10777 Point::new(0, 1)..Point::new(0, 1)
10778 ]
10779 );
10780
10781 // Refreshing selections will relocate the first selection to the original buffer
10782 // location.
10783 editor.change_selections(None, window, cx, |s| s.refresh());
10784 assert_eq!(
10785 editor.selections.ranges(cx),
10786 [
10787 Point::new(0, 1)..Point::new(0, 1),
10788 Point::new(0, 3)..Point::new(0, 3)
10789 ]
10790 );
10791 assert!(editor.selections.pending_anchor().is_some());
10792 });
10793}
10794
10795#[gpui::test]
10796fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10797 init_test(cx, |_| {});
10798
10799 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10800 let mut excerpt1_id = None;
10801 let multibuffer = cx.new(|cx| {
10802 let mut multibuffer = MultiBuffer::new(ReadWrite);
10803 excerpt1_id = multibuffer
10804 .push_excerpts(
10805 buffer.clone(),
10806 [
10807 ExcerptRange {
10808 context: Point::new(0, 0)..Point::new(1, 4),
10809 primary: None,
10810 },
10811 ExcerptRange {
10812 context: Point::new(1, 0)..Point::new(2, 4),
10813 primary: None,
10814 },
10815 ],
10816 cx,
10817 )
10818 .into_iter()
10819 .next();
10820 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10821 multibuffer
10822 });
10823
10824 let editor = cx.add_window(|window, cx| {
10825 let mut editor = build_editor(multibuffer.clone(), window, cx);
10826 let snapshot = editor.snapshot(window, cx);
10827 editor.begin_selection(
10828 Point::new(1, 3).to_display_point(&snapshot),
10829 false,
10830 1,
10831 window,
10832 cx,
10833 );
10834 assert_eq!(
10835 editor.selections.ranges(cx),
10836 [Point::new(1, 3)..Point::new(1, 3)]
10837 );
10838 editor
10839 });
10840
10841 multibuffer.update(cx, |multibuffer, cx| {
10842 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10843 });
10844 _ = editor.update(cx, |editor, window, cx| {
10845 assert_eq!(
10846 editor.selections.ranges(cx),
10847 [Point::new(0, 0)..Point::new(0, 0)]
10848 );
10849
10850 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10851 editor.change_selections(None, window, cx, |s| s.refresh());
10852 assert_eq!(
10853 editor.selections.ranges(cx),
10854 [Point::new(0, 3)..Point::new(0, 3)]
10855 );
10856 assert!(editor.selections.pending_anchor().is_some());
10857 });
10858}
10859
10860#[gpui::test]
10861async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10862 init_test(cx, |_| {});
10863
10864 let language = Arc::new(
10865 Language::new(
10866 LanguageConfig {
10867 brackets: BracketPairConfig {
10868 pairs: vec![
10869 BracketPair {
10870 start: "{".to_string(),
10871 end: "}".to_string(),
10872 close: true,
10873 surround: true,
10874 newline: true,
10875 },
10876 BracketPair {
10877 start: "/* ".to_string(),
10878 end: " */".to_string(),
10879 close: true,
10880 surround: true,
10881 newline: true,
10882 },
10883 ],
10884 ..Default::default()
10885 },
10886 ..Default::default()
10887 },
10888 Some(tree_sitter_rust::LANGUAGE.into()),
10889 )
10890 .with_indents_query("")
10891 .unwrap(),
10892 );
10893
10894 let text = concat!(
10895 "{ }\n", //
10896 " x\n", //
10897 " /* */\n", //
10898 "x\n", //
10899 "{{} }\n", //
10900 );
10901
10902 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10903 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10904 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10905 editor
10906 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10907 .await;
10908
10909 editor.update_in(cx, |editor, window, cx| {
10910 editor.change_selections(None, window, cx, |s| {
10911 s.select_display_ranges([
10912 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10913 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10914 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10915 ])
10916 });
10917 editor.newline(&Newline, window, cx);
10918
10919 assert_eq!(
10920 editor.buffer().read(cx).read(cx).text(),
10921 concat!(
10922 "{ \n", // Suppress rustfmt
10923 "\n", //
10924 "}\n", //
10925 " x\n", //
10926 " /* \n", //
10927 " \n", //
10928 " */\n", //
10929 "x\n", //
10930 "{{} \n", //
10931 "}\n", //
10932 )
10933 );
10934 });
10935}
10936
10937#[gpui::test]
10938fn test_highlighted_ranges(cx: &mut TestAppContext) {
10939 init_test(cx, |_| {});
10940
10941 let editor = cx.add_window(|window, cx| {
10942 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10943 build_editor(buffer.clone(), window, cx)
10944 });
10945
10946 _ = editor.update(cx, |editor, window, cx| {
10947 struct Type1;
10948 struct Type2;
10949
10950 let buffer = editor.buffer.read(cx).snapshot(cx);
10951
10952 let anchor_range =
10953 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10954
10955 editor.highlight_background::<Type1>(
10956 &[
10957 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10958 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10959 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10960 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10961 ],
10962 |_| Hsla::red(),
10963 cx,
10964 );
10965 editor.highlight_background::<Type2>(
10966 &[
10967 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10968 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10969 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10970 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10971 ],
10972 |_| Hsla::green(),
10973 cx,
10974 );
10975
10976 let snapshot = editor.snapshot(window, cx);
10977 let mut highlighted_ranges = editor.background_highlights_in_range(
10978 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10979 &snapshot,
10980 cx.theme().colors(),
10981 );
10982 // Enforce a consistent ordering based on color without relying on the ordering of the
10983 // highlight's `TypeId` which is non-executor.
10984 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10985 assert_eq!(
10986 highlighted_ranges,
10987 &[
10988 (
10989 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10990 Hsla::red(),
10991 ),
10992 (
10993 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10994 Hsla::red(),
10995 ),
10996 (
10997 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10998 Hsla::green(),
10999 ),
11000 (
11001 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
11002 Hsla::green(),
11003 ),
11004 ]
11005 );
11006 assert_eq!(
11007 editor.background_highlights_in_range(
11008 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
11009 &snapshot,
11010 cx.theme().colors(),
11011 ),
11012 &[(
11013 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11014 Hsla::red(),
11015 )]
11016 );
11017 });
11018}
11019
11020#[gpui::test]
11021async fn test_following(cx: &mut TestAppContext) {
11022 init_test(cx, |_| {});
11023
11024 let fs = FakeFs::new(cx.executor());
11025 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11026
11027 let buffer = project.update(cx, |project, cx| {
11028 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
11029 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
11030 });
11031 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
11032 let follower = cx.update(|cx| {
11033 cx.open_window(
11034 WindowOptions {
11035 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
11036 gpui::Point::new(px(0.), px(0.)),
11037 gpui::Point::new(px(10.), px(80.)),
11038 ))),
11039 ..Default::default()
11040 },
11041 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
11042 )
11043 .unwrap()
11044 });
11045
11046 let is_still_following = Rc::new(RefCell::new(true));
11047 let follower_edit_event_count = Rc::new(RefCell::new(0));
11048 let pending_update = Rc::new(RefCell::new(None));
11049 let leader_entity = leader.root(cx).unwrap();
11050 let follower_entity = follower.root(cx).unwrap();
11051 _ = follower.update(cx, {
11052 let update = pending_update.clone();
11053 let is_still_following = is_still_following.clone();
11054 let follower_edit_event_count = follower_edit_event_count.clone();
11055 |_, window, cx| {
11056 cx.subscribe_in(
11057 &leader_entity,
11058 window,
11059 move |_, leader, event, window, cx| {
11060 leader.read(cx).add_event_to_update_proto(
11061 event,
11062 &mut update.borrow_mut(),
11063 window,
11064 cx,
11065 );
11066 },
11067 )
11068 .detach();
11069
11070 cx.subscribe_in(
11071 &follower_entity,
11072 window,
11073 move |_, _, event: &EditorEvent, _window, _cx| {
11074 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11075 *is_still_following.borrow_mut() = false;
11076 }
11077
11078 if let EditorEvent::BufferEdited = event {
11079 *follower_edit_event_count.borrow_mut() += 1;
11080 }
11081 },
11082 )
11083 .detach();
11084 }
11085 });
11086
11087 // Update the selections only
11088 _ = leader.update(cx, |leader, window, cx| {
11089 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11090 });
11091 follower
11092 .update(cx, |follower, window, cx| {
11093 follower.apply_update_proto(
11094 &project,
11095 pending_update.borrow_mut().take().unwrap(),
11096 window,
11097 cx,
11098 )
11099 })
11100 .unwrap()
11101 .await
11102 .unwrap();
11103 _ = follower.update(cx, |follower, _, cx| {
11104 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11105 });
11106 assert!(*is_still_following.borrow());
11107 assert_eq!(*follower_edit_event_count.borrow(), 0);
11108
11109 // Update the scroll position only
11110 _ = leader.update(cx, |leader, window, cx| {
11111 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11112 });
11113 follower
11114 .update(cx, |follower, window, cx| {
11115 follower.apply_update_proto(
11116 &project,
11117 pending_update.borrow_mut().take().unwrap(),
11118 window,
11119 cx,
11120 )
11121 })
11122 .unwrap()
11123 .await
11124 .unwrap();
11125 assert_eq!(
11126 follower
11127 .update(cx, |follower, _, cx| follower.scroll_position(cx))
11128 .unwrap(),
11129 gpui::Point::new(1.5, 3.5)
11130 );
11131 assert!(*is_still_following.borrow());
11132 assert_eq!(*follower_edit_event_count.borrow(), 0);
11133
11134 // Update the selections and scroll position. The follower's scroll position is updated
11135 // via autoscroll, not via the leader's exact scroll position.
11136 _ = leader.update(cx, |leader, window, cx| {
11137 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11138 leader.request_autoscroll(Autoscroll::newest(), cx);
11139 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11140 });
11141 follower
11142 .update(cx, |follower, window, cx| {
11143 follower.apply_update_proto(
11144 &project,
11145 pending_update.borrow_mut().take().unwrap(),
11146 window,
11147 cx,
11148 )
11149 })
11150 .unwrap()
11151 .await
11152 .unwrap();
11153 _ = follower.update(cx, |follower, _, cx| {
11154 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11155 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11156 });
11157 assert!(*is_still_following.borrow());
11158
11159 // Creating a pending selection that precedes another selection
11160 _ = leader.update(cx, |leader, window, cx| {
11161 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11162 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11163 });
11164 follower
11165 .update(cx, |follower, window, cx| {
11166 follower.apply_update_proto(
11167 &project,
11168 pending_update.borrow_mut().take().unwrap(),
11169 window,
11170 cx,
11171 )
11172 })
11173 .unwrap()
11174 .await
11175 .unwrap();
11176 _ = follower.update(cx, |follower, _, cx| {
11177 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11178 });
11179 assert!(*is_still_following.borrow());
11180
11181 // Extend the pending selection so that it surrounds another selection
11182 _ = leader.update(cx, |leader, window, cx| {
11183 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11184 });
11185 follower
11186 .update(cx, |follower, window, cx| {
11187 follower.apply_update_proto(
11188 &project,
11189 pending_update.borrow_mut().take().unwrap(),
11190 window,
11191 cx,
11192 )
11193 })
11194 .unwrap()
11195 .await
11196 .unwrap();
11197 _ = follower.update(cx, |follower, _, cx| {
11198 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11199 });
11200
11201 // Scrolling locally breaks the follow
11202 _ = follower.update(cx, |follower, window, cx| {
11203 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11204 follower.set_scroll_anchor(
11205 ScrollAnchor {
11206 anchor: top_anchor,
11207 offset: gpui::Point::new(0.0, 0.5),
11208 },
11209 window,
11210 cx,
11211 );
11212 });
11213 assert!(!(*is_still_following.borrow()));
11214}
11215
11216#[gpui::test]
11217async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11218 init_test(cx, |_| {});
11219
11220 let fs = FakeFs::new(cx.executor());
11221 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11222 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11223 let pane = workspace
11224 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11225 .unwrap();
11226
11227 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11228
11229 let leader = pane.update_in(cx, |_, window, cx| {
11230 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11231 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11232 });
11233
11234 // Start following the editor when it has no excerpts.
11235 let mut state_message =
11236 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11237 let workspace_entity = workspace.root(cx).unwrap();
11238 let follower_1 = cx
11239 .update_window(*workspace.deref(), |_, window, cx| {
11240 Editor::from_state_proto(
11241 workspace_entity,
11242 ViewId {
11243 creator: Default::default(),
11244 id: 0,
11245 },
11246 &mut state_message,
11247 window,
11248 cx,
11249 )
11250 })
11251 .unwrap()
11252 .unwrap()
11253 .await
11254 .unwrap();
11255
11256 let update_message = Rc::new(RefCell::new(None));
11257 follower_1.update_in(cx, {
11258 let update = update_message.clone();
11259 |_, window, cx| {
11260 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11261 leader.read(cx).add_event_to_update_proto(
11262 event,
11263 &mut update.borrow_mut(),
11264 window,
11265 cx,
11266 );
11267 })
11268 .detach();
11269 }
11270 });
11271
11272 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11273 (
11274 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11275 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11276 )
11277 });
11278
11279 // Insert some excerpts.
11280 leader.update(cx, |leader, cx| {
11281 leader.buffer.update(cx, |multibuffer, cx| {
11282 let excerpt_ids = multibuffer.push_excerpts(
11283 buffer_1.clone(),
11284 [
11285 ExcerptRange {
11286 context: 1..6,
11287 primary: None,
11288 },
11289 ExcerptRange {
11290 context: 12..15,
11291 primary: None,
11292 },
11293 ExcerptRange {
11294 context: 0..3,
11295 primary: None,
11296 },
11297 ],
11298 cx,
11299 );
11300 multibuffer.insert_excerpts_after(
11301 excerpt_ids[0],
11302 buffer_2.clone(),
11303 [
11304 ExcerptRange {
11305 context: 8..12,
11306 primary: None,
11307 },
11308 ExcerptRange {
11309 context: 0..6,
11310 primary: None,
11311 },
11312 ],
11313 cx,
11314 );
11315 });
11316 });
11317
11318 // Apply the update of adding the excerpts.
11319 follower_1
11320 .update_in(cx, |follower, window, cx| {
11321 follower.apply_update_proto(
11322 &project,
11323 update_message.borrow().clone().unwrap(),
11324 window,
11325 cx,
11326 )
11327 })
11328 .await
11329 .unwrap();
11330 assert_eq!(
11331 follower_1.update(cx, |editor, cx| editor.text(cx)),
11332 leader.update(cx, |editor, cx| editor.text(cx))
11333 );
11334 update_message.borrow_mut().take();
11335
11336 // Start following separately after it already has excerpts.
11337 let mut state_message =
11338 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11339 let workspace_entity = workspace.root(cx).unwrap();
11340 let follower_2 = cx
11341 .update_window(*workspace.deref(), |_, window, cx| {
11342 Editor::from_state_proto(
11343 workspace_entity,
11344 ViewId {
11345 creator: Default::default(),
11346 id: 0,
11347 },
11348 &mut state_message,
11349 window,
11350 cx,
11351 )
11352 })
11353 .unwrap()
11354 .unwrap()
11355 .await
11356 .unwrap();
11357 assert_eq!(
11358 follower_2.update(cx, |editor, cx| editor.text(cx)),
11359 leader.update(cx, |editor, cx| editor.text(cx))
11360 );
11361
11362 // Remove some excerpts.
11363 leader.update(cx, |leader, cx| {
11364 leader.buffer.update(cx, |multibuffer, cx| {
11365 let excerpt_ids = multibuffer.excerpt_ids();
11366 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11367 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11368 });
11369 });
11370
11371 // Apply the update of removing the excerpts.
11372 follower_1
11373 .update_in(cx, |follower, window, cx| {
11374 follower.apply_update_proto(
11375 &project,
11376 update_message.borrow().clone().unwrap(),
11377 window,
11378 cx,
11379 )
11380 })
11381 .await
11382 .unwrap();
11383 follower_2
11384 .update_in(cx, |follower, window, cx| {
11385 follower.apply_update_proto(
11386 &project,
11387 update_message.borrow().clone().unwrap(),
11388 window,
11389 cx,
11390 )
11391 })
11392 .await
11393 .unwrap();
11394 update_message.borrow_mut().take();
11395 assert_eq!(
11396 follower_1.update(cx, |editor, cx| editor.text(cx)),
11397 leader.update(cx, |editor, cx| editor.text(cx))
11398 );
11399}
11400
11401#[gpui::test]
11402async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11403 init_test(cx, |_| {});
11404
11405 let mut cx = EditorTestContext::new(cx).await;
11406 let lsp_store =
11407 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11408
11409 cx.set_state(indoc! {"
11410 ˇfn func(abc def: i32) -> u32 {
11411 }
11412 "});
11413
11414 cx.update(|_, cx| {
11415 lsp_store.update(cx, |lsp_store, cx| {
11416 lsp_store
11417 .update_diagnostics(
11418 LanguageServerId(0),
11419 lsp::PublishDiagnosticsParams {
11420 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11421 version: None,
11422 diagnostics: vec![
11423 lsp::Diagnostic {
11424 range: lsp::Range::new(
11425 lsp::Position::new(0, 11),
11426 lsp::Position::new(0, 12),
11427 ),
11428 severity: Some(lsp::DiagnosticSeverity::ERROR),
11429 ..Default::default()
11430 },
11431 lsp::Diagnostic {
11432 range: lsp::Range::new(
11433 lsp::Position::new(0, 12),
11434 lsp::Position::new(0, 15),
11435 ),
11436 severity: Some(lsp::DiagnosticSeverity::ERROR),
11437 ..Default::default()
11438 },
11439 lsp::Diagnostic {
11440 range: lsp::Range::new(
11441 lsp::Position::new(0, 25),
11442 lsp::Position::new(0, 28),
11443 ),
11444 severity: Some(lsp::DiagnosticSeverity::ERROR),
11445 ..Default::default()
11446 },
11447 ],
11448 },
11449 &[],
11450 cx,
11451 )
11452 .unwrap()
11453 });
11454 });
11455
11456 executor.run_until_parked();
11457
11458 cx.update_editor(|editor, window, cx| {
11459 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11460 });
11461
11462 cx.assert_editor_state(indoc! {"
11463 fn func(abc def: i32) -> ˇu32 {
11464 }
11465 "});
11466
11467 cx.update_editor(|editor, window, cx| {
11468 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11469 });
11470
11471 cx.assert_editor_state(indoc! {"
11472 fn func(abc ˇdef: i32) -> u32 {
11473 }
11474 "});
11475
11476 cx.update_editor(|editor, window, cx| {
11477 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11478 });
11479
11480 cx.assert_editor_state(indoc! {"
11481 fn func(abcˇ def: i32) -> u32 {
11482 }
11483 "});
11484
11485 cx.update_editor(|editor, window, cx| {
11486 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11487 });
11488
11489 cx.assert_editor_state(indoc! {"
11490 fn func(abc def: i32) -> ˇu32 {
11491 }
11492 "});
11493}
11494
11495#[gpui::test]
11496async fn cycle_through_same_place_diagnostics(
11497 executor: BackgroundExecutor,
11498 cx: &mut TestAppContext,
11499) {
11500 init_test(cx, |_| {});
11501
11502 let mut cx = EditorTestContext::new(cx).await;
11503 let lsp_store =
11504 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11505
11506 cx.set_state(indoc! {"
11507 ˇfn func(abc def: i32) -> u32 {
11508 }
11509 "});
11510
11511 cx.update(|_, cx| {
11512 lsp_store.update(cx, |lsp_store, cx| {
11513 lsp_store
11514 .update_diagnostics(
11515 LanguageServerId(0),
11516 lsp::PublishDiagnosticsParams {
11517 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11518 version: None,
11519 diagnostics: vec![
11520 lsp::Diagnostic {
11521 range: lsp::Range::new(
11522 lsp::Position::new(0, 11),
11523 lsp::Position::new(0, 12),
11524 ),
11525 severity: Some(lsp::DiagnosticSeverity::ERROR),
11526 ..Default::default()
11527 },
11528 lsp::Diagnostic {
11529 range: lsp::Range::new(
11530 lsp::Position::new(0, 12),
11531 lsp::Position::new(0, 15),
11532 ),
11533 severity: Some(lsp::DiagnosticSeverity::ERROR),
11534 ..Default::default()
11535 },
11536 lsp::Diagnostic {
11537 range: lsp::Range::new(
11538 lsp::Position::new(0, 12),
11539 lsp::Position::new(0, 15),
11540 ),
11541 severity: Some(lsp::DiagnosticSeverity::ERROR),
11542 ..Default::default()
11543 },
11544 lsp::Diagnostic {
11545 range: lsp::Range::new(
11546 lsp::Position::new(0, 25),
11547 lsp::Position::new(0, 28),
11548 ),
11549 severity: Some(lsp::DiagnosticSeverity::ERROR),
11550 ..Default::default()
11551 },
11552 ],
11553 },
11554 &[],
11555 cx,
11556 )
11557 .unwrap()
11558 });
11559 });
11560 executor.run_until_parked();
11561
11562 //// Backward
11563
11564 // Fourth diagnostic
11565 cx.update_editor(|editor, window, cx| {
11566 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11567 });
11568 cx.assert_editor_state(indoc! {"
11569 fn func(abc def: i32) -> ˇu32 {
11570 }
11571 "});
11572
11573 // Third diagnostic
11574 cx.update_editor(|editor, window, cx| {
11575 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11576 });
11577 cx.assert_editor_state(indoc! {"
11578 fn func(abc ˇdef: i32) -> u32 {
11579 }
11580 "});
11581
11582 // Second diagnostic, same place
11583 cx.update_editor(|editor, window, cx| {
11584 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11585 });
11586 cx.assert_editor_state(indoc! {"
11587 fn func(abc ˇdef: i32) -> u32 {
11588 }
11589 "});
11590
11591 // First diagnostic
11592 cx.update_editor(|editor, window, cx| {
11593 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11594 });
11595 cx.assert_editor_state(indoc! {"
11596 fn func(abcˇ def: i32) -> u32 {
11597 }
11598 "});
11599
11600 // Wrapped over, fourth diagnostic
11601 cx.update_editor(|editor, window, cx| {
11602 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11603 });
11604 cx.assert_editor_state(indoc! {"
11605 fn func(abc def: i32) -> ˇu32 {
11606 }
11607 "});
11608
11609 cx.update_editor(|editor, window, cx| {
11610 editor.move_to_beginning(&MoveToBeginning, window, cx);
11611 });
11612 cx.assert_editor_state(indoc! {"
11613 ˇfn func(abc def: i32) -> u32 {
11614 }
11615 "});
11616
11617 //// Forward
11618
11619 // First diagnostic
11620 cx.update_editor(|editor, window, cx| {
11621 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11622 });
11623 cx.assert_editor_state(indoc! {"
11624 fn func(abcˇ def: i32) -> u32 {
11625 }
11626 "});
11627
11628 // Second diagnostic
11629 cx.update_editor(|editor, window, cx| {
11630 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11631 });
11632 cx.assert_editor_state(indoc! {"
11633 fn func(abc ˇdef: i32) -> u32 {
11634 }
11635 "});
11636
11637 // Third diagnostic, same place
11638 cx.update_editor(|editor, window, cx| {
11639 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11640 });
11641 cx.assert_editor_state(indoc! {"
11642 fn func(abc ˇdef: i32) -> u32 {
11643 }
11644 "});
11645
11646 // Fourth diagnostic
11647 cx.update_editor(|editor, window, cx| {
11648 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11649 });
11650 cx.assert_editor_state(indoc! {"
11651 fn func(abc def: i32) -> ˇu32 {
11652 }
11653 "});
11654
11655 // Wrapped around, first diagnostic
11656 cx.update_editor(|editor, window, cx| {
11657 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11658 });
11659 cx.assert_editor_state(indoc! {"
11660 fn func(abcˇ def: i32) -> u32 {
11661 }
11662 "});
11663}
11664
11665#[gpui::test]
11666async fn active_diagnostics_dismiss_after_invalidation(
11667 executor: BackgroundExecutor,
11668 cx: &mut TestAppContext,
11669) {
11670 init_test(cx, |_| {});
11671
11672 let mut cx = EditorTestContext::new(cx).await;
11673 let lsp_store =
11674 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11675
11676 cx.set_state(indoc! {"
11677 ˇfn func(abc def: i32) -> u32 {
11678 }
11679 "});
11680
11681 let message = "Something's wrong!";
11682 cx.update(|_, cx| {
11683 lsp_store.update(cx, |lsp_store, cx| {
11684 lsp_store
11685 .update_diagnostics(
11686 LanguageServerId(0),
11687 lsp::PublishDiagnosticsParams {
11688 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11689 version: None,
11690 diagnostics: vec![lsp::Diagnostic {
11691 range: lsp::Range::new(
11692 lsp::Position::new(0, 11),
11693 lsp::Position::new(0, 12),
11694 ),
11695 severity: Some(lsp::DiagnosticSeverity::ERROR),
11696 message: message.to_string(),
11697 ..Default::default()
11698 }],
11699 },
11700 &[],
11701 cx,
11702 )
11703 .unwrap()
11704 });
11705 });
11706 executor.run_until_parked();
11707
11708 cx.update_editor(|editor, window, cx| {
11709 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11710 assert_eq!(
11711 editor
11712 .active_diagnostics
11713 .as_ref()
11714 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11715 Some(message),
11716 "Should have a diagnostics group activated"
11717 );
11718 });
11719 cx.assert_editor_state(indoc! {"
11720 fn func(abcˇ def: i32) -> u32 {
11721 }
11722 "});
11723
11724 cx.update(|_, cx| {
11725 lsp_store.update(cx, |lsp_store, cx| {
11726 lsp_store
11727 .update_diagnostics(
11728 LanguageServerId(0),
11729 lsp::PublishDiagnosticsParams {
11730 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11731 version: None,
11732 diagnostics: Vec::new(),
11733 },
11734 &[],
11735 cx,
11736 )
11737 .unwrap()
11738 });
11739 });
11740 executor.run_until_parked();
11741 cx.update_editor(|editor, _, _| {
11742 assert_eq!(
11743 editor.active_diagnostics, None,
11744 "After no diagnostics set to the editor, no diagnostics should be active"
11745 );
11746 });
11747 cx.assert_editor_state(indoc! {"
11748 fn func(abcˇ def: i32) -> u32 {
11749 }
11750 "});
11751
11752 cx.update_editor(|editor, window, cx| {
11753 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11754 assert_eq!(
11755 editor.active_diagnostics, None,
11756 "Should be no diagnostics to go to and activate"
11757 );
11758 });
11759 cx.assert_editor_state(indoc! {"
11760 fn func(abcˇ def: i32) -> u32 {
11761 }
11762 "});
11763}
11764
11765#[gpui::test]
11766async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11767 init_test(cx, |_| {});
11768
11769 let mut cx = EditorTestContext::new(cx).await;
11770
11771 cx.set_state(indoc! {"
11772 fn func(abˇc def: i32) -> u32 {
11773 }
11774 "});
11775 let lsp_store =
11776 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11777
11778 cx.update(|_, cx| {
11779 lsp_store.update(cx, |lsp_store, cx| {
11780 lsp_store.update_diagnostics(
11781 LanguageServerId(0),
11782 lsp::PublishDiagnosticsParams {
11783 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11784 version: None,
11785 diagnostics: vec![lsp::Diagnostic {
11786 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11787 severity: Some(lsp::DiagnosticSeverity::ERROR),
11788 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11789 ..Default::default()
11790 }],
11791 },
11792 &[],
11793 cx,
11794 )
11795 })
11796 }).unwrap();
11797 cx.run_until_parked();
11798 cx.update_editor(|editor, window, cx| {
11799 hover_popover::hover(editor, &Default::default(), window, cx)
11800 });
11801 cx.run_until_parked();
11802 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11803}
11804
11805#[gpui::test]
11806async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11807 init_test(cx, |_| {});
11808
11809 let mut cx = EditorTestContext::new(cx).await;
11810
11811 let diff_base = r#"
11812 use some::mod;
11813
11814 const A: u32 = 42;
11815
11816 fn main() {
11817 println!("hello");
11818
11819 println!("world");
11820 }
11821 "#
11822 .unindent();
11823
11824 // Edits are modified, removed, modified, added
11825 cx.set_state(
11826 &r#"
11827 use some::modified;
11828
11829 ˇ
11830 fn main() {
11831 println!("hello there");
11832
11833 println!("around the");
11834 println!("world");
11835 }
11836 "#
11837 .unindent(),
11838 );
11839
11840 cx.set_head_text(&diff_base);
11841 executor.run_until_parked();
11842
11843 cx.update_editor(|editor, window, cx| {
11844 //Wrap around the bottom of the buffer
11845 for _ in 0..3 {
11846 editor.go_to_next_hunk(&GoToHunk, window, cx);
11847 }
11848 });
11849
11850 cx.assert_editor_state(
11851 &r#"
11852 ˇuse some::modified;
11853
11854
11855 fn main() {
11856 println!("hello there");
11857
11858 println!("around the");
11859 println!("world");
11860 }
11861 "#
11862 .unindent(),
11863 );
11864
11865 cx.update_editor(|editor, window, cx| {
11866 //Wrap around the top of the buffer
11867 for _ in 0..2 {
11868 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11869 }
11870 });
11871
11872 cx.assert_editor_state(
11873 &r#"
11874 use some::modified;
11875
11876
11877 fn main() {
11878 ˇ println!("hello there");
11879
11880 println!("around the");
11881 println!("world");
11882 }
11883 "#
11884 .unindent(),
11885 );
11886
11887 cx.update_editor(|editor, window, cx| {
11888 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11889 });
11890
11891 cx.assert_editor_state(
11892 &r#"
11893 use some::modified;
11894
11895 ˇ
11896 fn main() {
11897 println!("hello there");
11898
11899 println!("around the");
11900 println!("world");
11901 }
11902 "#
11903 .unindent(),
11904 );
11905
11906 cx.update_editor(|editor, window, cx| {
11907 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11908 });
11909
11910 cx.assert_editor_state(
11911 &r#"
11912 ˇuse some::modified;
11913
11914
11915 fn main() {
11916 println!("hello there");
11917
11918 println!("around the");
11919 println!("world");
11920 }
11921 "#
11922 .unindent(),
11923 );
11924
11925 cx.update_editor(|editor, window, cx| {
11926 for _ in 0..2 {
11927 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11928 }
11929 });
11930
11931 cx.assert_editor_state(
11932 &r#"
11933 use some::modified;
11934
11935
11936 fn main() {
11937 ˇ println!("hello there");
11938
11939 println!("around the");
11940 println!("world");
11941 }
11942 "#
11943 .unindent(),
11944 );
11945
11946 cx.update_editor(|editor, window, cx| {
11947 editor.fold(&Fold, window, cx);
11948 });
11949
11950 cx.update_editor(|editor, window, cx| {
11951 editor.go_to_next_hunk(&GoToHunk, window, cx);
11952 });
11953
11954 cx.assert_editor_state(
11955 &r#"
11956 ˇuse some::modified;
11957
11958
11959 fn main() {
11960 println!("hello there");
11961
11962 println!("around the");
11963 println!("world");
11964 }
11965 "#
11966 .unindent(),
11967 );
11968}
11969
11970#[test]
11971fn test_split_words() {
11972 fn split(text: &str) -> Vec<&str> {
11973 split_words(text).collect()
11974 }
11975
11976 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11977 assert_eq!(split("hello_world"), &["hello_", "world"]);
11978 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11979 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11980 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11981 assert_eq!(split("helloworld"), &["helloworld"]);
11982
11983 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11984}
11985
11986#[gpui::test]
11987async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11988 init_test(cx, |_| {});
11989
11990 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11991 let mut assert = |before, after| {
11992 let _state_context = cx.set_state(before);
11993 cx.run_until_parked();
11994 cx.update_editor(|editor, window, cx| {
11995 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11996 });
11997 cx.run_until_parked();
11998 cx.assert_editor_state(after);
11999 };
12000
12001 // Outside bracket jumps to outside of matching bracket
12002 assert("console.logˇ(var);", "console.log(var)ˇ;");
12003 assert("console.log(var)ˇ;", "console.logˇ(var);");
12004
12005 // Inside bracket jumps to inside of matching bracket
12006 assert("console.log(ˇvar);", "console.log(varˇ);");
12007 assert("console.log(varˇ);", "console.log(ˇvar);");
12008
12009 // When outside a bracket and inside, favor jumping to the inside bracket
12010 assert(
12011 "console.log('foo', [1, 2, 3]ˇ);",
12012 "console.log(ˇ'foo', [1, 2, 3]);",
12013 );
12014 assert(
12015 "console.log(ˇ'foo', [1, 2, 3]);",
12016 "console.log('foo', [1, 2, 3]ˇ);",
12017 );
12018
12019 // Bias forward if two options are equally likely
12020 assert(
12021 "let result = curried_fun()ˇ();",
12022 "let result = curried_fun()()ˇ;",
12023 );
12024
12025 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
12026 assert(
12027 indoc! {"
12028 function test() {
12029 console.log('test')ˇ
12030 }"},
12031 indoc! {"
12032 function test() {
12033 console.logˇ('test')
12034 }"},
12035 );
12036}
12037
12038#[gpui::test]
12039async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
12040 init_test(cx, |_| {});
12041
12042 let fs = FakeFs::new(cx.executor());
12043 fs.insert_tree(
12044 path!("/a"),
12045 json!({
12046 "main.rs": "fn main() { let a = 5; }",
12047 "other.rs": "// Test file",
12048 }),
12049 )
12050 .await;
12051 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12052
12053 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12054 language_registry.add(Arc::new(Language::new(
12055 LanguageConfig {
12056 name: "Rust".into(),
12057 matcher: LanguageMatcher {
12058 path_suffixes: vec!["rs".to_string()],
12059 ..Default::default()
12060 },
12061 brackets: BracketPairConfig {
12062 pairs: vec![BracketPair {
12063 start: "{".to_string(),
12064 end: "}".to_string(),
12065 close: true,
12066 surround: true,
12067 newline: true,
12068 }],
12069 disabled_scopes_by_bracket_ix: Vec::new(),
12070 },
12071 ..Default::default()
12072 },
12073 Some(tree_sitter_rust::LANGUAGE.into()),
12074 )));
12075 let mut fake_servers = language_registry.register_fake_lsp(
12076 "Rust",
12077 FakeLspAdapter {
12078 capabilities: lsp::ServerCapabilities {
12079 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12080 first_trigger_character: "{".to_string(),
12081 more_trigger_character: None,
12082 }),
12083 ..Default::default()
12084 },
12085 ..Default::default()
12086 },
12087 );
12088
12089 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12090
12091 let cx = &mut VisualTestContext::from_window(*workspace, cx);
12092
12093 let worktree_id = workspace
12094 .update(cx, |workspace, _, cx| {
12095 workspace.project().update(cx, |project, cx| {
12096 project.worktrees(cx).next().unwrap().read(cx).id()
12097 })
12098 })
12099 .unwrap();
12100
12101 let buffer = project
12102 .update(cx, |project, cx| {
12103 project.open_local_buffer(path!("/a/main.rs"), cx)
12104 })
12105 .await
12106 .unwrap();
12107 let editor_handle = workspace
12108 .update(cx, |workspace, window, cx| {
12109 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12110 })
12111 .unwrap()
12112 .await
12113 .unwrap()
12114 .downcast::<Editor>()
12115 .unwrap();
12116
12117 cx.executor().start_waiting();
12118 let fake_server = fake_servers.next().await.unwrap();
12119
12120 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
12121 assert_eq!(
12122 params.text_document_position.text_document.uri,
12123 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12124 );
12125 assert_eq!(
12126 params.text_document_position.position,
12127 lsp::Position::new(0, 21),
12128 );
12129
12130 Ok(Some(vec![lsp::TextEdit {
12131 new_text: "]".to_string(),
12132 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12133 }]))
12134 });
12135
12136 editor_handle.update_in(cx, |editor, window, cx| {
12137 window.focus(&editor.focus_handle(cx));
12138 editor.change_selections(None, window, cx, |s| {
12139 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12140 });
12141 editor.handle_input("{", window, cx);
12142 });
12143
12144 cx.executor().run_until_parked();
12145
12146 buffer.update(cx, |buffer, _| {
12147 assert_eq!(
12148 buffer.text(),
12149 "fn main() { let a = {5}; }",
12150 "No extra braces from on type formatting should appear in the buffer"
12151 )
12152 });
12153}
12154
12155#[gpui::test]
12156async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12157 init_test(cx, |_| {});
12158
12159 let fs = FakeFs::new(cx.executor());
12160 fs.insert_tree(
12161 path!("/a"),
12162 json!({
12163 "main.rs": "fn main() { let a = 5; }",
12164 "other.rs": "// Test file",
12165 }),
12166 )
12167 .await;
12168
12169 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12170
12171 let server_restarts = Arc::new(AtomicUsize::new(0));
12172 let closure_restarts = Arc::clone(&server_restarts);
12173 let language_server_name = "test language server";
12174 let language_name: LanguageName = "Rust".into();
12175
12176 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12177 language_registry.add(Arc::new(Language::new(
12178 LanguageConfig {
12179 name: language_name.clone(),
12180 matcher: LanguageMatcher {
12181 path_suffixes: vec!["rs".to_string()],
12182 ..Default::default()
12183 },
12184 ..Default::default()
12185 },
12186 Some(tree_sitter_rust::LANGUAGE.into()),
12187 )));
12188 let mut fake_servers = language_registry.register_fake_lsp(
12189 "Rust",
12190 FakeLspAdapter {
12191 name: language_server_name,
12192 initialization_options: Some(json!({
12193 "testOptionValue": true
12194 })),
12195 initializer: Some(Box::new(move |fake_server| {
12196 let task_restarts = Arc::clone(&closure_restarts);
12197 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
12198 task_restarts.fetch_add(1, atomic::Ordering::Release);
12199 futures::future::ready(Ok(()))
12200 });
12201 })),
12202 ..Default::default()
12203 },
12204 );
12205
12206 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12207 let _buffer = project
12208 .update(cx, |project, cx| {
12209 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12210 })
12211 .await
12212 .unwrap();
12213 let _fake_server = fake_servers.next().await.unwrap();
12214 update_test_language_settings(cx, |language_settings| {
12215 language_settings.languages.insert(
12216 language_name.clone(),
12217 LanguageSettingsContent {
12218 tab_size: NonZeroU32::new(8),
12219 ..Default::default()
12220 },
12221 );
12222 });
12223 cx.executor().run_until_parked();
12224 assert_eq!(
12225 server_restarts.load(atomic::Ordering::Acquire),
12226 0,
12227 "Should not restart LSP server on an unrelated change"
12228 );
12229
12230 update_test_project_settings(cx, |project_settings| {
12231 project_settings.lsp.insert(
12232 "Some other server name".into(),
12233 LspSettings {
12234 binary: None,
12235 settings: None,
12236 initialization_options: Some(json!({
12237 "some other init value": false
12238 })),
12239 },
12240 );
12241 });
12242 cx.executor().run_until_parked();
12243 assert_eq!(
12244 server_restarts.load(atomic::Ordering::Acquire),
12245 0,
12246 "Should not restart LSP server on an unrelated LSP settings change"
12247 );
12248
12249 update_test_project_settings(cx, |project_settings| {
12250 project_settings.lsp.insert(
12251 language_server_name.into(),
12252 LspSettings {
12253 binary: None,
12254 settings: None,
12255 initialization_options: Some(json!({
12256 "anotherInitValue": false
12257 })),
12258 },
12259 );
12260 });
12261 cx.executor().run_until_parked();
12262 assert_eq!(
12263 server_restarts.load(atomic::Ordering::Acquire),
12264 1,
12265 "Should restart LSP server on a related LSP settings change"
12266 );
12267
12268 update_test_project_settings(cx, |project_settings| {
12269 project_settings.lsp.insert(
12270 language_server_name.into(),
12271 LspSettings {
12272 binary: None,
12273 settings: None,
12274 initialization_options: Some(json!({
12275 "anotherInitValue": false
12276 })),
12277 },
12278 );
12279 });
12280 cx.executor().run_until_parked();
12281 assert_eq!(
12282 server_restarts.load(atomic::Ordering::Acquire),
12283 1,
12284 "Should not restart LSP server on a related LSP settings change that is the same"
12285 );
12286
12287 update_test_project_settings(cx, |project_settings| {
12288 project_settings.lsp.insert(
12289 language_server_name.into(),
12290 LspSettings {
12291 binary: None,
12292 settings: None,
12293 initialization_options: None,
12294 },
12295 );
12296 });
12297 cx.executor().run_until_parked();
12298 assert_eq!(
12299 server_restarts.load(atomic::Ordering::Acquire),
12300 2,
12301 "Should restart LSP server on another related LSP settings change"
12302 );
12303}
12304
12305#[gpui::test]
12306async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12307 init_test(cx, |_| {});
12308
12309 let mut cx = EditorLspTestContext::new_rust(
12310 lsp::ServerCapabilities {
12311 completion_provider: Some(lsp::CompletionOptions {
12312 trigger_characters: Some(vec![".".to_string()]),
12313 resolve_provider: Some(true),
12314 ..Default::default()
12315 }),
12316 ..Default::default()
12317 },
12318 cx,
12319 )
12320 .await;
12321
12322 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12323 cx.simulate_keystroke(".");
12324 let completion_item = lsp::CompletionItem {
12325 label: "some".into(),
12326 kind: Some(lsp::CompletionItemKind::SNIPPET),
12327 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12328 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12329 kind: lsp::MarkupKind::Markdown,
12330 value: "```rust\nSome(2)\n```".to_string(),
12331 })),
12332 deprecated: Some(false),
12333 sort_text: Some("fffffff2".to_string()),
12334 filter_text: Some("some".to_string()),
12335 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12336 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12337 range: lsp::Range {
12338 start: lsp::Position {
12339 line: 0,
12340 character: 22,
12341 },
12342 end: lsp::Position {
12343 line: 0,
12344 character: 22,
12345 },
12346 },
12347 new_text: "Some(2)".to_string(),
12348 })),
12349 additional_text_edits: Some(vec![lsp::TextEdit {
12350 range: lsp::Range {
12351 start: lsp::Position {
12352 line: 0,
12353 character: 20,
12354 },
12355 end: lsp::Position {
12356 line: 0,
12357 character: 22,
12358 },
12359 },
12360 new_text: "".to_string(),
12361 }]),
12362 ..Default::default()
12363 };
12364
12365 let closure_completion_item = completion_item.clone();
12366 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12367 let task_completion_item = closure_completion_item.clone();
12368 async move {
12369 Ok(Some(lsp::CompletionResponse::Array(vec![
12370 task_completion_item,
12371 ])))
12372 }
12373 });
12374
12375 request.next().await;
12376
12377 cx.condition(|editor, _| editor.context_menu_visible())
12378 .await;
12379 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12380 editor
12381 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12382 .unwrap()
12383 });
12384 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12385
12386 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12387 let task_completion_item = completion_item.clone();
12388 async move { Ok(task_completion_item) }
12389 })
12390 .next()
12391 .await
12392 .unwrap();
12393 apply_additional_edits.await.unwrap();
12394 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12395}
12396
12397#[gpui::test]
12398async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12399 init_test(cx, |_| {});
12400
12401 let mut cx = EditorLspTestContext::new_rust(
12402 lsp::ServerCapabilities {
12403 completion_provider: Some(lsp::CompletionOptions {
12404 trigger_characters: Some(vec![".".to_string()]),
12405 resolve_provider: Some(true),
12406 ..Default::default()
12407 }),
12408 ..Default::default()
12409 },
12410 cx,
12411 )
12412 .await;
12413
12414 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12415 cx.simulate_keystroke(".");
12416
12417 let item1 = lsp::CompletionItem {
12418 label: "method id()".to_string(),
12419 filter_text: Some("id".to_string()),
12420 detail: None,
12421 documentation: None,
12422 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12423 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12424 new_text: ".id".to_string(),
12425 })),
12426 ..lsp::CompletionItem::default()
12427 };
12428
12429 let item2 = lsp::CompletionItem {
12430 label: "other".to_string(),
12431 filter_text: Some("other".to_string()),
12432 detail: None,
12433 documentation: None,
12434 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12435 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12436 new_text: ".other".to_string(),
12437 })),
12438 ..lsp::CompletionItem::default()
12439 };
12440
12441 let item1 = item1.clone();
12442 cx.handle_request::<lsp::request::Completion, _, _>({
12443 let item1 = item1.clone();
12444 move |_, _, _| {
12445 let item1 = item1.clone();
12446 let item2 = item2.clone();
12447 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12448 }
12449 })
12450 .next()
12451 .await;
12452
12453 cx.condition(|editor, _| editor.context_menu_visible())
12454 .await;
12455 cx.update_editor(|editor, _, _| {
12456 let context_menu = editor.context_menu.borrow_mut();
12457 let context_menu = context_menu
12458 .as_ref()
12459 .expect("Should have the context menu deployed");
12460 match context_menu {
12461 CodeContextMenu::Completions(completions_menu) => {
12462 let completions = completions_menu.completions.borrow_mut();
12463 assert_eq!(
12464 completions
12465 .iter()
12466 .map(|completion| &completion.label.text)
12467 .collect::<Vec<_>>(),
12468 vec!["method id()", "other"]
12469 )
12470 }
12471 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12472 }
12473 });
12474
12475 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12476 let item1 = item1.clone();
12477 move |_, item_to_resolve, _| {
12478 let item1 = item1.clone();
12479 async move {
12480 if item1 == item_to_resolve {
12481 Ok(lsp::CompletionItem {
12482 label: "method id()".to_string(),
12483 filter_text: Some("id".to_string()),
12484 detail: Some("Now resolved!".to_string()),
12485 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12486 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12487 range: lsp::Range::new(
12488 lsp::Position::new(0, 22),
12489 lsp::Position::new(0, 22),
12490 ),
12491 new_text: ".id".to_string(),
12492 })),
12493 ..lsp::CompletionItem::default()
12494 })
12495 } else {
12496 Ok(item_to_resolve)
12497 }
12498 }
12499 }
12500 })
12501 .next()
12502 .await
12503 .unwrap();
12504 cx.run_until_parked();
12505
12506 cx.update_editor(|editor, window, cx| {
12507 editor.context_menu_next(&Default::default(), window, cx);
12508 });
12509
12510 cx.update_editor(|editor, _, _| {
12511 let context_menu = editor.context_menu.borrow_mut();
12512 let context_menu = context_menu
12513 .as_ref()
12514 .expect("Should have the context menu deployed");
12515 match context_menu {
12516 CodeContextMenu::Completions(completions_menu) => {
12517 let completions = completions_menu.completions.borrow_mut();
12518 assert_eq!(
12519 completions
12520 .iter()
12521 .map(|completion| &completion.label.text)
12522 .collect::<Vec<_>>(),
12523 vec!["method id() Now resolved!", "other"],
12524 "Should update first completion label, but not second as the filter text did not match."
12525 );
12526 }
12527 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12528 }
12529 });
12530}
12531
12532#[gpui::test]
12533async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12534 init_test(cx, |_| {});
12535
12536 let mut cx = EditorLspTestContext::new_rust(
12537 lsp::ServerCapabilities {
12538 completion_provider: Some(lsp::CompletionOptions {
12539 trigger_characters: Some(vec![".".to_string()]),
12540 resolve_provider: Some(true),
12541 ..Default::default()
12542 }),
12543 ..Default::default()
12544 },
12545 cx,
12546 )
12547 .await;
12548
12549 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12550 cx.simulate_keystroke(".");
12551
12552 let unresolved_item_1 = lsp::CompletionItem {
12553 label: "id".to_string(),
12554 filter_text: Some("id".to_string()),
12555 detail: None,
12556 documentation: None,
12557 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12558 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12559 new_text: ".id".to_string(),
12560 })),
12561 ..lsp::CompletionItem::default()
12562 };
12563 let resolved_item_1 = lsp::CompletionItem {
12564 additional_text_edits: Some(vec![lsp::TextEdit {
12565 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12566 new_text: "!!".to_string(),
12567 }]),
12568 ..unresolved_item_1.clone()
12569 };
12570 let unresolved_item_2 = lsp::CompletionItem {
12571 label: "other".to_string(),
12572 filter_text: Some("other".to_string()),
12573 detail: None,
12574 documentation: None,
12575 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12576 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12577 new_text: ".other".to_string(),
12578 })),
12579 ..lsp::CompletionItem::default()
12580 };
12581 let resolved_item_2 = lsp::CompletionItem {
12582 additional_text_edits: Some(vec![lsp::TextEdit {
12583 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12584 new_text: "??".to_string(),
12585 }]),
12586 ..unresolved_item_2.clone()
12587 };
12588
12589 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12590 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12591 cx.lsp
12592 .server
12593 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12594 let unresolved_item_1 = unresolved_item_1.clone();
12595 let resolved_item_1 = resolved_item_1.clone();
12596 let unresolved_item_2 = unresolved_item_2.clone();
12597 let resolved_item_2 = resolved_item_2.clone();
12598 let resolve_requests_1 = resolve_requests_1.clone();
12599 let resolve_requests_2 = resolve_requests_2.clone();
12600 move |unresolved_request, _| {
12601 let unresolved_item_1 = unresolved_item_1.clone();
12602 let resolved_item_1 = resolved_item_1.clone();
12603 let unresolved_item_2 = unresolved_item_2.clone();
12604 let resolved_item_2 = resolved_item_2.clone();
12605 let resolve_requests_1 = resolve_requests_1.clone();
12606 let resolve_requests_2 = resolve_requests_2.clone();
12607 async move {
12608 if unresolved_request == unresolved_item_1 {
12609 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12610 Ok(resolved_item_1.clone())
12611 } else if unresolved_request == unresolved_item_2 {
12612 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12613 Ok(resolved_item_2.clone())
12614 } else {
12615 panic!("Unexpected completion item {unresolved_request:?}")
12616 }
12617 }
12618 }
12619 })
12620 .detach();
12621
12622 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12623 let unresolved_item_1 = unresolved_item_1.clone();
12624 let unresolved_item_2 = unresolved_item_2.clone();
12625 async move {
12626 Ok(Some(lsp::CompletionResponse::Array(vec![
12627 unresolved_item_1,
12628 unresolved_item_2,
12629 ])))
12630 }
12631 })
12632 .next()
12633 .await;
12634
12635 cx.condition(|editor, _| editor.context_menu_visible())
12636 .await;
12637 cx.update_editor(|editor, _, _| {
12638 let context_menu = editor.context_menu.borrow_mut();
12639 let context_menu = context_menu
12640 .as_ref()
12641 .expect("Should have the context menu deployed");
12642 match context_menu {
12643 CodeContextMenu::Completions(completions_menu) => {
12644 let completions = completions_menu.completions.borrow_mut();
12645 assert_eq!(
12646 completions
12647 .iter()
12648 .map(|completion| &completion.label.text)
12649 .collect::<Vec<_>>(),
12650 vec!["id", "other"]
12651 )
12652 }
12653 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12654 }
12655 });
12656 cx.run_until_parked();
12657
12658 cx.update_editor(|editor, window, cx| {
12659 editor.context_menu_next(&ContextMenuNext, window, cx);
12660 });
12661 cx.run_until_parked();
12662 cx.update_editor(|editor, window, cx| {
12663 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12664 });
12665 cx.run_until_parked();
12666 cx.update_editor(|editor, window, cx| {
12667 editor.context_menu_next(&ContextMenuNext, window, cx);
12668 });
12669 cx.run_until_parked();
12670 cx.update_editor(|editor, window, cx| {
12671 editor
12672 .compose_completion(&ComposeCompletion::default(), window, cx)
12673 .expect("No task returned")
12674 })
12675 .await
12676 .expect("Completion failed");
12677 cx.run_until_parked();
12678
12679 cx.update_editor(|editor, _, cx| {
12680 assert_eq!(
12681 resolve_requests_1.load(atomic::Ordering::Acquire),
12682 1,
12683 "Should always resolve once despite multiple selections"
12684 );
12685 assert_eq!(
12686 resolve_requests_2.load(atomic::Ordering::Acquire),
12687 1,
12688 "Should always resolve once after multiple selections and applying the completion"
12689 );
12690 assert_eq!(
12691 editor.text(cx),
12692 "fn main() { let a = ??.other; }",
12693 "Should use resolved data when applying the completion"
12694 );
12695 });
12696}
12697
12698#[gpui::test]
12699async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12700 init_test(cx, |_| {});
12701
12702 let item_0 = lsp::CompletionItem {
12703 label: "abs".into(),
12704 insert_text: Some("abs".into()),
12705 data: Some(json!({ "very": "special"})),
12706 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12707 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12708 lsp::InsertReplaceEdit {
12709 new_text: "abs".to_string(),
12710 insert: lsp::Range::default(),
12711 replace: lsp::Range::default(),
12712 },
12713 )),
12714 ..lsp::CompletionItem::default()
12715 };
12716 let items = iter::once(item_0.clone())
12717 .chain((11..51).map(|i| lsp::CompletionItem {
12718 label: format!("item_{}", i),
12719 insert_text: Some(format!("item_{}", i)),
12720 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12721 ..lsp::CompletionItem::default()
12722 }))
12723 .collect::<Vec<_>>();
12724
12725 let default_commit_characters = vec!["?".to_string()];
12726 let default_data = json!({ "default": "data"});
12727 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12728 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12729 let default_edit_range = lsp::Range {
12730 start: lsp::Position {
12731 line: 0,
12732 character: 5,
12733 },
12734 end: lsp::Position {
12735 line: 0,
12736 character: 5,
12737 },
12738 };
12739
12740 let mut cx = EditorLspTestContext::new_rust(
12741 lsp::ServerCapabilities {
12742 completion_provider: Some(lsp::CompletionOptions {
12743 trigger_characters: Some(vec![".".to_string()]),
12744 resolve_provider: Some(true),
12745 ..Default::default()
12746 }),
12747 ..Default::default()
12748 },
12749 cx,
12750 )
12751 .await;
12752
12753 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12754 cx.simulate_keystroke(".");
12755
12756 let completion_data = default_data.clone();
12757 let completion_characters = default_commit_characters.clone();
12758 let completion_items = items.clone();
12759 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12760 let default_data = completion_data.clone();
12761 let default_commit_characters = completion_characters.clone();
12762 let items = completion_items.clone();
12763 async move {
12764 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12765 items,
12766 item_defaults: Some(lsp::CompletionListItemDefaults {
12767 data: Some(default_data.clone()),
12768 commit_characters: Some(default_commit_characters.clone()),
12769 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12770 default_edit_range,
12771 )),
12772 insert_text_format: Some(default_insert_text_format),
12773 insert_text_mode: Some(default_insert_text_mode),
12774 }),
12775 ..lsp::CompletionList::default()
12776 })))
12777 }
12778 })
12779 .next()
12780 .await;
12781
12782 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12783 cx.lsp
12784 .server
12785 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12786 let closure_resolved_items = resolved_items.clone();
12787 move |item_to_resolve, _| {
12788 let closure_resolved_items = closure_resolved_items.clone();
12789 async move {
12790 closure_resolved_items.lock().push(item_to_resolve.clone());
12791 Ok(item_to_resolve)
12792 }
12793 }
12794 })
12795 .detach();
12796
12797 cx.condition(|editor, _| editor.context_menu_visible())
12798 .await;
12799 cx.run_until_parked();
12800 cx.update_editor(|editor, _, _| {
12801 let menu = editor.context_menu.borrow_mut();
12802 match menu.as_ref().expect("should have the completions menu") {
12803 CodeContextMenu::Completions(completions_menu) => {
12804 assert_eq!(
12805 completions_menu
12806 .entries
12807 .borrow()
12808 .iter()
12809 .map(|mat| mat.string.clone())
12810 .collect::<Vec<String>>(),
12811 items
12812 .iter()
12813 .map(|completion| completion.label.clone())
12814 .collect::<Vec<String>>()
12815 );
12816 }
12817 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12818 }
12819 });
12820 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12821 // with 4 from the end.
12822 assert_eq!(
12823 *resolved_items.lock(),
12824 [&items[0..16], &items[items.len() - 4..items.len()]]
12825 .concat()
12826 .iter()
12827 .cloned()
12828 .map(|mut item| {
12829 if item.data.is_none() {
12830 item.data = Some(default_data.clone());
12831 }
12832 item
12833 })
12834 .collect::<Vec<lsp::CompletionItem>>(),
12835 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12836 );
12837 resolved_items.lock().clear();
12838
12839 cx.update_editor(|editor, window, cx| {
12840 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12841 });
12842 cx.run_until_parked();
12843 // Completions that have already been resolved are skipped.
12844 assert_eq!(
12845 *resolved_items.lock(),
12846 items[items.len() - 16..items.len() - 4]
12847 .iter()
12848 .cloned()
12849 .map(|mut item| {
12850 if item.data.is_none() {
12851 item.data = Some(default_data.clone());
12852 }
12853 item
12854 })
12855 .collect::<Vec<lsp::CompletionItem>>()
12856 );
12857 resolved_items.lock().clear();
12858}
12859
12860#[gpui::test]
12861async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12862 init_test(cx, |_| {});
12863
12864 let mut cx = EditorLspTestContext::new(
12865 Language::new(
12866 LanguageConfig {
12867 matcher: LanguageMatcher {
12868 path_suffixes: vec!["jsx".into()],
12869 ..Default::default()
12870 },
12871 overrides: [(
12872 "element".into(),
12873 LanguageConfigOverride {
12874 word_characters: Override::Set(['-'].into_iter().collect()),
12875 ..Default::default()
12876 },
12877 )]
12878 .into_iter()
12879 .collect(),
12880 ..Default::default()
12881 },
12882 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12883 )
12884 .with_override_query("(jsx_self_closing_element) @element")
12885 .unwrap(),
12886 lsp::ServerCapabilities {
12887 completion_provider: Some(lsp::CompletionOptions {
12888 trigger_characters: Some(vec![":".to_string()]),
12889 ..Default::default()
12890 }),
12891 ..Default::default()
12892 },
12893 cx,
12894 )
12895 .await;
12896
12897 cx.lsp
12898 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12899 Ok(Some(lsp::CompletionResponse::Array(vec![
12900 lsp::CompletionItem {
12901 label: "bg-blue".into(),
12902 ..Default::default()
12903 },
12904 lsp::CompletionItem {
12905 label: "bg-red".into(),
12906 ..Default::default()
12907 },
12908 lsp::CompletionItem {
12909 label: "bg-yellow".into(),
12910 ..Default::default()
12911 },
12912 ])))
12913 });
12914
12915 cx.set_state(r#"<p class="bgˇ" />"#);
12916
12917 // Trigger completion when typing a dash, because the dash is an extra
12918 // word character in the 'element' scope, which contains the cursor.
12919 cx.simulate_keystroke("-");
12920 cx.executor().run_until_parked();
12921 cx.update_editor(|editor, _, _| {
12922 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12923 {
12924 assert_eq!(
12925 completion_menu_entries(&menu),
12926 &["bg-red", "bg-blue", "bg-yellow"]
12927 );
12928 } else {
12929 panic!("expected completion menu to be open");
12930 }
12931 });
12932
12933 cx.simulate_keystroke("l");
12934 cx.executor().run_until_parked();
12935 cx.update_editor(|editor, _, _| {
12936 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12937 {
12938 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12939 } else {
12940 panic!("expected completion menu to be open");
12941 }
12942 });
12943
12944 // When filtering completions, consider the character after the '-' to
12945 // be the start of a subword.
12946 cx.set_state(r#"<p class="yelˇ" />"#);
12947 cx.simulate_keystroke("l");
12948 cx.executor().run_until_parked();
12949 cx.update_editor(|editor, _, _| {
12950 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12951 {
12952 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12953 } else {
12954 panic!("expected completion menu to be open");
12955 }
12956 });
12957}
12958
12959fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12960 let entries = menu.entries.borrow();
12961 entries.iter().map(|mat| mat.string.clone()).collect()
12962}
12963
12964#[gpui::test]
12965async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12966 init_test(cx, |settings| {
12967 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12968 FormatterList(vec![Formatter::Prettier].into()),
12969 ))
12970 });
12971
12972 let fs = FakeFs::new(cx.executor());
12973 fs.insert_file(path!("/file.ts"), Default::default()).await;
12974
12975 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12976 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12977
12978 language_registry.add(Arc::new(Language::new(
12979 LanguageConfig {
12980 name: "TypeScript".into(),
12981 matcher: LanguageMatcher {
12982 path_suffixes: vec!["ts".to_string()],
12983 ..Default::default()
12984 },
12985 ..Default::default()
12986 },
12987 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12988 )));
12989 update_test_language_settings(cx, |settings| {
12990 settings.defaults.prettier = Some(PrettierSettings {
12991 allowed: true,
12992 ..PrettierSettings::default()
12993 });
12994 });
12995
12996 let test_plugin = "test_plugin";
12997 let _ = language_registry.register_fake_lsp(
12998 "TypeScript",
12999 FakeLspAdapter {
13000 prettier_plugins: vec![test_plugin],
13001 ..Default::default()
13002 },
13003 );
13004
13005 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
13006 let buffer = project
13007 .update(cx, |project, cx| {
13008 project.open_local_buffer(path!("/file.ts"), cx)
13009 })
13010 .await
13011 .unwrap();
13012
13013 let buffer_text = "one\ntwo\nthree\n";
13014 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13015 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13016 editor.update_in(cx, |editor, window, cx| {
13017 editor.set_text(buffer_text, window, cx)
13018 });
13019
13020 editor
13021 .update_in(cx, |editor, window, cx| {
13022 editor.perform_format(
13023 project.clone(),
13024 FormatTrigger::Manual,
13025 FormatTarget::Buffers,
13026 window,
13027 cx,
13028 )
13029 })
13030 .unwrap()
13031 .await;
13032 assert_eq!(
13033 editor.update(cx, |editor, cx| editor.text(cx)),
13034 buffer_text.to_string() + prettier_format_suffix,
13035 "Test prettier formatting was not applied to the original buffer text",
13036 );
13037
13038 update_test_language_settings(cx, |settings| {
13039 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
13040 });
13041 let format = editor.update_in(cx, |editor, window, cx| {
13042 editor.perform_format(
13043 project.clone(),
13044 FormatTrigger::Manual,
13045 FormatTarget::Buffers,
13046 window,
13047 cx,
13048 )
13049 });
13050 format.await.unwrap();
13051 assert_eq!(
13052 editor.update(cx, |editor, cx| editor.text(cx)),
13053 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
13054 "Autoformatting (via test prettier) was not applied to the original buffer text",
13055 );
13056}
13057
13058#[gpui::test]
13059async fn test_addition_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 // When addition hunks are not adjacent to carets, no hunk revert is performed
13076 assert_hunk_revert(
13077 indoc! {r#"struct Row;
13078 struct Row1;
13079 struct Row1.1;
13080 struct Row1.2;
13081 struct Row2;ˇ
13082
13083 struct Row4;
13084 struct Row5;
13085 struct Row6;
13086
13087 struct Row8;
13088 ˇstruct Row9;
13089 struct Row9.1;
13090 struct Row9.2;
13091 struct Row9.3;
13092 struct Row10;"#},
13093 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13094 indoc! {r#"struct Row;
13095 struct Row1;
13096 struct Row1.1;
13097 struct Row1.2;
13098 struct Row2;ˇ
13099
13100 struct Row4;
13101 struct Row5;
13102 struct Row6;
13103
13104 struct Row8;
13105 ˇstruct Row9;
13106 struct Row9.1;
13107 struct Row9.2;
13108 struct Row9.3;
13109 struct Row10;"#},
13110 base_text,
13111 &mut cx,
13112 );
13113 // Same for selections
13114 assert_hunk_revert(
13115 indoc! {r#"struct Row;
13116 struct Row1;
13117 struct Row2;
13118 struct Row2.1;
13119 struct Row2.2;
13120 «ˇ
13121 struct Row4;
13122 struct» Row5;
13123 «struct Row6;
13124 ˇ»
13125 struct Row9.1;
13126 struct Row9.2;
13127 struct Row9.3;
13128 struct Row8;
13129 struct Row9;
13130 struct Row10;"#},
13131 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13132 indoc! {r#"struct Row;
13133 struct Row1;
13134 struct Row2;
13135 struct Row2.1;
13136 struct Row2.2;
13137 «ˇ
13138 struct Row4;
13139 struct» Row5;
13140 «struct Row6;
13141 ˇ»
13142 struct Row9.1;
13143 struct Row9.2;
13144 struct Row9.3;
13145 struct Row8;
13146 struct Row9;
13147 struct Row10;"#},
13148 base_text,
13149 &mut cx,
13150 );
13151
13152 // When carets and selections intersect the addition hunks, those are reverted.
13153 // Adjacent carets got merged.
13154 assert_hunk_revert(
13155 indoc! {r#"struct Row;
13156 ˇ// something on the top
13157 struct Row1;
13158 struct Row2;
13159 struct Roˇw3.1;
13160 struct Row2.2;
13161 struct Row2.3;ˇ
13162
13163 struct Row4;
13164 struct ˇRow5.1;
13165 struct Row5.2;
13166 struct «Rowˇ»5.3;
13167 struct Row5;
13168 struct Row6;
13169 ˇ
13170 struct Row9.1;
13171 struct «Rowˇ»9.2;
13172 struct «ˇRow»9.3;
13173 struct Row8;
13174 struct Row9;
13175 «ˇ// something on bottom»
13176 struct Row10;"#},
13177 vec![
13178 DiffHunkStatusKind::Added,
13179 DiffHunkStatusKind::Added,
13180 DiffHunkStatusKind::Added,
13181 DiffHunkStatusKind::Added,
13182 DiffHunkStatusKind::Added,
13183 ],
13184 indoc! {r#"struct Row;
13185 ˇstruct Row1;
13186 struct Row2;
13187 ˇ
13188 struct Row4;
13189 ˇstruct Row5;
13190 struct Row6;
13191 ˇ
13192 ˇstruct Row8;
13193 struct Row9;
13194 ˇstruct Row10;"#},
13195 base_text,
13196 &mut cx,
13197 );
13198}
13199
13200#[gpui::test]
13201async fn test_modification_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#"
13205 struct Row;
13206 struct Row1;
13207 struct Row2;
13208
13209 struct Row4;
13210 struct Row5;
13211 struct Row6;
13212
13213 struct Row8;
13214 struct Row9;
13215 struct Row10;"#};
13216
13217 // Modification hunks behave the same as the addition ones.
13218 assert_hunk_revert(
13219 indoc! {r#"struct Row;
13220 struct Row1;
13221 struct Row33;
13222 ˇ
13223 struct Row4;
13224 struct Row5;
13225 struct Row6;
13226 ˇ
13227 struct Row99;
13228 struct Row9;
13229 struct Row10;"#},
13230 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13231 indoc! {r#"struct Row;
13232 struct Row1;
13233 struct Row33;
13234 ˇ
13235 struct Row4;
13236 struct Row5;
13237 struct Row6;
13238 ˇ
13239 struct Row99;
13240 struct Row9;
13241 struct Row10;"#},
13242 base_text,
13243 &mut cx,
13244 );
13245 assert_hunk_revert(
13246 indoc! {r#"struct Row;
13247 struct Row1;
13248 struct Row33;
13249 «ˇ
13250 struct Row4;
13251 struct» Row5;
13252 «struct Row6;
13253 ˇ»
13254 struct Row99;
13255 struct Row9;
13256 struct Row10;"#},
13257 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13258 indoc! {r#"struct Row;
13259 struct Row1;
13260 struct Row33;
13261 «ˇ
13262 struct Row4;
13263 struct» Row5;
13264 «struct Row6;
13265 ˇ»
13266 struct Row99;
13267 struct Row9;
13268 struct Row10;"#},
13269 base_text,
13270 &mut cx,
13271 );
13272
13273 assert_hunk_revert(
13274 indoc! {r#"ˇstruct Row1.1;
13275 struct Row1;
13276 «ˇstr»uct Row22;
13277
13278 struct ˇRow44;
13279 struct Row5;
13280 struct «Rˇ»ow66;ˇ
13281
13282 «struˇ»ct Row88;
13283 struct Row9;
13284 struct Row1011;ˇ"#},
13285 vec![
13286 DiffHunkStatusKind::Modified,
13287 DiffHunkStatusKind::Modified,
13288 DiffHunkStatusKind::Modified,
13289 DiffHunkStatusKind::Modified,
13290 DiffHunkStatusKind::Modified,
13291 DiffHunkStatusKind::Modified,
13292 ],
13293 indoc! {r#"struct Row;
13294 ˇstruct Row1;
13295 struct Row2;
13296 ˇ
13297 struct Row4;
13298 ˇstruct Row5;
13299 struct Row6;
13300 ˇ
13301 struct Row8;
13302 ˇstruct Row9;
13303 struct Row10;ˇ"#},
13304 base_text,
13305 &mut cx,
13306 );
13307}
13308
13309#[gpui::test]
13310async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13311 init_test(cx, |_| {});
13312 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13313 let base_text = indoc! {r#"
13314 one
13315
13316 two
13317 three
13318 "#};
13319
13320 cx.set_head_text(base_text);
13321 cx.set_state("\nˇ\n");
13322 cx.executor().run_until_parked();
13323 cx.update_editor(|editor, _window, cx| {
13324 editor.expand_selected_diff_hunks(cx);
13325 });
13326 cx.executor().run_until_parked();
13327 cx.update_editor(|editor, window, cx| {
13328 editor.backspace(&Default::default(), window, cx);
13329 });
13330 cx.run_until_parked();
13331 cx.assert_state_with_diff(
13332 indoc! {r#"
13333
13334 - two
13335 - threeˇ
13336 +
13337 "#}
13338 .to_string(),
13339 );
13340}
13341
13342#[gpui::test]
13343async fn test_deletion_reverts(cx: &mut TestAppContext) {
13344 init_test(cx, |_| {});
13345 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13346 let base_text = indoc! {r#"struct Row;
13347struct Row1;
13348struct Row2;
13349
13350struct Row4;
13351struct Row5;
13352struct Row6;
13353
13354struct Row8;
13355struct Row9;
13356struct Row10;"#};
13357
13358 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13359 assert_hunk_revert(
13360 indoc! {r#"struct Row;
13361 struct Row2;
13362
13363 ˇstruct Row4;
13364 struct Row5;
13365 struct Row6;
13366 ˇ
13367 struct Row8;
13368 struct Row10;"#},
13369 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13370 indoc! {r#"struct Row;
13371 struct Row2;
13372
13373 ˇstruct Row4;
13374 struct Row5;
13375 struct Row6;
13376 ˇ
13377 struct Row8;
13378 struct Row10;"#},
13379 base_text,
13380 &mut cx,
13381 );
13382 assert_hunk_revert(
13383 indoc! {r#"struct Row;
13384 struct Row2;
13385
13386 «ˇstruct Row4;
13387 struct» Row5;
13388 «struct Row6;
13389 ˇ»
13390 struct Row8;
13391 struct Row10;"#},
13392 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13393 indoc! {r#"struct Row;
13394 struct Row2;
13395
13396 «ˇstruct Row4;
13397 struct» Row5;
13398 «struct Row6;
13399 ˇ»
13400 struct Row8;
13401 struct Row10;"#},
13402 base_text,
13403 &mut cx,
13404 );
13405
13406 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13407 assert_hunk_revert(
13408 indoc! {r#"struct Row;
13409 ˇstruct Row2;
13410
13411 struct Row4;
13412 struct Row5;
13413 struct Row6;
13414
13415 struct Row8;ˇ
13416 struct Row10;"#},
13417 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13418 indoc! {r#"struct Row;
13419 struct Row1;
13420 ˇstruct Row2;
13421
13422 struct Row4;
13423 struct Row5;
13424 struct Row6;
13425
13426 struct Row8;ˇ
13427 struct Row9;
13428 struct Row10;"#},
13429 base_text,
13430 &mut cx,
13431 );
13432 assert_hunk_revert(
13433 indoc! {r#"struct Row;
13434 struct Row2«ˇ;
13435 struct Row4;
13436 struct» Row5;
13437 «struct Row6;
13438
13439 struct Row8;ˇ»
13440 struct Row10;"#},
13441 vec![
13442 DiffHunkStatusKind::Deleted,
13443 DiffHunkStatusKind::Deleted,
13444 DiffHunkStatusKind::Deleted,
13445 ],
13446 indoc! {r#"struct Row;
13447 struct Row1;
13448 struct Row2«ˇ;
13449
13450 struct Row4;
13451 struct» Row5;
13452 «struct Row6;
13453
13454 struct Row8;ˇ»
13455 struct Row9;
13456 struct Row10;"#},
13457 base_text,
13458 &mut cx,
13459 );
13460}
13461
13462#[gpui::test]
13463async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13464 init_test(cx, |_| {});
13465
13466 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13467 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13468 let base_text_3 =
13469 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13470
13471 let text_1 = edit_first_char_of_every_line(base_text_1);
13472 let text_2 = edit_first_char_of_every_line(base_text_2);
13473 let text_3 = edit_first_char_of_every_line(base_text_3);
13474
13475 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13476 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13477 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13478
13479 let multibuffer = cx.new(|cx| {
13480 let mut multibuffer = MultiBuffer::new(ReadWrite);
13481 multibuffer.push_excerpts(
13482 buffer_1.clone(),
13483 [
13484 ExcerptRange {
13485 context: Point::new(0, 0)..Point::new(3, 0),
13486 primary: None,
13487 },
13488 ExcerptRange {
13489 context: Point::new(5, 0)..Point::new(7, 0),
13490 primary: None,
13491 },
13492 ExcerptRange {
13493 context: Point::new(9, 0)..Point::new(10, 4),
13494 primary: None,
13495 },
13496 ],
13497 cx,
13498 );
13499 multibuffer.push_excerpts(
13500 buffer_2.clone(),
13501 [
13502 ExcerptRange {
13503 context: Point::new(0, 0)..Point::new(3, 0),
13504 primary: None,
13505 },
13506 ExcerptRange {
13507 context: Point::new(5, 0)..Point::new(7, 0),
13508 primary: None,
13509 },
13510 ExcerptRange {
13511 context: Point::new(9, 0)..Point::new(10, 4),
13512 primary: None,
13513 },
13514 ],
13515 cx,
13516 );
13517 multibuffer.push_excerpts(
13518 buffer_3.clone(),
13519 [
13520 ExcerptRange {
13521 context: Point::new(0, 0)..Point::new(3, 0),
13522 primary: None,
13523 },
13524 ExcerptRange {
13525 context: Point::new(5, 0)..Point::new(7, 0),
13526 primary: None,
13527 },
13528 ExcerptRange {
13529 context: Point::new(9, 0)..Point::new(10, 4),
13530 primary: None,
13531 },
13532 ],
13533 cx,
13534 );
13535 multibuffer
13536 });
13537
13538 let fs = FakeFs::new(cx.executor());
13539 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13540 let (editor, cx) = cx
13541 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13542 editor.update_in(cx, |editor, _window, cx| {
13543 for (buffer, diff_base) in [
13544 (buffer_1.clone(), base_text_1),
13545 (buffer_2.clone(), base_text_2),
13546 (buffer_3.clone(), base_text_3),
13547 ] {
13548 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13549 editor
13550 .buffer
13551 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13552 }
13553 });
13554 cx.executor().run_until_parked();
13555
13556 editor.update_in(cx, |editor, window, cx| {
13557 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}");
13558 editor.select_all(&SelectAll, window, cx);
13559 editor.git_restore(&Default::default(), window, cx);
13560 });
13561 cx.executor().run_until_parked();
13562
13563 // When all ranges are selected, all buffer hunks are reverted.
13564 editor.update(cx, |editor, cx| {
13565 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");
13566 });
13567 buffer_1.update(cx, |buffer, _| {
13568 assert_eq!(buffer.text(), base_text_1);
13569 });
13570 buffer_2.update(cx, |buffer, _| {
13571 assert_eq!(buffer.text(), base_text_2);
13572 });
13573 buffer_3.update(cx, |buffer, _| {
13574 assert_eq!(buffer.text(), base_text_3);
13575 });
13576
13577 editor.update_in(cx, |editor, window, cx| {
13578 editor.undo(&Default::default(), window, cx);
13579 });
13580
13581 editor.update_in(cx, |editor, window, cx| {
13582 editor.change_selections(None, window, cx, |s| {
13583 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13584 });
13585 editor.git_restore(&Default::default(), window, cx);
13586 });
13587
13588 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13589 // but not affect buffer_2 and its related excerpts.
13590 editor.update(cx, |editor, cx| {
13591 assert_eq!(
13592 editor.text(cx),
13593 "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}"
13594 );
13595 });
13596 buffer_1.update(cx, |buffer, _| {
13597 assert_eq!(buffer.text(), base_text_1);
13598 });
13599 buffer_2.update(cx, |buffer, _| {
13600 assert_eq!(
13601 buffer.text(),
13602 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13603 );
13604 });
13605 buffer_3.update(cx, |buffer, _| {
13606 assert_eq!(
13607 buffer.text(),
13608 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13609 );
13610 });
13611
13612 fn edit_first_char_of_every_line(text: &str) -> String {
13613 text.split('\n')
13614 .map(|line| format!("X{}", &line[1..]))
13615 .collect::<Vec<_>>()
13616 .join("\n")
13617 }
13618}
13619
13620#[gpui::test]
13621async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13622 init_test(cx, |_| {});
13623
13624 let cols = 4;
13625 let rows = 10;
13626 let sample_text_1 = sample_text(rows, cols, 'a');
13627 assert_eq!(
13628 sample_text_1,
13629 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13630 );
13631 let sample_text_2 = sample_text(rows, cols, 'l');
13632 assert_eq!(
13633 sample_text_2,
13634 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13635 );
13636 let sample_text_3 = sample_text(rows, cols, 'v');
13637 assert_eq!(
13638 sample_text_3,
13639 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13640 );
13641
13642 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13643 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13644 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13645
13646 let multi_buffer = cx.new(|cx| {
13647 let mut multibuffer = MultiBuffer::new(ReadWrite);
13648 multibuffer.push_excerpts(
13649 buffer_1.clone(),
13650 [
13651 ExcerptRange {
13652 context: Point::new(0, 0)..Point::new(3, 0),
13653 primary: None,
13654 },
13655 ExcerptRange {
13656 context: Point::new(5, 0)..Point::new(7, 0),
13657 primary: None,
13658 },
13659 ExcerptRange {
13660 context: Point::new(9, 0)..Point::new(10, 4),
13661 primary: None,
13662 },
13663 ],
13664 cx,
13665 );
13666 multibuffer.push_excerpts(
13667 buffer_2.clone(),
13668 [
13669 ExcerptRange {
13670 context: Point::new(0, 0)..Point::new(3, 0),
13671 primary: None,
13672 },
13673 ExcerptRange {
13674 context: Point::new(5, 0)..Point::new(7, 0),
13675 primary: None,
13676 },
13677 ExcerptRange {
13678 context: Point::new(9, 0)..Point::new(10, 4),
13679 primary: None,
13680 },
13681 ],
13682 cx,
13683 );
13684 multibuffer.push_excerpts(
13685 buffer_3.clone(),
13686 [
13687 ExcerptRange {
13688 context: Point::new(0, 0)..Point::new(3, 0),
13689 primary: None,
13690 },
13691 ExcerptRange {
13692 context: Point::new(5, 0)..Point::new(7, 0),
13693 primary: None,
13694 },
13695 ExcerptRange {
13696 context: Point::new(9, 0)..Point::new(10, 4),
13697 primary: None,
13698 },
13699 ],
13700 cx,
13701 );
13702 multibuffer
13703 });
13704
13705 let fs = FakeFs::new(cx.executor());
13706 fs.insert_tree(
13707 "/a",
13708 json!({
13709 "main.rs": sample_text_1,
13710 "other.rs": sample_text_2,
13711 "lib.rs": sample_text_3,
13712 }),
13713 )
13714 .await;
13715 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13716 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13717 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13718 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13719 Editor::new(
13720 EditorMode::Full,
13721 multi_buffer,
13722 Some(project.clone()),
13723 window,
13724 cx,
13725 )
13726 });
13727 let multibuffer_item_id = workspace
13728 .update(cx, |workspace, window, cx| {
13729 assert!(
13730 workspace.active_item(cx).is_none(),
13731 "active item should be None before the first item is added"
13732 );
13733 workspace.add_item_to_active_pane(
13734 Box::new(multi_buffer_editor.clone()),
13735 None,
13736 true,
13737 window,
13738 cx,
13739 );
13740 let active_item = workspace
13741 .active_item(cx)
13742 .expect("should have an active item after adding the multi buffer");
13743 assert!(
13744 !active_item.is_singleton(cx),
13745 "A multi buffer was expected to active after adding"
13746 );
13747 active_item.item_id()
13748 })
13749 .unwrap();
13750 cx.executor().run_until_parked();
13751
13752 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13753 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13754 s.select_ranges(Some(1..2))
13755 });
13756 editor.open_excerpts(&OpenExcerpts, window, cx);
13757 });
13758 cx.executor().run_until_parked();
13759 let first_item_id = workspace
13760 .update(cx, |workspace, window, cx| {
13761 let active_item = workspace
13762 .active_item(cx)
13763 .expect("should have an active item after navigating into the 1st buffer");
13764 let first_item_id = active_item.item_id();
13765 assert_ne!(
13766 first_item_id, multibuffer_item_id,
13767 "Should navigate into the 1st buffer and activate it"
13768 );
13769 assert!(
13770 active_item.is_singleton(cx),
13771 "New active item should be a singleton buffer"
13772 );
13773 assert_eq!(
13774 active_item
13775 .act_as::<Editor>(cx)
13776 .expect("should have navigated into an editor for the 1st buffer")
13777 .read(cx)
13778 .text(cx),
13779 sample_text_1
13780 );
13781
13782 workspace
13783 .go_back(workspace.active_pane().downgrade(), window, cx)
13784 .detach_and_log_err(cx);
13785
13786 first_item_id
13787 })
13788 .unwrap();
13789 cx.executor().run_until_parked();
13790 workspace
13791 .update(cx, |workspace, _, cx| {
13792 let active_item = workspace
13793 .active_item(cx)
13794 .expect("should have an active item after navigating back");
13795 assert_eq!(
13796 active_item.item_id(),
13797 multibuffer_item_id,
13798 "Should navigate back to the multi buffer"
13799 );
13800 assert!(!active_item.is_singleton(cx));
13801 })
13802 .unwrap();
13803
13804 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13805 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13806 s.select_ranges(Some(39..40))
13807 });
13808 editor.open_excerpts(&OpenExcerpts, window, cx);
13809 });
13810 cx.executor().run_until_parked();
13811 let second_item_id = workspace
13812 .update(cx, |workspace, window, cx| {
13813 let active_item = workspace
13814 .active_item(cx)
13815 .expect("should have an active item after navigating into the 2nd buffer");
13816 let second_item_id = active_item.item_id();
13817 assert_ne!(
13818 second_item_id, multibuffer_item_id,
13819 "Should navigate away from the multibuffer"
13820 );
13821 assert_ne!(
13822 second_item_id, first_item_id,
13823 "Should navigate into the 2nd buffer and activate it"
13824 );
13825 assert!(
13826 active_item.is_singleton(cx),
13827 "New active item should be a singleton buffer"
13828 );
13829 assert_eq!(
13830 active_item
13831 .act_as::<Editor>(cx)
13832 .expect("should have navigated into an editor")
13833 .read(cx)
13834 .text(cx),
13835 sample_text_2
13836 );
13837
13838 workspace
13839 .go_back(workspace.active_pane().downgrade(), window, cx)
13840 .detach_and_log_err(cx);
13841
13842 second_item_id
13843 })
13844 .unwrap();
13845 cx.executor().run_until_parked();
13846 workspace
13847 .update(cx, |workspace, _, cx| {
13848 let active_item = workspace
13849 .active_item(cx)
13850 .expect("should have an active item after navigating back from the 2nd buffer");
13851 assert_eq!(
13852 active_item.item_id(),
13853 multibuffer_item_id,
13854 "Should navigate back from the 2nd buffer to the multi buffer"
13855 );
13856 assert!(!active_item.is_singleton(cx));
13857 })
13858 .unwrap();
13859
13860 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13861 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13862 s.select_ranges(Some(70..70))
13863 });
13864 editor.open_excerpts(&OpenExcerpts, window, cx);
13865 });
13866 cx.executor().run_until_parked();
13867 workspace
13868 .update(cx, |workspace, window, cx| {
13869 let active_item = workspace
13870 .active_item(cx)
13871 .expect("should have an active item after navigating into the 3rd buffer");
13872 let third_item_id = active_item.item_id();
13873 assert_ne!(
13874 third_item_id, multibuffer_item_id,
13875 "Should navigate into the 3rd buffer and activate it"
13876 );
13877 assert_ne!(third_item_id, first_item_id);
13878 assert_ne!(third_item_id, second_item_id);
13879 assert!(
13880 active_item.is_singleton(cx),
13881 "New active item should be a singleton buffer"
13882 );
13883 assert_eq!(
13884 active_item
13885 .act_as::<Editor>(cx)
13886 .expect("should have navigated into an editor")
13887 .read(cx)
13888 .text(cx),
13889 sample_text_3
13890 );
13891
13892 workspace
13893 .go_back(workspace.active_pane().downgrade(), window, cx)
13894 .detach_and_log_err(cx);
13895 })
13896 .unwrap();
13897 cx.executor().run_until_parked();
13898 workspace
13899 .update(cx, |workspace, _, cx| {
13900 let active_item = workspace
13901 .active_item(cx)
13902 .expect("should have an active item after navigating back from the 3rd buffer");
13903 assert_eq!(
13904 active_item.item_id(),
13905 multibuffer_item_id,
13906 "Should navigate back from the 3rd buffer to the multi buffer"
13907 );
13908 assert!(!active_item.is_singleton(cx));
13909 })
13910 .unwrap();
13911}
13912
13913#[gpui::test]
13914async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13915 init_test(cx, |_| {});
13916
13917 let mut cx = EditorTestContext::new(cx).await;
13918
13919 let diff_base = r#"
13920 use some::mod;
13921
13922 const A: u32 = 42;
13923
13924 fn main() {
13925 println!("hello");
13926
13927 println!("world");
13928 }
13929 "#
13930 .unindent();
13931
13932 cx.set_state(
13933 &r#"
13934 use some::modified;
13935
13936 ˇ
13937 fn main() {
13938 println!("hello there");
13939
13940 println!("around the");
13941 println!("world");
13942 }
13943 "#
13944 .unindent(),
13945 );
13946
13947 cx.set_head_text(&diff_base);
13948 executor.run_until_parked();
13949
13950 cx.update_editor(|editor, window, cx| {
13951 editor.go_to_next_hunk(&GoToHunk, window, cx);
13952 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13953 });
13954 executor.run_until_parked();
13955 cx.assert_state_with_diff(
13956 r#"
13957 use some::modified;
13958
13959
13960 fn main() {
13961 - println!("hello");
13962 + ˇ println!("hello there");
13963
13964 println!("around the");
13965 println!("world");
13966 }
13967 "#
13968 .unindent(),
13969 );
13970
13971 cx.update_editor(|editor, window, cx| {
13972 for _ in 0..2 {
13973 editor.go_to_next_hunk(&GoToHunk, window, cx);
13974 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13975 }
13976 });
13977 executor.run_until_parked();
13978 cx.assert_state_with_diff(
13979 r#"
13980 - use some::mod;
13981 + ˇuse some::modified;
13982
13983
13984 fn main() {
13985 - println!("hello");
13986 + println!("hello there");
13987
13988 + println!("around the");
13989 println!("world");
13990 }
13991 "#
13992 .unindent(),
13993 );
13994
13995 cx.update_editor(|editor, window, cx| {
13996 editor.go_to_next_hunk(&GoToHunk, window, cx);
13997 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13998 });
13999 executor.run_until_parked();
14000 cx.assert_state_with_diff(
14001 r#"
14002 - use some::mod;
14003 + use some::modified;
14004
14005 - const A: u32 = 42;
14006 ˇ
14007 fn main() {
14008 - println!("hello");
14009 + println!("hello there");
14010
14011 + println!("around the");
14012 println!("world");
14013 }
14014 "#
14015 .unindent(),
14016 );
14017
14018 cx.update_editor(|editor, window, cx| {
14019 editor.cancel(&Cancel, window, cx);
14020 });
14021
14022 cx.assert_state_with_diff(
14023 r#"
14024 use some::modified;
14025
14026 ˇ
14027 fn main() {
14028 println!("hello there");
14029
14030 println!("around the");
14031 println!("world");
14032 }
14033 "#
14034 .unindent(),
14035 );
14036}
14037
14038#[gpui::test]
14039async fn test_diff_base_change_with_expanded_diff_hunks(
14040 executor: BackgroundExecutor,
14041 cx: &mut TestAppContext,
14042) {
14043 init_test(cx, |_| {});
14044
14045 let mut cx = EditorTestContext::new(cx).await;
14046
14047 let diff_base = r#"
14048 use some::mod1;
14049 use some::mod2;
14050
14051 const A: u32 = 42;
14052 const B: u32 = 42;
14053 const C: u32 = 42;
14054
14055 fn main() {
14056 println!("hello");
14057
14058 println!("world");
14059 }
14060 "#
14061 .unindent();
14062
14063 cx.set_state(
14064 &r#"
14065 use some::mod2;
14066
14067 const A: u32 = 42;
14068 const C: u32 = 42;
14069
14070 fn main(ˇ) {
14071 //println!("hello");
14072
14073 println!("world");
14074 //
14075 //
14076 }
14077 "#
14078 .unindent(),
14079 );
14080
14081 cx.set_head_text(&diff_base);
14082 executor.run_until_parked();
14083
14084 cx.update_editor(|editor, window, cx| {
14085 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14086 });
14087 executor.run_until_parked();
14088 cx.assert_state_with_diff(
14089 r#"
14090 - use some::mod1;
14091 use some::mod2;
14092
14093 const A: u32 = 42;
14094 - const B: u32 = 42;
14095 const C: u32 = 42;
14096
14097 fn main(ˇ) {
14098 - println!("hello");
14099 + //println!("hello");
14100
14101 println!("world");
14102 + //
14103 + //
14104 }
14105 "#
14106 .unindent(),
14107 );
14108
14109 cx.set_head_text("new diff base!");
14110 executor.run_until_parked();
14111 cx.assert_state_with_diff(
14112 r#"
14113 - new diff base!
14114 + use some::mod2;
14115 +
14116 + const A: u32 = 42;
14117 + const C: u32 = 42;
14118 +
14119 + fn main(ˇ) {
14120 + //println!("hello");
14121 +
14122 + println!("world");
14123 + //
14124 + //
14125 + }
14126 "#
14127 .unindent(),
14128 );
14129}
14130
14131#[gpui::test]
14132async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14133 init_test(cx, |_| {});
14134
14135 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14136 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14137 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14138 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14139 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14140 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14141
14142 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14143 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14144 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14145
14146 let multi_buffer = cx.new(|cx| {
14147 let mut multibuffer = MultiBuffer::new(ReadWrite);
14148 multibuffer.push_excerpts(
14149 buffer_1.clone(),
14150 [
14151 ExcerptRange {
14152 context: Point::new(0, 0)..Point::new(3, 0),
14153 primary: None,
14154 },
14155 ExcerptRange {
14156 context: Point::new(5, 0)..Point::new(7, 0),
14157 primary: None,
14158 },
14159 ExcerptRange {
14160 context: Point::new(9, 0)..Point::new(10, 3),
14161 primary: None,
14162 },
14163 ],
14164 cx,
14165 );
14166 multibuffer.push_excerpts(
14167 buffer_2.clone(),
14168 [
14169 ExcerptRange {
14170 context: Point::new(0, 0)..Point::new(3, 0),
14171 primary: None,
14172 },
14173 ExcerptRange {
14174 context: Point::new(5, 0)..Point::new(7, 0),
14175 primary: None,
14176 },
14177 ExcerptRange {
14178 context: Point::new(9, 0)..Point::new(10, 3),
14179 primary: None,
14180 },
14181 ],
14182 cx,
14183 );
14184 multibuffer.push_excerpts(
14185 buffer_3.clone(),
14186 [
14187 ExcerptRange {
14188 context: Point::new(0, 0)..Point::new(3, 0),
14189 primary: None,
14190 },
14191 ExcerptRange {
14192 context: Point::new(5, 0)..Point::new(7, 0),
14193 primary: None,
14194 },
14195 ExcerptRange {
14196 context: Point::new(9, 0)..Point::new(10, 3),
14197 primary: None,
14198 },
14199 ],
14200 cx,
14201 );
14202 multibuffer
14203 });
14204
14205 let editor =
14206 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14207 editor
14208 .update(cx, |editor, _window, cx| {
14209 for (buffer, diff_base) in [
14210 (buffer_1.clone(), file_1_old),
14211 (buffer_2.clone(), file_2_old),
14212 (buffer_3.clone(), file_3_old),
14213 ] {
14214 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14215 editor
14216 .buffer
14217 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14218 }
14219 })
14220 .unwrap();
14221
14222 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14223 cx.run_until_parked();
14224
14225 cx.assert_editor_state(
14226 &"
14227 ˇaaa
14228 ccc
14229 ddd
14230
14231 ggg
14232 hhh
14233
14234
14235 lll
14236 mmm
14237 NNN
14238
14239 qqq
14240 rrr
14241
14242 uuu
14243 111
14244 222
14245 333
14246
14247 666
14248 777
14249
14250 000
14251 !!!"
14252 .unindent(),
14253 );
14254
14255 cx.update_editor(|editor, window, cx| {
14256 editor.select_all(&SelectAll, window, cx);
14257 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14258 });
14259 cx.executor().run_until_parked();
14260
14261 cx.assert_state_with_diff(
14262 "
14263 «aaa
14264 - bbb
14265 ccc
14266 ddd
14267
14268 ggg
14269 hhh
14270
14271
14272 lll
14273 mmm
14274 - nnn
14275 + NNN
14276
14277 qqq
14278 rrr
14279
14280 uuu
14281 111
14282 222
14283 333
14284
14285 + 666
14286 777
14287
14288 000
14289 !!!ˇ»"
14290 .unindent(),
14291 );
14292}
14293
14294#[gpui::test]
14295async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14296 init_test(cx, |_| {});
14297
14298 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14299 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14300
14301 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14302 let multi_buffer = cx.new(|cx| {
14303 let mut multibuffer = MultiBuffer::new(ReadWrite);
14304 multibuffer.push_excerpts(
14305 buffer.clone(),
14306 [
14307 ExcerptRange {
14308 context: Point::new(0, 0)..Point::new(2, 0),
14309 primary: None,
14310 },
14311 ExcerptRange {
14312 context: Point::new(4, 0)..Point::new(7, 0),
14313 primary: None,
14314 },
14315 ExcerptRange {
14316 context: Point::new(9, 0)..Point::new(10, 0),
14317 primary: None,
14318 },
14319 ],
14320 cx,
14321 );
14322 multibuffer
14323 });
14324
14325 let editor =
14326 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14327 editor
14328 .update(cx, |editor, _window, cx| {
14329 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14330 editor
14331 .buffer
14332 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14333 })
14334 .unwrap();
14335
14336 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14337 cx.run_until_parked();
14338
14339 cx.update_editor(|editor, window, cx| {
14340 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14341 });
14342 cx.executor().run_until_parked();
14343
14344 // When the start of a hunk coincides with the start of its excerpt,
14345 // the hunk is expanded. When the start of a a hunk is earlier than
14346 // the start of its excerpt, the hunk is not expanded.
14347 cx.assert_state_with_diff(
14348 "
14349 ˇaaa
14350 - bbb
14351 + BBB
14352
14353 - ddd
14354 - eee
14355 + DDD
14356 + EEE
14357 fff
14358
14359 iii
14360 "
14361 .unindent(),
14362 );
14363}
14364
14365#[gpui::test]
14366async fn test_edits_around_expanded_insertion_hunks(
14367 executor: BackgroundExecutor,
14368 cx: &mut TestAppContext,
14369) {
14370 init_test(cx, |_| {});
14371
14372 let mut cx = EditorTestContext::new(cx).await;
14373
14374 let diff_base = r#"
14375 use some::mod1;
14376 use some::mod2;
14377
14378 const A: u32 = 42;
14379
14380 fn main() {
14381 println!("hello");
14382
14383 println!("world");
14384 }
14385 "#
14386 .unindent();
14387 executor.run_until_parked();
14388 cx.set_state(
14389 &r#"
14390 use some::mod1;
14391 use some::mod2;
14392
14393 const A: u32 = 42;
14394 const B: u32 = 42;
14395 const C: u32 = 42;
14396 ˇ
14397
14398 fn main() {
14399 println!("hello");
14400
14401 println!("world");
14402 }
14403 "#
14404 .unindent(),
14405 );
14406
14407 cx.set_head_text(&diff_base);
14408 executor.run_until_parked();
14409
14410 cx.update_editor(|editor, window, cx| {
14411 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14412 });
14413 executor.run_until_parked();
14414
14415 cx.assert_state_with_diff(
14416 r#"
14417 use some::mod1;
14418 use some::mod2;
14419
14420 const A: u32 = 42;
14421 + const B: u32 = 42;
14422 + const C: u32 = 42;
14423 + ˇ
14424
14425 fn main() {
14426 println!("hello");
14427
14428 println!("world");
14429 }
14430 "#
14431 .unindent(),
14432 );
14433
14434 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14435 executor.run_until_parked();
14436
14437 cx.assert_state_with_diff(
14438 r#"
14439 use some::mod1;
14440 use some::mod2;
14441
14442 const A: u32 = 42;
14443 + const B: u32 = 42;
14444 + const C: u32 = 42;
14445 + const D: u32 = 42;
14446 + ˇ
14447
14448 fn main() {
14449 println!("hello");
14450
14451 println!("world");
14452 }
14453 "#
14454 .unindent(),
14455 );
14456
14457 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14458 executor.run_until_parked();
14459
14460 cx.assert_state_with_diff(
14461 r#"
14462 use some::mod1;
14463 use some::mod2;
14464
14465 const A: u32 = 42;
14466 + const B: u32 = 42;
14467 + const C: u32 = 42;
14468 + const D: u32 = 42;
14469 + const E: u32 = 42;
14470 + ˇ
14471
14472 fn main() {
14473 println!("hello");
14474
14475 println!("world");
14476 }
14477 "#
14478 .unindent(),
14479 );
14480
14481 cx.update_editor(|editor, window, cx| {
14482 editor.delete_line(&DeleteLine, window, cx);
14483 });
14484 executor.run_until_parked();
14485
14486 cx.assert_state_with_diff(
14487 r#"
14488 use some::mod1;
14489 use some::mod2;
14490
14491 const A: u32 = 42;
14492 + const B: u32 = 42;
14493 + const C: u32 = 42;
14494 + const D: u32 = 42;
14495 + const E: u32 = 42;
14496 ˇ
14497 fn main() {
14498 println!("hello");
14499
14500 println!("world");
14501 }
14502 "#
14503 .unindent(),
14504 );
14505
14506 cx.update_editor(|editor, window, cx| {
14507 editor.move_up(&MoveUp, window, cx);
14508 editor.delete_line(&DeleteLine, window, cx);
14509 editor.move_up(&MoveUp, window, cx);
14510 editor.delete_line(&DeleteLine, window, cx);
14511 editor.move_up(&MoveUp, window, cx);
14512 editor.delete_line(&DeleteLine, window, cx);
14513 });
14514 executor.run_until_parked();
14515 cx.assert_state_with_diff(
14516 r#"
14517 use some::mod1;
14518 use some::mod2;
14519
14520 const A: u32 = 42;
14521 + const B: u32 = 42;
14522 ˇ
14523 fn main() {
14524 println!("hello");
14525
14526 println!("world");
14527 }
14528 "#
14529 .unindent(),
14530 );
14531
14532 cx.update_editor(|editor, window, cx| {
14533 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14534 editor.delete_line(&DeleteLine, window, cx);
14535 });
14536 executor.run_until_parked();
14537 cx.assert_state_with_diff(
14538 r#"
14539 ˇ
14540 fn main() {
14541 println!("hello");
14542
14543 println!("world");
14544 }
14545 "#
14546 .unindent(),
14547 );
14548}
14549
14550#[gpui::test]
14551async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14552 init_test(cx, |_| {});
14553
14554 let mut cx = EditorTestContext::new(cx).await;
14555 cx.set_head_text(indoc! { "
14556 one
14557 two
14558 three
14559 four
14560 five
14561 "
14562 });
14563 cx.set_state(indoc! { "
14564 one
14565 ˇthree
14566 five
14567 "});
14568 cx.run_until_parked();
14569 cx.update_editor(|editor, window, cx| {
14570 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14571 });
14572 cx.assert_state_with_diff(
14573 indoc! { "
14574 one
14575 - two
14576 ˇthree
14577 - four
14578 five
14579 "}
14580 .to_string(),
14581 );
14582 cx.update_editor(|editor, window, cx| {
14583 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14584 });
14585
14586 cx.assert_state_with_diff(
14587 indoc! { "
14588 one
14589 ˇthree
14590 five
14591 "}
14592 .to_string(),
14593 );
14594
14595 cx.set_state(indoc! { "
14596 one
14597 ˇTWO
14598 three
14599 four
14600 five
14601 "});
14602 cx.run_until_parked();
14603 cx.update_editor(|editor, window, cx| {
14604 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14605 });
14606
14607 cx.assert_state_with_diff(
14608 indoc! { "
14609 one
14610 - two
14611 + ˇTWO
14612 three
14613 four
14614 five
14615 "}
14616 .to_string(),
14617 );
14618 cx.update_editor(|editor, window, cx| {
14619 editor.move_up(&Default::default(), window, cx);
14620 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14621 });
14622 cx.assert_state_with_diff(
14623 indoc! { "
14624 one
14625 ˇTWO
14626 three
14627 four
14628 five
14629 "}
14630 .to_string(),
14631 );
14632}
14633
14634#[gpui::test]
14635async fn test_edits_around_expanded_deletion_hunks(
14636 executor: BackgroundExecutor,
14637 cx: &mut TestAppContext,
14638) {
14639 init_test(cx, |_| {});
14640
14641 let mut cx = EditorTestContext::new(cx).await;
14642
14643 let diff_base = r#"
14644 use some::mod1;
14645 use some::mod2;
14646
14647 const A: u32 = 42;
14648 const B: u32 = 42;
14649 const C: u32 = 42;
14650
14651
14652 fn main() {
14653 println!("hello");
14654
14655 println!("world");
14656 }
14657 "#
14658 .unindent();
14659 executor.run_until_parked();
14660 cx.set_state(
14661 &r#"
14662 use some::mod1;
14663 use some::mod2;
14664
14665 ˇconst B: u32 = 42;
14666 const C: u32 = 42;
14667
14668
14669 fn main() {
14670 println!("hello");
14671
14672 println!("world");
14673 }
14674 "#
14675 .unindent(),
14676 );
14677
14678 cx.set_head_text(&diff_base);
14679 executor.run_until_parked();
14680
14681 cx.update_editor(|editor, window, cx| {
14682 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14683 });
14684 executor.run_until_parked();
14685
14686 cx.assert_state_with_diff(
14687 r#"
14688 use some::mod1;
14689 use some::mod2;
14690
14691 - const A: u32 = 42;
14692 ˇconst B: u32 = 42;
14693 const C: u32 = 42;
14694
14695
14696 fn main() {
14697 println!("hello");
14698
14699 println!("world");
14700 }
14701 "#
14702 .unindent(),
14703 );
14704
14705 cx.update_editor(|editor, window, cx| {
14706 editor.delete_line(&DeleteLine, window, cx);
14707 });
14708 executor.run_until_parked();
14709 cx.assert_state_with_diff(
14710 r#"
14711 use some::mod1;
14712 use some::mod2;
14713
14714 - const A: u32 = 42;
14715 - const B: u32 = 42;
14716 ˇconst C: u32 = 42;
14717
14718
14719 fn main() {
14720 println!("hello");
14721
14722 println!("world");
14723 }
14724 "#
14725 .unindent(),
14726 );
14727
14728 cx.update_editor(|editor, window, cx| {
14729 editor.delete_line(&DeleteLine, window, cx);
14730 });
14731 executor.run_until_parked();
14732 cx.assert_state_with_diff(
14733 r#"
14734 use some::mod1;
14735 use some::mod2;
14736
14737 - const A: u32 = 42;
14738 - const B: u32 = 42;
14739 - const C: u32 = 42;
14740 ˇ
14741
14742 fn main() {
14743 println!("hello");
14744
14745 println!("world");
14746 }
14747 "#
14748 .unindent(),
14749 );
14750
14751 cx.update_editor(|editor, window, cx| {
14752 editor.handle_input("replacement", window, cx);
14753 });
14754 executor.run_until_parked();
14755 cx.assert_state_with_diff(
14756 r#"
14757 use some::mod1;
14758 use some::mod2;
14759
14760 - const A: u32 = 42;
14761 - const B: u32 = 42;
14762 - const C: u32 = 42;
14763 -
14764 + replacementˇ
14765
14766 fn main() {
14767 println!("hello");
14768
14769 println!("world");
14770 }
14771 "#
14772 .unindent(),
14773 );
14774}
14775
14776#[gpui::test]
14777async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14778 init_test(cx, |_| {});
14779
14780 let mut cx = EditorTestContext::new(cx).await;
14781
14782 let base_text = r#"
14783 one
14784 two
14785 three
14786 four
14787 five
14788 "#
14789 .unindent();
14790 executor.run_until_parked();
14791 cx.set_state(
14792 &r#"
14793 one
14794 two
14795 fˇour
14796 five
14797 "#
14798 .unindent(),
14799 );
14800
14801 cx.set_head_text(&base_text);
14802 executor.run_until_parked();
14803
14804 cx.update_editor(|editor, window, cx| {
14805 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14806 });
14807 executor.run_until_parked();
14808
14809 cx.assert_state_with_diff(
14810 r#"
14811 one
14812 two
14813 - three
14814 fˇour
14815 five
14816 "#
14817 .unindent(),
14818 );
14819
14820 cx.update_editor(|editor, window, cx| {
14821 editor.backspace(&Backspace, window, cx);
14822 editor.backspace(&Backspace, window, cx);
14823 });
14824 executor.run_until_parked();
14825 cx.assert_state_with_diff(
14826 r#"
14827 one
14828 two
14829 - threeˇ
14830 - four
14831 + our
14832 five
14833 "#
14834 .unindent(),
14835 );
14836}
14837
14838#[gpui::test]
14839async fn test_edit_after_expanded_modification_hunk(
14840 executor: BackgroundExecutor,
14841 cx: &mut TestAppContext,
14842) {
14843 init_test(cx, |_| {});
14844
14845 let mut cx = EditorTestContext::new(cx).await;
14846
14847 let diff_base = r#"
14848 use some::mod1;
14849 use some::mod2;
14850
14851 const A: u32 = 42;
14852 const B: u32 = 42;
14853 const C: u32 = 42;
14854 const D: u32 = 42;
14855
14856
14857 fn main() {
14858 println!("hello");
14859
14860 println!("world");
14861 }"#
14862 .unindent();
14863
14864 cx.set_state(
14865 &r#"
14866 use some::mod1;
14867 use some::mod2;
14868
14869 const A: u32 = 42;
14870 const B: u32 = 42;
14871 const C: u32 = 43ˇ
14872 const D: u32 = 42;
14873
14874
14875 fn main() {
14876 println!("hello");
14877
14878 println!("world");
14879 }"#
14880 .unindent(),
14881 );
14882
14883 cx.set_head_text(&diff_base);
14884 executor.run_until_parked();
14885 cx.update_editor(|editor, window, cx| {
14886 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14887 });
14888 executor.run_until_parked();
14889
14890 cx.assert_state_with_diff(
14891 r#"
14892 use some::mod1;
14893 use some::mod2;
14894
14895 const A: u32 = 42;
14896 const B: u32 = 42;
14897 - const C: u32 = 42;
14898 + const C: u32 = 43ˇ
14899 const D: u32 = 42;
14900
14901
14902 fn main() {
14903 println!("hello");
14904
14905 println!("world");
14906 }"#
14907 .unindent(),
14908 );
14909
14910 cx.update_editor(|editor, window, cx| {
14911 editor.handle_input("\nnew_line\n", window, cx);
14912 });
14913 executor.run_until_parked();
14914
14915 cx.assert_state_with_diff(
14916 r#"
14917 use some::mod1;
14918 use some::mod2;
14919
14920 const A: u32 = 42;
14921 const B: u32 = 42;
14922 - const C: u32 = 42;
14923 + const C: u32 = 43
14924 + new_line
14925 + ˇ
14926 const D: u32 = 42;
14927
14928
14929 fn main() {
14930 println!("hello");
14931
14932 println!("world");
14933 }"#
14934 .unindent(),
14935 );
14936}
14937
14938#[gpui::test]
14939async fn test_stage_and_unstage_added_file_hunk(
14940 executor: BackgroundExecutor,
14941 cx: &mut TestAppContext,
14942) {
14943 init_test(cx, |_| {});
14944
14945 let mut cx = EditorTestContext::new(cx).await;
14946 cx.update_editor(|editor, _, cx| {
14947 editor.set_expand_all_diff_hunks(cx);
14948 });
14949
14950 let working_copy = r#"
14951 ˇfn main() {
14952 println!("hello, world!");
14953 }
14954 "#
14955 .unindent();
14956
14957 cx.set_state(&working_copy);
14958 executor.run_until_parked();
14959
14960 cx.assert_state_with_diff(
14961 r#"
14962 + ˇfn main() {
14963 + println!("hello, world!");
14964 + }
14965 "#
14966 .unindent(),
14967 );
14968 cx.assert_index_text(None);
14969
14970 cx.update_editor(|editor, window, cx| {
14971 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14972 });
14973 executor.run_until_parked();
14974 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14975 cx.assert_state_with_diff(
14976 r#"
14977 + ˇfn main() {
14978 + println!("hello, world!");
14979 + }
14980 "#
14981 .unindent(),
14982 );
14983
14984 cx.update_editor(|editor, window, cx| {
14985 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14986 });
14987 executor.run_until_parked();
14988 cx.assert_index_text(None);
14989}
14990
14991async fn setup_indent_guides_editor(
14992 text: &str,
14993 cx: &mut TestAppContext,
14994) -> (BufferId, EditorTestContext) {
14995 init_test(cx, |_| {});
14996
14997 let mut cx = EditorTestContext::new(cx).await;
14998
14999 let buffer_id = cx.update_editor(|editor, window, cx| {
15000 editor.set_text(text, window, cx);
15001 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15002
15003 buffer_ids[0]
15004 });
15005
15006 (buffer_id, cx)
15007}
15008
15009fn assert_indent_guides(
15010 range: Range<u32>,
15011 expected: Vec<IndentGuide>,
15012 active_indices: Option<Vec<usize>>,
15013 cx: &mut EditorTestContext,
15014) {
15015 let indent_guides = cx.update_editor(|editor, window, cx| {
15016 let snapshot = editor.snapshot(window, cx).display_snapshot;
15017 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15018 editor,
15019 MultiBufferRow(range.start)..MultiBufferRow(range.end),
15020 true,
15021 &snapshot,
15022 cx,
15023 );
15024
15025 indent_guides.sort_by(|a, b| {
15026 a.depth.cmp(&b.depth).then(
15027 a.start_row
15028 .cmp(&b.start_row)
15029 .then(a.end_row.cmp(&b.end_row)),
15030 )
15031 });
15032 indent_guides
15033 });
15034
15035 if let Some(expected) = active_indices {
15036 let active_indices = cx.update_editor(|editor, window, cx| {
15037 let snapshot = editor.snapshot(window, cx).display_snapshot;
15038 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
15039 });
15040
15041 assert_eq!(
15042 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
15043 expected,
15044 "Active indent guide indices do not match"
15045 );
15046 }
15047
15048 assert_eq!(indent_guides, expected, "Indent guides do not match");
15049}
15050
15051fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
15052 IndentGuide {
15053 buffer_id,
15054 start_row: MultiBufferRow(start_row),
15055 end_row: MultiBufferRow(end_row),
15056 depth,
15057 tab_size: 4,
15058 settings: IndentGuideSettings {
15059 enabled: true,
15060 line_width: 1,
15061 active_line_width: 1,
15062 ..Default::default()
15063 },
15064 }
15065}
15066
15067#[gpui::test]
15068async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15069 let (buffer_id, mut cx) = setup_indent_guides_editor(
15070 &"
15071 fn main() {
15072 let a = 1;
15073 }"
15074 .unindent(),
15075 cx,
15076 )
15077 .await;
15078
15079 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15080}
15081
15082#[gpui::test]
15083async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15084 let (buffer_id, mut cx) = setup_indent_guides_editor(
15085 &"
15086 fn main() {
15087 let a = 1;
15088 let b = 2;
15089 }"
15090 .unindent(),
15091 cx,
15092 )
15093 .await;
15094
15095 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15096}
15097
15098#[gpui::test]
15099async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15100 let (buffer_id, mut cx) = setup_indent_guides_editor(
15101 &"
15102 fn main() {
15103 let a = 1;
15104 if a == 3 {
15105 let b = 2;
15106 } else {
15107 let c = 3;
15108 }
15109 }"
15110 .unindent(),
15111 cx,
15112 )
15113 .await;
15114
15115 assert_indent_guides(
15116 0..8,
15117 vec![
15118 indent_guide(buffer_id, 1, 6, 0),
15119 indent_guide(buffer_id, 3, 3, 1),
15120 indent_guide(buffer_id, 5, 5, 1),
15121 ],
15122 None,
15123 &mut cx,
15124 );
15125}
15126
15127#[gpui::test]
15128async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15129 let (buffer_id, mut cx) = setup_indent_guides_editor(
15130 &"
15131 fn main() {
15132 let a = 1;
15133 let b = 2;
15134 let c = 3;
15135 }"
15136 .unindent(),
15137 cx,
15138 )
15139 .await;
15140
15141 assert_indent_guides(
15142 0..5,
15143 vec![
15144 indent_guide(buffer_id, 1, 3, 0),
15145 indent_guide(buffer_id, 2, 2, 1),
15146 ],
15147 None,
15148 &mut cx,
15149 );
15150}
15151
15152#[gpui::test]
15153async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15154 let (buffer_id, mut cx) = setup_indent_guides_editor(
15155 &"
15156 fn main() {
15157 let a = 1;
15158
15159 let c = 3;
15160 }"
15161 .unindent(),
15162 cx,
15163 )
15164 .await;
15165
15166 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15167}
15168
15169#[gpui::test]
15170async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15171 let (buffer_id, mut cx) = setup_indent_guides_editor(
15172 &"
15173 fn main() {
15174 let a = 1;
15175
15176 let c = 3;
15177
15178 if a == 3 {
15179 let b = 2;
15180 } else {
15181 let c = 3;
15182 }
15183 }"
15184 .unindent(),
15185 cx,
15186 )
15187 .await;
15188
15189 assert_indent_guides(
15190 0..11,
15191 vec![
15192 indent_guide(buffer_id, 1, 9, 0),
15193 indent_guide(buffer_id, 6, 6, 1),
15194 indent_guide(buffer_id, 8, 8, 1),
15195 ],
15196 None,
15197 &mut cx,
15198 );
15199}
15200
15201#[gpui::test]
15202async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15203 let (buffer_id, mut cx) = setup_indent_guides_editor(
15204 &"
15205 fn main() {
15206 let a = 1;
15207
15208 let c = 3;
15209
15210 if a == 3 {
15211 let b = 2;
15212 } else {
15213 let c = 3;
15214 }
15215 }"
15216 .unindent(),
15217 cx,
15218 )
15219 .await;
15220
15221 assert_indent_guides(
15222 1..11,
15223 vec![
15224 indent_guide(buffer_id, 1, 9, 0),
15225 indent_guide(buffer_id, 6, 6, 1),
15226 indent_guide(buffer_id, 8, 8, 1),
15227 ],
15228 None,
15229 &mut cx,
15230 );
15231}
15232
15233#[gpui::test]
15234async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15235 let (buffer_id, mut cx) = setup_indent_guides_editor(
15236 &"
15237 fn main() {
15238 let a = 1;
15239
15240 let c = 3;
15241
15242 if a == 3 {
15243 let b = 2;
15244 } else {
15245 let c = 3;
15246 }
15247 }"
15248 .unindent(),
15249 cx,
15250 )
15251 .await;
15252
15253 assert_indent_guides(
15254 1..10,
15255 vec![
15256 indent_guide(buffer_id, 1, 9, 0),
15257 indent_guide(buffer_id, 6, 6, 1),
15258 indent_guide(buffer_id, 8, 8, 1),
15259 ],
15260 None,
15261 &mut cx,
15262 );
15263}
15264
15265#[gpui::test]
15266async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15267 let (buffer_id, mut cx) = setup_indent_guides_editor(
15268 &"
15269 block1
15270 block2
15271 block3
15272 block4
15273 block2
15274 block1
15275 block1"
15276 .unindent(),
15277 cx,
15278 )
15279 .await;
15280
15281 assert_indent_guides(
15282 1..10,
15283 vec![
15284 indent_guide(buffer_id, 1, 4, 0),
15285 indent_guide(buffer_id, 2, 3, 1),
15286 indent_guide(buffer_id, 3, 3, 2),
15287 ],
15288 None,
15289 &mut cx,
15290 );
15291}
15292
15293#[gpui::test]
15294async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15295 let (buffer_id, mut cx) = setup_indent_guides_editor(
15296 &"
15297 block1
15298 block2
15299 block3
15300
15301 block1
15302 block1"
15303 .unindent(),
15304 cx,
15305 )
15306 .await;
15307
15308 assert_indent_guides(
15309 0..6,
15310 vec![
15311 indent_guide(buffer_id, 1, 2, 0),
15312 indent_guide(buffer_id, 2, 2, 1),
15313 ],
15314 None,
15315 &mut cx,
15316 );
15317}
15318
15319#[gpui::test]
15320async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15321 let (buffer_id, mut cx) = setup_indent_guides_editor(
15322 &"
15323 block1
15324
15325
15326
15327 block2
15328 "
15329 .unindent(),
15330 cx,
15331 )
15332 .await;
15333
15334 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15335}
15336
15337#[gpui::test]
15338async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15339 let (buffer_id, mut cx) = setup_indent_guides_editor(
15340 &"
15341 def a:
15342 \tb = 3
15343 \tif True:
15344 \t\tc = 4
15345 \t\td = 5
15346 \tprint(b)
15347 "
15348 .unindent(),
15349 cx,
15350 )
15351 .await;
15352
15353 assert_indent_guides(
15354 0..6,
15355 vec![
15356 indent_guide(buffer_id, 1, 6, 0),
15357 indent_guide(buffer_id, 3, 4, 1),
15358 ],
15359 None,
15360 &mut cx,
15361 );
15362}
15363
15364#[gpui::test]
15365async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15366 let (buffer_id, mut cx) = setup_indent_guides_editor(
15367 &"
15368 fn main() {
15369 let a = 1;
15370 }"
15371 .unindent(),
15372 cx,
15373 )
15374 .await;
15375
15376 cx.update_editor(|editor, window, cx| {
15377 editor.change_selections(None, window, cx, |s| {
15378 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15379 });
15380 });
15381
15382 assert_indent_guides(
15383 0..3,
15384 vec![indent_guide(buffer_id, 1, 1, 0)],
15385 Some(vec![0]),
15386 &mut cx,
15387 );
15388}
15389
15390#[gpui::test]
15391async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15392 let (buffer_id, mut cx) = setup_indent_guides_editor(
15393 &"
15394 fn main() {
15395 if 1 == 2 {
15396 let a = 1;
15397 }
15398 }"
15399 .unindent(),
15400 cx,
15401 )
15402 .await;
15403
15404 cx.update_editor(|editor, window, cx| {
15405 editor.change_selections(None, window, cx, |s| {
15406 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15407 });
15408 });
15409
15410 assert_indent_guides(
15411 0..4,
15412 vec![
15413 indent_guide(buffer_id, 1, 3, 0),
15414 indent_guide(buffer_id, 2, 2, 1),
15415 ],
15416 Some(vec![1]),
15417 &mut cx,
15418 );
15419
15420 cx.update_editor(|editor, window, cx| {
15421 editor.change_selections(None, window, cx, |s| {
15422 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15423 });
15424 });
15425
15426 assert_indent_guides(
15427 0..4,
15428 vec![
15429 indent_guide(buffer_id, 1, 3, 0),
15430 indent_guide(buffer_id, 2, 2, 1),
15431 ],
15432 Some(vec![1]),
15433 &mut cx,
15434 );
15435
15436 cx.update_editor(|editor, window, cx| {
15437 editor.change_selections(None, window, cx, |s| {
15438 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15439 });
15440 });
15441
15442 assert_indent_guides(
15443 0..4,
15444 vec![
15445 indent_guide(buffer_id, 1, 3, 0),
15446 indent_guide(buffer_id, 2, 2, 1),
15447 ],
15448 Some(vec![0]),
15449 &mut cx,
15450 );
15451}
15452
15453#[gpui::test]
15454async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15455 let (buffer_id, mut cx) = setup_indent_guides_editor(
15456 &"
15457 fn main() {
15458 let a = 1;
15459
15460 let b = 2;
15461 }"
15462 .unindent(),
15463 cx,
15464 )
15465 .await;
15466
15467 cx.update_editor(|editor, window, cx| {
15468 editor.change_selections(None, window, cx, |s| {
15469 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15470 });
15471 });
15472
15473 assert_indent_guides(
15474 0..5,
15475 vec![indent_guide(buffer_id, 1, 3, 0)],
15476 Some(vec![0]),
15477 &mut cx,
15478 );
15479}
15480
15481#[gpui::test]
15482async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15483 let (buffer_id, mut cx) = setup_indent_guides_editor(
15484 &"
15485 def m:
15486 a = 1
15487 pass"
15488 .unindent(),
15489 cx,
15490 )
15491 .await;
15492
15493 cx.update_editor(|editor, window, cx| {
15494 editor.change_selections(None, window, cx, |s| {
15495 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15496 });
15497 });
15498
15499 assert_indent_guides(
15500 0..3,
15501 vec![indent_guide(buffer_id, 1, 2, 0)],
15502 Some(vec![0]),
15503 &mut cx,
15504 );
15505}
15506
15507#[gpui::test]
15508async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15509 init_test(cx, |_| {});
15510 let mut cx = EditorTestContext::new(cx).await;
15511 let text = indoc! {
15512 "
15513 impl A {
15514 fn b() {
15515 0;
15516 3;
15517 5;
15518 6;
15519 7;
15520 }
15521 }
15522 "
15523 };
15524 let base_text = indoc! {
15525 "
15526 impl A {
15527 fn b() {
15528 0;
15529 1;
15530 2;
15531 3;
15532 4;
15533 }
15534 fn c() {
15535 5;
15536 6;
15537 7;
15538 }
15539 }
15540 "
15541 };
15542
15543 cx.update_editor(|editor, window, cx| {
15544 editor.set_text(text, window, cx);
15545
15546 editor.buffer().update(cx, |multibuffer, cx| {
15547 let buffer = multibuffer.as_singleton().unwrap();
15548 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15549
15550 multibuffer.set_all_diff_hunks_expanded(cx);
15551 multibuffer.add_diff(diff, cx);
15552
15553 buffer.read(cx).remote_id()
15554 })
15555 });
15556 cx.run_until_parked();
15557
15558 cx.assert_state_with_diff(
15559 indoc! { "
15560 impl A {
15561 fn b() {
15562 0;
15563 - 1;
15564 - 2;
15565 3;
15566 - 4;
15567 - }
15568 - fn c() {
15569 5;
15570 6;
15571 7;
15572 }
15573 }
15574 ˇ"
15575 }
15576 .to_string(),
15577 );
15578
15579 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15580 editor
15581 .snapshot(window, cx)
15582 .buffer_snapshot
15583 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15584 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15585 .collect::<Vec<_>>()
15586 });
15587 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15588 assert_eq!(
15589 actual_guides,
15590 vec![
15591 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15592 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15593 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15594 ]
15595 );
15596}
15597
15598#[gpui::test]
15599async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15600 init_test(cx, |_| {});
15601 let mut cx = EditorTestContext::new(cx).await;
15602
15603 let diff_base = r#"
15604 a
15605 b
15606 c
15607 "#
15608 .unindent();
15609
15610 cx.set_state(
15611 &r#"
15612 ˇA
15613 b
15614 C
15615 "#
15616 .unindent(),
15617 );
15618 cx.set_head_text(&diff_base);
15619 cx.update_editor(|editor, window, cx| {
15620 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15621 });
15622 executor.run_until_parked();
15623
15624 let both_hunks_expanded = r#"
15625 - a
15626 + ˇA
15627 b
15628 - c
15629 + C
15630 "#
15631 .unindent();
15632
15633 cx.assert_state_with_diff(both_hunks_expanded.clone());
15634
15635 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15636 let snapshot = editor.snapshot(window, cx);
15637 let hunks = editor
15638 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15639 .collect::<Vec<_>>();
15640 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15641 let buffer_id = hunks[0].buffer_id;
15642 hunks
15643 .into_iter()
15644 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15645 .collect::<Vec<_>>()
15646 });
15647 assert_eq!(hunk_ranges.len(), 2);
15648
15649 cx.update_editor(|editor, _, cx| {
15650 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15651 });
15652 executor.run_until_parked();
15653
15654 let second_hunk_expanded = r#"
15655 ˇA
15656 b
15657 - c
15658 + C
15659 "#
15660 .unindent();
15661
15662 cx.assert_state_with_diff(second_hunk_expanded);
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 cx.assert_state_with_diff(both_hunks_expanded.clone());
15670
15671 cx.update_editor(|editor, _, cx| {
15672 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15673 });
15674 executor.run_until_parked();
15675
15676 let first_hunk_expanded = r#"
15677 - a
15678 + ˇA
15679 b
15680 C
15681 "#
15682 .unindent();
15683
15684 cx.assert_state_with_diff(first_hunk_expanded);
15685
15686 cx.update_editor(|editor, _, cx| {
15687 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15688 });
15689 executor.run_until_parked();
15690
15691 cx.assert_state_with_diff(both_hunks_expanded);
15692
15693 cx.set_state(
15694 &r#"
15695 ˇA
15696 b
15697 "#
15698 .unindent(),
15699 );
15700 cx.run_until_parked();
15701
15702 // TODO this cursor position seems bad
15703 cx.assert_state_with_diff(
15704 r#"
15705 - ˇa
15706 + A
15707 b
15708 "#
15709 .unindent(),
15710 );
15711
15712 cx.update_editor(|editor, window, cx| {
15713 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15714 });
15715
15716 cx.assert_state_with_diff(
15717 r#"
15718 - ˇa
15719 + A
15720 b
15721 - c
15722 "#
15723 .unindent(),
15724 );
15725
15726 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15727 let snapshot = editor.snapshot(window, cx);
15728 let hunks = editor
15729 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15730 .collect::<Vec<_>>();
15731 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15732 let buffer_id = hunks[0].buffer_id;
15733 hunks
15734 .into_iter()
15735 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15736 .collect::<Vec<_>>()
15737 });
15738 assert_eq!(hunk_ranges.len(), 2);
15739
15740 cx.update_editor(|editor, _, cx| {
15741 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15742 });
15743 executor.run_until_parked();
15744
15745 cx.assert_state_with_diff(
15746 r#"
15747 - ˇa
15748 + A
15749 b
15750 "#
15751 .unindent(),
15752 );
15753}
15754
15755#[gpui::test]
15756async fn test_toggle_deletion_hunk_at_start_of_file(
15757 executor: BackgroundExecutor,
15758 cx: &mut TestAppContext,
15759) {
15760 init_test(cx, |_| {});
15761 let mut cx = EditorTestContext::new(cx).await;
15762
15763 let diff_base = r#"
15764 a
15765 b
15766 c
15767 "#
15768 .unindent();
15769
15770 cx.set_state(
15771 &r#"
15772 ˇb
15773 c
15774 "#
15775 .unindent(),
15776 );
15777 cx.set_head_text(&diff_base);
15778 cx.update_editor(|editor, window, cx| {
15779 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15780 });
15781 executor.run_until_parked();
15782
15783 let hunk_expanded = r#"
15784 - a
15785 ˇb
15786 c
15787 "#
15788 .unindent();
15789
15790 cx.assert_state_with_diff(hunk_expanded.clone());
15791
15792 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15793 let snapshot = editor.snapshot(window, cx);
15794 let hunks = editor
15795 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15796 .collect::<Vec<_>>();
15797 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15798 let buffer_id = hunks[0].buffer_id;
15799 hunks
15800 .into_iter()
15801 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15802 .collect::<Vec<_>>()
15803 });
15804 assert_eq!(hunk_ranges.len(), 1);
15805
15806 cx.update_editor(|editor, _, cx| {
15807 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15808 });
15809 executor.run_until_parked();
15810
15811 let hunk_collapsed = r#"
15812 ˇb
15813 c
15814 "#
15815 .unindent();
15816
15817 cx.assert_state_with_diff(hunk_collapsed);
15818
15819 cx.update_editor(|editor, _, cx| {
15820 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15821 });
15822 executor.run_until_parked();
15823
15824 cx.assert_state_with_diff(hunk_expanded.clone());
15825}
15826
15827#[gpui::test]
15828async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15829 init_test(cx, |_| {});
15830
15831 let fs = FakeFs::new(cx.executor());
15832 fs.insert_tree(
15833 path!("/test"),
15834 json!({
15835 ".git": {},
15836 "file-1": "ONE\n",
15837 "file-2": "TWO\n",
15838 "file-3": "THREE\n",
15839 }),
15840 )
15841 .await;
15842
15843 fs.set_head_for_repo(
15844 path!("/test/.git").as_ref(),
15845 &[
15846 ("file-1".into(), "one\n".into()),
15847 ("file-2".into(), "two\n".into()),
15848 ("file-3".into(), "three\n".into()),
15849 ],
15850 );
15851
15852 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15853 let mut buffers = vec![];
15854 for i in 1..=3 {
15855 let buffer = project
15856 .update(cx, |project, cx| {
15857 let path = format!(path!("/test/file-{}"), i);
15858 project.open_local_buffer(path, cx)
15859 })
15860 .await
15861 .unwrap();
15862 buffers.push(buffer);
15863 }
15864
15865 let multibuffer = cx.new(|cx| {
15866 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15867 multibuffer.set_all_diff_hunks_expanded(cx);
15868 for buffer in &buffers {
15869 let snapshot = buffer.read(cx).snapshot();
15870 multibuffer.set_excerpts_for_path(
15871 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15872 buffer.clone(),
15873 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15874 DEFAULT_MULTIBUFFER_CONTEXT,
15875 cx,
15876 );
15877 }
15878 multibuffer
15879 });
15880
15881 let editor = cx.add_window(|window, cx| {
15882 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
15883 });
15884 cx.run_until_parked();
15885
15886 let snapshot = editor
15887 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15888 .unwrap();
15889 let hunks = snapshot
15890 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15891 .map(|hunk| match hunk {
15892 DisplayDiffHunk::Unfolded {
15893 display_row_range, ..
15894 } => display_row_range,
15895 DisplayDiffHunk::Folded { .. } => unreachable!(),
15896 })
15897 .collect::<Vec<_>>();
15898 assert_eq!(
15899 hunks,
15900 [
15901 DisplayRow(2)..DisplayRow(4),
15902 DisplayRow(7)..DisplayRow(9),
15903 DisplayRow(12)..DisplayRow(14),
15904 ]
15905 );
15906}
15907
15908#[gpui::test]
15909async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15910 init_test(cx, |_| {});
15911
15912 let mut cx = EditorTestContext::new(cx).await;
15913 cx.set_head_text(indoc! { "
15914 one
15915 two
15916 three
15917 four
15918 five
15919 "
15920 });
15921 cx.set_index_text(indoc! { "
15922 one
15923 two
15924 three
15925 four
15926 five
15927 "
15928 });
15929 cx.set_state(indoc! {"
15930 one
15931 TWO
15932 ˇTHREE
15933 FOUR
15934 five
15935 "});
15936 cx.run_until_parked();
15937 cx.update_editor(|editor, window, cx| {
15938 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15939 });
15940 cx.run_until_parked();
15941 cx.assert_index_text(Some(indoc! {"
15942 one
15943 TWO
15944 THREE
15945 FOUR
15946 five
15947 "}));
15948 cx.set_state(indoc! { "
15949 one
15950 TWO
15951 ˇTHREE-HUNDRED
15952 FOUR
15953 five
15954 "});
15955 cx.run_until_parked();
15956 cx.update_editor(|editor, window, cx| {
15957 let snapshot = editor.snapshot(window, cx);
15958 let hunks = editor
15959 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15960 .collect::<Vec<_>>();
15961 assert_eq!(hunks.len(), 1);
15962 assert_eq!(
15963 hunks[0].status(),
15964 DiffHunkStatus {
15965 kind: DiffHunkStatusKind::Modified,
15966 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15967 }
15968 );
15969
15970 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15971 });
15972 cx.run_until_parked();
15973 cx.assert_index_text(Some(indoc! {"
15974 one
15975 TWO
15976 THREE-HUNDRED
15977 FOUR
15978 five
15979 "}));
15980}
15981
15982#[gpui::test]
15983fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15984 init_test(cx, |_| {});
15985
15986 let editor = cx.add_window(|window, cx| {
15987 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15988 build_editor(buffer, window, cx)
15989 });
15990
15991 let render_args = Arc::new(Mutex::new(None));
15992 let snapshot = editor
15993 .update(cx, |editor, window, cx| {
15994 let snapshot = editor.buffer().read(cx).snapshot(cx);
15995 let range =
15996 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15997
15998 struct RenderArgs {
15999 row: MultiBufferRow,
16000 folded: bool,
16001 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16002 }
16003
16004 let crease = Crease::inline(
16005 range,
16006 FoldPlaceholder::test(),
16007 {
16008 let toggle_callback = render_args.clone();
16009 move |row, folded, callback, _window, _cx| {
16010 *toggle_callback.lock() = Some(RenderArgs {
16011 row,
16012 folded,
16013 callback,
16014 });
16015 div()
16016 }
16017 },
16018 |_row, _folded, _window, _cx| div(),
16019 );
16020
16021 editor.insert_creases(Some(crease), cx);
16022 let snapshot = editor.snapshot(window, cx);
16023 let _div = snapshot.render_crease_toggle(
16024 MultiBufferRow(1),
16025 false,
16026 cx.entity().clone(),
16027 window,
16028 cx,
16029 );
16030 snapshot
16031 })
16032 .unwrap();
16033
16034 let render_args = render_args.lock().take().unwrap();
16035 assert_eq!(render_args.row, MultiBufferRow(1));
16036 assert!(!render_args.folded);
16037 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16038
16039 cx.update_window(*editor, |_, window, cx| {
16040 (render_args.callback)(true, window, cx)
16041 })
16042 .unwrap();
16043 let snapshot = editor
16044 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16045 .unwrap();
16046 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
16047
16048 cx.update_window(*editor, |_, window, cx| {
16049 (render_args.callback)(false, window, cx)
16050 })
16051 .unwrap();
16052 let snapshot = editor
16053 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16054 .unwrap();
16055 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16056}
16057
16058#[gpui::test]
16059async fn test_input_text(cx: &mut TestAppContext) {
16060 init_test(cx, |_| {});
16061 let mut cx = EditorTestContext::new(cx).await;
16062
16063 cx.set_state(
16064 &r#"ˇone
16065 two
16066
16067 three
16068 fourˇ
16069 five
16070
16071 siˇx"#
16072 .unindent(),
16073 );
16074
16075 cx.dispatch_action(HandleInput(String::new()));
16076 cx.assert_editor_state(
16077 &r#"ˇone
16078 two
16079
16080 three
16081 fourˇ
16082 five
16083
16084 siˇx"#
16085 .unindent(),
16086 );
16087
16088 cx.dispatch_action(HandleInput("AAAA".to_string()));
16089 cx.assert_editor_state(
16090 &r#"AAAAˇone
16091 two
16092
16093 three
16094 fourAAAAˇ
16095 five
16096
16097 siAAAAˇx"#
16098 .unindent(),
16099 );
16100}
16101
16102#[gpui::test]
16103async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16104 init_test(cx, |_| {});
16105
16106 let mut cx = EditorTestContext::new(cx).await;
16107 cx.set_state(
16108 r#"let foo = 1;
16109let foo = 2;
16110let foo = 3;
16111let fooˇ = 4;
16112let foo = 5;
16113let foo = 6;
16114let foo = 7;
16115let foo = 8;
16116let foo = 9;
16117let foo = 10;
16118let foo = 11;
16119let foo = 12;
16120let foo = 13;
16121let foo = 14;
16122let foo = 15;"#,
16123 );
16124
16125 cx.update_editor(|e, window, cx| {
16126 assert_eq!(
16127 e.next_scroll_position,
16128 NextScrollCursorCenterTopBottom::Center,
16129 "Default next scroll direction is center",
16130 );
16131
16132 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16133 assert_eq!(
16134 e.next_scroll_position,
16135 NextScrollCursorCenterTopBottom::Top,
16136 "After center, next scroll direction should be top",
16137 );
16138
16139 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16140 assert_eq!(
16141 e.next_scroll_position,
16142 NextScrollCursorCenterTopBottom::Bottom,
16143 "After top, next scroll direction should be bottom",
16144 );
16145
16146 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16147 assert_eq!(
16148 e.next_scroll_position,
16149 NextScrollCursorCenterTopBottom::Center,
16150 "After bottom, scrolling should start over",
16151 );
16152
16153 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16154 assert_eq!(
16155 e.next_scroll_position,
16156 NextScrollCursorCenterTopBottom::Top,
16157 "Scrolling continues if retriggered fast enough"
16158 );
16159 });
16160
16161 cx.executor()
16162 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16163 cx.executor().run_until_parked();
16164 cx.update_editor(|e, _, _| {
16165 assert_eq!(
16166 e.next_scroll_position,
16167 NextScrollCursorCenterTopBottom::Center,
16168 "If scrolling is not triggered fast enough, it should reset"
16169 );
16170 });
16171}
16172
16173#[gpui::test]
16174async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16175 init_test(cx, |_| {});
16176 let mut cx = EditorLspTestContext::new_rust(
16177 lsp::ServerCapabilities {
16178 definition_provider: Some(lsp::OneOf::Left(true)),
16179 references_provider: Some(lsp::OneOf::Left(true)),
16180 ..lsp::ServerCapabilities::default()
16181 },
16182 cx,
16183 )
16184 .await;
16185
16186 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16187 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
16188 move |params, _| async move {
16189 if empty_go_to_definition {
16190 Ok(None)
16191 } else {
16192 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16193 uri: params.text_document_position_params.text_document.uri,
16194 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
16195 })))
16196 }
16197 },
16198 );
16199 let references =
16200 cx.lsp
16201 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
16202 Ok(Some(vec![lsp::Location {
16203 uri: params.text_document_position.text_document.uri,
16204 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16205 }]))
16206 });
16207 (go_to_definition, references)
16208 };
16209
16210 cx.set_state(
16211 &r#"fn one() {
16212 let mut a = ˇtwo();
16213 }
16214
16215 fn two() {}"#
16216 .unindent(),
16217 );
16218 set_up_lsp_handlers(false, &mut cx);
16219 let navigated = cx
16220 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16221 .await
16222 .expect("Failed to navigate to definition");
16223 assert_eq!(
16224 navigated,
16225 Navigated::Yes,
16226 "Should have navigated to definition from the GetDefinition response"
16227 );
16228 cx.assert_editor_state(
16229 &r#"fn one() {
16230 let mut a = two();
16231 }
16232
16233 fn «twoˇ»() {}"#
16234 .unindent(),
16235 );
16236
16237 let editors = cx.update_workspace(|workspace, _, cx| {
16238 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16239 });
16240 cx.update_editor(|_, _, test_editor_cx| {
16241 assert_eq!(
16242 editors.len(),
16243 1,
16244 "Initially, only one, test, editor should be open in the workspace"
16245 );
16246 assert_eq!(
16247 test_editor_cx.entity(),
16248 editors.last().expect("Asserted len is 1").clone()
16249 );
16250 });
16251
16252 set_up_lsp_handlers(true, &mut cx);
16253 let navigated = cx
16254 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16255 .await
16256 .expect("Failed to navigate to lookup references");
16257 assert_eq!(
16258 navigated,
16259 Navigated::Yes,
16260 "Should have navigated to references as a fallback after empty GoToDefinition response"
16261 );
16262 // We should not change the selections in the existing file,
16263 // if opening another milti buffer with the references
16264 cx.assert_editor_state(
16265 &r#"fn one() {
16266 let mut a = two();
16267 }
16268
16269 fn «twoˇ»() {}"#
16270 .unindent(),
16271 );
16272 let editors = cx.update_workspace(|workspace, _, cx| {
16273 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16274 });
16275 cx.update_editor(|_, _, test_editor_cx| {
16276 assert_eq!(
16277 editors.len(),
16278 2,
16279 "After falling back to references search, we open a new editor with the results"
16280 );
16281 let references_fallback_text = editors
16282 .into_iter()
16283 .find(|new_editor| *new_editor != test_editor_cx.entity())
16284 .expect("Should have one non-test editor now")
16285 .read(test_editor_cx)
16286 .text(test_editor_cx);
16287 assert_eq!(
16288 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16289 "Should use the range from the references response and not the GoToDefinition one"
16290 );
16291 });
16292}
16293
16294#[gpui::test]
16295async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16296 init_test(cx, |_| {});
16297
16298 let language = Arc::new(Language::new(
16299 LanguageConfig::default(),
16300 Some(tree_sitter_rust::LANGUAGE.into()),
16301 ));
16302
16303 let text = r#"
16304 #[cfg(test)]
16305 mod tests() {
16306 #[test]
16307 fn runnable_1() {
16308 let a = 1;
16309 }
16310
16311 #[test]
16312 fn runnable_2() {
16313 let a = 1;
16314 let b = 2;
16315 }
16316 }
16317 "#
16318 .unindent();
16319
16320 let fs = FakeFs::new(cx.executor());
16321 fs.insert_file("/file.rs", Default::default()).await;
16322
16323 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16324 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16325 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16326 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16327 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16328
16329 let editor = cx.new_window_entity(|window, cx| {
16330 Editor::new(
16331 EditorMode::Full,
16332 multi_buffer,
16333 Some(project.clone()),
16334 window,
16335 cx,
16336 )
16337 });
16338
16339 editor.update_in(cx, |editor, window, cx| {
16340 let snapshot = editor.buffer().read(cx).snapshot(cx);
16341 editor.tasks.insert(
16342 (buffer.read(cx).remote_id(), 3),
16343 RunnableTasks {
16344 templates: vec![],
16345 offset: snapshot.anchor_before(43),
16346 column: 0,
16347 extra_variables: HashMap::default(),
16348 context_range: BufferOffset(43)..BufferOffset(85),
16349 },
16350 );
16351 editor.tasks.insert(
16352 (buffer.read(cx).remote_id(), 8),
16353 RunnableTasks {
16354 templates: vec![],
16355 offset: snapshot.anchor_before(86),
16356 column: 0,
16357 extra_variables: HashMap::default(),
16358 context_range: BufferOffset(86)..BufferOffset(191),
16359 },
16360 );
16361
16362 // Test finding task when cursor is inside function body
16363 editor.change_selections(None, window, cx, |s| {
16364 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16365 });
16366 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16367 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16368
16369 // Test finding task when cursor is on function name
16370 editor.change_selections(None, window, cx, |s| {
16371 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16372 });
16373 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16374 assert_eq!(row, 8, "Should find task when cursor is on function name");
16375 });
16376}
16377
16378#[gpui::test]
16379async fn test_folding_buffers(cx: &mut TestAppContext) {
16380 init_test(cx, |_| {});
16381
16382 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16383 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16384 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16385
16386 let fs = FakeFs::new(cx.executor());
16387 fs.insert_tree(
16388 path!("/a"),
16389 json!({
16390 "first.rs": sample_text_1,
16391 "second.rs": sample_text_2,
16392 "third.rs": sample_text_3,
16393 }),
16394 )
16395 .await;
16396 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16397 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16398 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16399 let worktree = project.update(cx, |project, cx| {
16400 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16401 assert_eq!(worktrees.len(), 1);
16402 worktrees.pop().unwrap()
16403 });
16404 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16405
16406 let buffer_1 = project
16407 .update(cx, |project, cx| {
16408 project.open_buffer((worktree_id, "first.rs"), cx)
16409 })
16410 .await
16411 .unwrap();
16412 let buffer_2 = project
16413 .update(cx, |project, cx| {
16414 project.open_buffer((worktree_id, "second.rs"), cx)
16415 })
16416 .await
16417 .unwrap();
16418 let buffer_3 = project
16419 .update(cx, |project, cx| {
16420 project.open_buffer((worktree_id, "third.rs"), cx)
16421 })
16422 .await
16423 .unwrap();
16424
16425 let multi_buffer = cx.new(|cx| {
16426 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16427 multi_buffer.push_excerpts(
16428 buffer_1.clone(),
16429 [
16430 ExcerptRange {
16431 context: Point::new(0, 0)..Point::new(3, 0),
16432 primary: None,
16433 },
16434 ExcerptRange {
16435 context: Point::new(5, 0)..Point::new(7, 0),
16436 primary: None,
16437 },
16438 ExcerptRange {
16439 context: Point::new(9, 0)..Point::new(10, 4),
16440 primary: None,
16441 },
16442 ],
16443 cx,
16444 );
16445 multi_buffer.push_excerpts(
16446 buffer_2.clone(),
16447 [
16448 ExcerptRange {
16449 context: Point::new(0, 0)..Point::new(3, 0),
16450 primary: None,
16451 },
16452 ExcerptRange {
16453 context: Point::new(5, 0)..Point::new(7, 0),
16454 primary: None,
16455 },
16456 ExcerptRange {
16457 context: Point::new(9, 0)..Point::new(10, 4),
16458 primary: None,
16459 },
16460 ],
16461 cx,
16462 );
16463 multi_buffer.push_excerpts(
16464 buffer_3.clone(),
16465 [
16466 ExcerptRange {
16467 context: Point::new(0, 0)..Point::new(3, 0),
16468 primary: None,
16469 },
16470 ExcerptRange {
16471 context: Point::new(5, 0)..Point::new(7, 0),
16472 primary: None,
16473 },
16474 ExcerptRange {
16475 context: Point::new(9, 0)..Point::new(10, 4),
16476 primary: None,
16477 },
16478 ],
16479 cx,
16480 );
16481 multi_buffer
16482 });
16483 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16484 Editor::new(
16485 EditorMode::Full,
16486 multi_buffer.clone(),
16487 Some(project.clone()),
16488 window,
16489 cx,
16490 )
16491 });
16492
16493 assert_eq!(
16494 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16495 "\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",
16496 );
16497
16498 multi_buffer_editor.update(cx, |editor, cx| {
16499 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16500 });
16501 assert_eq!(
16502 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16503 "\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",
16504 "After folding the first buffer, its text should not be displayed"
16505 );
16506
16507 multi_buffer_editor.update(cx, |editor, cx| {
16508 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16509 });
16510 assert_eq!(
16511 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16512 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16513 "After folding the second buffer, its text should not be displayed"
16514 );
16515
16516 multi_buffer_editor.update(cx, |editor, cx| {
16517 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16518 });
16519 assert_eq!(
16520 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16521 "\n\n\n\n\n",
16522 "After folding the third buffer, its text should not be displayed"
16523 );
16524
16525 // Emulate selection inside the fold logic, that should work
16526 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16527 editor
16528 .snapshot(window, cx)
16529 .next_line_boundary(Point::new(0, 4));
16530 });
16531
16532 multi_buffer_editor.update(cx, |editor, cx| {
16533 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16534 });
16535 assert_eq!(
16536 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16537 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16538 "After unfolding the second buffer, its text should be displayed"
16539 );
16540
16541 // Typing inside of buffer 1 causes that buffer to be unfolded.
16542 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16543 assert_eq!(
16544 multi_buffer
16545 .read(cx)
16546 .snapshot(cx)
16547 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16548 .collect::<String>(),
16549 "bbbb"
16550 );
16551 editor.change_selections(None, window, cx, |selections| {
16552 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16553 });
16554 editor.handle_input("B", window, cx);
16555 });
16556
16557 assert_eq!(
16558 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16559 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16560 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16561 );
16562
16563 multi_buffer_editor.update(cx, |editor, cx| {
16564 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16565 });
16566 assert_eq!(
16567 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16568 "\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",
16569 "After unfolding the all buffers, all original text should be displayed"
16570 );
16571}
16572
16573#[gpui::test]
16574async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16575 init_test(cx, |_| {});
16576
16577 let sample_text_1 = "1111\n2222\n3333".to_string();
16578 let sample_text_2 = "4444\n5555\n6666".to_string();
16579 let sample_text_3 = "7777\n8888\n9999".to_string();
16580
16581 let fs = FakeFs::new(cx.executor());
16582 fs.insert_tree(
16583 path!("/a"),
16584 json!({
16585 "first.rs": sample_text_1,
16586 "second.rs": sample_text_2,
16587 "third.rs": sample_text_3,
16588 }),
16589 )
16590 .await;
16591 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16592 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16593 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16594 let worktree = project.update(cx, |project, cx| {
16595 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16596 assert_eq!(worktrees.len(), 1);
16597 worktrees.pop().unwrap()
16598 });
16599 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16600
16601 let buffer_1 = project
16602 .update(cx, |project, cx| {
16603 project.open_buffer((worktree_id, "first.rs"), cx)
16604 })
16605 .await
16606 .unwrap();
16607 let buffer_2 = project
16608 .update(cx, |project, cx| {
16609 project.open_buffer((worktree_id, "second.rs"), cx)
16610 })
16611 .await
16612 .unwrap();
16613 let buffer_3 = project
16614 .update(cx, |project, cx| {
16615 project.open_buffer((worktree_id, "third.rs"), cx)
16616 })
16617 .await
16618 .unwrap();
16619
16620 let multi_buffer = cx.new(|cx| {
16621 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16622 multi_buffer.push_excerpts(
16623 buffer_1.clone(),
16624 [ExcerptRange {
16625 context: Point::new(0, 0)..Point::new(3, 0),
16626 primary: None,
16627 }],
16628 cx,
16629 );
16630 multi_buffer.push_excerpts(
16631 buffer_2.clone(),
16632 [ExcerptRange {
16633 context: Point::new(0, 0)..Point::new(3, 0),
16634 primary: None,
16635 }],
16636 cx,
16637 );
16638 multi_buffer.push_excerpts(
16639 buffer_3.clone(),
16640 [ExcerptRange {
16641 context: Point::new(0, 0)..Point::new(3, 0),
16642 primary: None,
16643 }],
16644 cx,
16645 );
16646 multi_buffer
16647 });
16648
16649 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16650 Editor::new(
16651 EditorMode::Full,
16652 multi_buffer,
16653 Some(project.clone()),
16654 window,
16655 cx,
16656 )
16657 });
16658
16659 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16660 assert_eq!(
16661 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16662 full_text,
16663 );
16664
16665 multi_buffer_editor.update(cx, |editor, cx| {
16666 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16667 });
16668 assert_eq!(
16669 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16670 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16671 "After folding the first buffer, its text should not be displayed"
16672 );
16673
16674 multi_buffer_editor.update(cx, |editor, cx| {
16675 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16676 });
16677
16678 assert_eq!(
16679 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16680 "\n\n\n\n\n\n7777\n8888\n9999",
16681 "After folding the second buffer, its text should not be displayed"
16682 );
16683
16684 multi_buffer_editor.update(cx, |editor, cx| {
16685 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16686 });
16687 assert_eq!(
16688 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16689 "\n\n\n\n\n",
16690 "After folding the third buffer, its text should not be displayed"
16691 );
16692
16693 multi_buffer_editor.update(cx, |editor, cx| {
16694 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16695 });
16696 assert_eq!(
16697 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16698 "\n\n\n\n4444\n5555\n6666\n\n",
16699 "After unfolding the second buffer, its text should be displayed"
16700 );
16701
16702 multi_buffer_editor.update(cx, |editor, cx| {
16703 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16704 });
16705 assert_eq!(
16706 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16707 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16708 "After unfolding the first buffer, its text should be displayed"
16709 );
16710
16711 multi_buffer_editor.update(cx, |editor, cx| {
16712 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16713 });
16714 assert_eq!(
16715 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16716 full_text,
16717 "After unfolding all buffers, all original text should be displayed"
16718 );
16719}
16720
16721#[gpui::test]
16722async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16723 init_test(cx, |_| {});
16724
16725 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16726
16727 let fs = FakeFs::new(cx.executor());
16728 fs.insert_tree(
16729 path!("/a"),
16730 json!({
16731 "main.rs": sample_text,
16732 }),
16733 )
16734 .await;
16735 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16736 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16737 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16738 let worktree = project.update(cx, |project, cx| {
16739 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16740 assert_eq!(worktrees.len(), 1);
16741 worktrees.pop().unwrap()
16742 });
16743 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16744
16745 let buffer_1 = project
16746 .update(cx, |project, cx| {
16747 project.open_buffer((worktree_id, "main.rs"), cx)
16748 })
16749 .await
16750 .unwrap();
16751
16752 let multi_buffer = cx.new(|cx| {
16753 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16754 multi_buffer.push_excerpts(
16755 buffer_1.clone(),
16756 [ExcerptRange {
16757 context: Point::new(0, 0)
16758 ..Point::new(
16759 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16760 0,
16761 ),
16762 primary: None,
16763 }],
16764 cx,
16765 );
16766 multi_buffer
16767 });
16768 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16769 Editor::new(
16770 EditorMode::Full,
16771 multi_buffer,
16772 Some(project.clone()),
16773 window,
16774 cx,
16775 )
16776 });
16777
16778 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16779 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16780 enum TestHighlight {}
16781 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16782 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16783 editor.highlight_text::<TestHighlight>(
16784 vec![highlight_range.clone()],
16785 HighlightStyle::color(Hsla::green()),
16786 cx,
16787 );
16788 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16789 });
16790
16791 let full_text = format!("\n\n{sample_text}");
16792 assert_eq!(
16793 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16794 full_text,
16795 );
16796}
16797
16798#[gpui::test]
16799async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16800 init_test(cx, |_| {});
16801 cx.update(|cx| {
16802 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16803 "keymaps/default-linux.json",
16804 cx,
16805 )
16806 .unwrap();
16807 cx.bind_keys(default_key_bindings);
16808 });
16809
16810 let (editor, cx) = cx.add_window_view(|window, cx| {
16811 let multi_buffer = MultiBuffer::build_multi(
16812 [
16813 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16814 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16815 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16816 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16817 ],
16818 cx,
16819 );
16820 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16821
16822 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16823 // fold all but the second buffer, so that we test navigating between two
16824 // adjacent folded buffers, as well as folded buffers at the start and
16825 // end the multibuffer
16826 editor.fold_buffer(buffer_ids[0], cx);
16827 editor.fold_buffer(buffer_ids[2], cx);
16828 editor.fold_buffer(buffer_ids[3], cx);
16829
16830 editor
16831 });
16832 cx.simulate_resize(size(px(1000.), px(1000.)));
16833
16834 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16835 cx.assert_excerpts_with_selections(indoc! {"
16836 [EXCERPT]
16837 ˇ[FOLDED]
16838 [EXCERPT]
16839 a1
16840 b1
16841 [EXCERPT]
16842 [FOLDED]
16843 [EXCERPT]
16844 [FOLDED]
16845 "
16846 });
16847 cx.simulate_keystroke("down");
16848 cx.assert_excerpts_with_selections(indoc! {"
16849 [EXCERPT]
16850 [FOLDED]
16851 [EXCERPT]
16852 ˇa1
16853 b1
16854 [EXCERPT]
16855 [FOLDED]
16856 [EXCERPT]
16857 [FOLDED]
16858 "
16859 });
16860 cx.simulate_keystroke("down");
16861 cx.assert_excerpts_with_selections(indoc! {"
16862 [EXCERPT]
16863 [FOLDED]
16864 [EXCERPT]
16865 a1
16866 ˇb1
16867 [EXCERPT]
16868 [FOLDED]
16869 [EXCERPT]
16870 [FOLDED]
16871 "
16872 });
16873 cx.simulate_keystroke("down");
16874 cx.assert_excerpts_with_selections(indoc! {"
16875 [EXCERPT]
16876 [FOLDED]
16877 [EXCERPT]
16878 a1
16879 b1
16880 ˇ[EXCERPT]
16881 [FOLDED]
16882 [EXCERPT]
16883 [FOLDED]
16884 "
16885 });
16886 cx.simulate_keystroke("down");
16887 cx.assert_excerpts_with_selections(indoc! {"
16888 [EXCERPT]
16889 [FOLDED]
16890 [EXCERPT]
16891 a1
16892 b1
16893 [EXCERPT]
16894 ˇ[FOLDED]
16895 [EXCERPT]
16896 [FOLDED]
16897 "
16898 });
16899 for _ in 0..5 {
16900 cx.simulate_keystroke("down");
16901 cx.assert_excerpts_with_selections(indoc! {"
16902 [EXCERPT]
16903 [FOLDED]
16904 [EXCERPT]
16905 a1
16906 b1
16907 [EXCERPT]
16908 [FOLDED]
16909 [EXCERPT]
16910 ˇ[FOLDED]
16911 "
16912 });
16913 }
16914
16915 cx.simulate_keystroke("up");
16916 cx.assert_excerpts_with_selections(indoc! {"
16917 [EXCERPT]
16918 [FOLDED]
16919 [EXCERPT]
16920 a1
16921 b1
16922 [EXCERPT]
16923 ˇ[FOLDED]
16924 [EXCERPT]
16925 [FOLDED]
16926 "
16927 });
16928 cx.simulate_keystroke("up");
16929 cx.assert_excerpts_with_selections(indoc! {"
16930 [EXCERPT]
16931 [FOLDED]
16932 [EXCERPT]
16933 a1
16934 b1
16935 ˇ[EXCERPT]
16936 [FOLDED]
16937 [EXCERPT]
16938 [FOLDED]
16939 "
16940 });
16941 cx.simulate_keystroke("up");
16942 cx.assert_excerpts_with_selections(indoc! {"
16943 [EXCERPT]
16944 [FOLDED]
16945 [EXCERPT]
16946 a1
16947 ˇb1
16948 [EXCERPT]
16949 [FOLDED]
16950 [EXCERPT]
16951 [FOLDED]
16952 "
16953 });
16954 cx.simulate_keystroke("up");
16955 cx.assert_excerpts_with_selections(indoc! {"
16956 [EXCERPT]
16957 [FOLDED]
16958 [EXCERPT]
16959 ˇa1
16960 b1
16961 [EXCERPT]
16962 [FOLDED]
16963 [EXCERPT]
16964 [FOLDED]
16965 "
16966 });
16967 for _ in 0..5 {
16968 cx.simulate_keystroke("up");
16969 cx.assert_excerpts_with_selections(indoc! {"
16970 [EXCERPT]
16971 ˇ[FOLDED]
16972 [EXCERPT]
16973 a1
16974 b1
16975 [EXCERPT]
16976 [FOLDED]
16977 [EXCERPT]
16978 [FOLDED]
16979 "
16980 });
16981 }
16982}
16983
16984#[gpui::test]
16985async fn test_inline_completion_text(cx: &mut TestAppContext) {
16986 init_test(cx, |_| {});
16987
16988 // Simple insertion
16989 assert_highlighted_edits(
16990 "Hello, world!",
16991 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16992 true,
16993 cx,
16994 |highlighted_edits, cx| {
16995 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16996 assert_eq!(highlighted_edits.highlights.len(), 1);
16997 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16998 assert_eq!(
16999 highlighted_edits.highlights[0].1.background_color,
17000 Some(cx.theme().status().created_background)
17001 );
17002 },
17003 )
17004 .await;
17005
17006 // Replacement
17007 assert_highlighted_edits(
17008 "This is a test.",
17009 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
17010 false,
17011 cx,
17012 |highlighted_edits, cx| {
17013 assert_eq!(highlighted_edits.text, "That is a test.");
17014 assert_eq!(highlighted_edits.highlights.len(), 1);
17015 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
17016 assert_eq!(
17017 highlighted_edits.highlights[0].1.background_color,
17018 Some(cx.theme().status().created_background)
17019 );
17020 },
17021 )
17022 .await;
17023
17024 // Multiple edits
17025 assert_highlighted_edits(
17026 "Hello, world!",
17027 vec![
17028 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
17029 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
17030 ],
17031 false,
17032 cx,
17033 |highlighted_edits, cx| {
17034 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
17035 assert_eq!(highlighted_edits.highlights.len(), 2);
17036 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
17037 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
17038 assert_eq!(
17039 highlighted_edits.highlights[0].1.background_color,
17040 Some(cx.theme().status().created_background)
17041 );
17042 assert_eq!(
17043 highlighted_edits.highlights[1].1.background_color,
17044 Some(cx.theme().status().created_background)
17045 );
17046 },
17047 )
17048 .await;
17049
17050 // Multiple lines with edits
17051 assert_highlighted_edits(
17052 "First line\nSecond line\nThird line\nFourth line",
17053 vec![
17054 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
17055 (
17056 Point::new(2, 0)..Point::new(2, 10),
17057 "New third line".to_string(),
17058 ),
17059 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
17060 ],
17061 false,
17062 cx,
17063 |highlighted_edits, cx| {
17064 assert_eq!(
17065 highlighted_edits.text,
17066 "Second modified\nNew third line\nFourth updated line"
17067 );
17068 assert_eq!(highlighted_edits.highlights.len(), 3);
17069 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17070 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17071 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17072 for highlight in &highlighted_edits.highlights {
17073 assert_eq!(
17074 highlight.1.background_color,
17075 Some(cx.theme().status().created_background)
17076 );
17077 }
17078 },
17079 )
17080 .await;
17081}
17082
17083#[gpui::test]
17084async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17085 init_test(cx, |_| {});
17086
17087 // Deletion
17088 assert_highlighted_edits(
17089 "Hello, world!",
17090 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17091 true,
17092 cx,
17093 |highlighted_edits, cx| {
17094 assert_eq!(highlighted_edits.text, "Hello, world!");
17095 assert_eq!(highlighted_edits.highlights.len(), 1);
17096 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17097 assert_eq!(
17098 highlighted_edits.highlights[0].1.background_color,
17099 Some(cx.theme().status().deleted_background)
17100 );
17101 },
17102 )
17103 .await;
17104
17105 // Insertion
17106 assert_highlighted_edits(
17107 "Hello, world!",
17108 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17109 true,
17110 cx,
17111 |highlighted_edits, cx| {
17112 assert_eq!(highlighted_edits.highlights.len(), 1);
17113 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17114 assert_eq!(
17115 highlighted_edits.highlights[0].1.background_color,
17116 Some(cx.theme().status().created_background)
17117 );
17118 },
17119 )
17120 .await;
17121}
17122
17123async fn assert_highlighted_edits(
17124 text: &str,
17125 edits: Vec<(Range<Point>, String)>,
17126 include_deletions: bool,
17127 cx: &mut TestAppContext,
17128 assertion_fn: impl Fn(HighlightedText, &App),
17129) {
17130 let window = cx.add_window(|window, cx| {
17131 let buffer = MultiBuffer::build_simple(text, cx);
17132 Editor::new(EditorMode::Full, buffer, None, window, cx)
17133 });
17134 let cx = &mut VisualTestContext::from_window(*window, cx);
17135
17136 let (buffer, snapshot) = window
17137 .update(cx, |editor, _window, cx| {
17138 (
17139 editor.buffer().clone(),
17140 editor.buffer().read(cx).snapshot(cx),
17141 )
17142 })
17143 .unwrap();
17144
17145 let edits = edits
17146 .into_iter()
17147 .map(|(range, edit)| {
17148 (
17149 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17150 edit,
17151 )
17152 })
17153 .collect::<Vec<_>>();
17154
17155 let text_anchor_edits = edits
17156 .clone()
17157 .into_iter()
17158 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17159 .collect::<Vec<_>>();
17160
17161 let edit_preview = window
17162 .update(cx, |_, _window, cx| {
17163 buffer
17164 .read(cx)
17165 .as_singleton()
17166 .unwrap()
17167 .read(cx)
17168 .preview_edits(text_anchor_edits.into(), cx)
17169 })
17170 .unwrap()
17171 .await;
17172
17173 cx.update(|_window, cx| {
17174 let highlighted_edits = inline_completion_edit_text(
17175 &snapshot.as_singleton().unwrap().2,
17176 &edits,
17177 &edit_preview,
17178 include_deletions,
17179 cx,
17180 );
17181 assertion_fn(highlighted_edits, cx)
17182 });
17183}
17184
17185#[track_caller]
17186fn assert_breakpoint(
17187 breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
17188 path: &Arc<Path>,
17189 expected: Vec<(u32, BreakpointKind)>,
17190) {
17191 if expected.len() == 0usize {
17192 assert!(!breakpoints.contains_key(path));
17193 } else {
17194 let mut breakpoint = breakpoints
17195 .get(path)
17196 .unwrap()
17197 .into_iter()
17198 .map(|breakpoint| (breakpoint.position, breakpoint.kind.clone()))
17199 .collect::<Vec<_>>();
17200
17201 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17202
17203 assert_eq!(expected, breakpoint);
17204 }
17205}
17206
17207fn add_log_breakpoint_at_cursor(
17208 editor: &mut Editor,
17209 log_message: &str,
17210 window: &mut Window,
17211 cx: &mut Context<Editor>,
17212) {
17213 let (anchor, bp) = editor
17214 .breakpoint_at_cursor_head(window, cx)
17215 .unwrap_or_else(|| {
17216 let cursor_position: Point = editor.selections.newest(cx).head();
17217
17218 let breakpoint_position = editor
17219 .snapshot(window, cx)
17220 .display_snapshot
17221 .buffer_snapshot
17222 .anchor_before(Point::new(cursor_position.row, 0));
17223
17224 let kind = BreakpointKind::Log(Arc::from(log_message));
17225
17226 (breakpoint_position, Breakpoint { kind })
17227 });
17228
17229 editor.edit_breakpoint_at_anchor(
17230 anchor,
17231 bp.kind,
17232 BreakpointEditAction::EditLogMessage(log_message.into()),
17233 cx,
17234 );
17235}
17236
17237#[gpui::test]
17238async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
17239 init_test(cx, |_| {});
17240
17241 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17242 let fs = FakeFs::new(cx.executor());
17243 fs.insert_tree(
17244 path!("/a"),
17245 json!({
17246 "main.rs": sample_text,
17247 }),
17248 )
17249 .await;
17250 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17251 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17252 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17253
17254 let fs = FakeFs::new(cx.executor());
17255 fs.insert_tree(
17256 path!("/a"),
17257 json!({
17258 "main.rs": sample_text,
17259 }),
17260 )
17261 .await;
17262 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17263 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17264 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17265 let worktree_id = workspace
17266 .update(cx, |workspace, _window, cx| {
17267 workspace.project().update(cx, |project, cx| {
17268 project.worktrees(cx).next().unwrap().read(cx).id()
17269 })
17270 })
17271 .unwrap();
17272
17273 let buffer = project
17274 .update(cx, |project, cx| {
17275 project.open_buffer((worktree_id, "main.rs"), cx)
17276 })
17277 .await
17278 .unwrap();
17279
17280 let (editor, cx) = cx.add_window_view(|window, cx| {
17281 Editor::new(
17282 EditorMode::Full,
17283 MultiBuffer::build_from_buffer(buffer, cx),
17284 Some(project.clone()),
17285 window,
17286 cx,
17287 )
17288 });
17289
17290 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17291 let abs_path = project.read_with(cx, |project, cx| {
17292 project
17293 .absolute_path(&project_path, cx)
17294 .map(|path_buf| Arc::from(path_buf.to_owned()))
17295 .unwrap()
17296 });
17297
17298 // assert we can add breakpoint on the first line
17299 editor.update_in(cx, |editor, window, cx| {
17300 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17301 editor.move_to_end(&MoveToEnd, window, cx);
17302 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17303 });
17304
17305 let breakpoints = editor.update(cx, |editor, cx| {
17306 editor
17307 .breakpoint_store()
17308 .as_ref()
17309 .unwrap()
17310 .read(cx)
17311 .all_breakpoints(cx)
17312 .clone()
17313 });
17314
17315 assert_eq!(1, breakpoints.len());
17316 assert_breakpoint(
17317 &breakpoints,
17318 &abs_path,
17319 vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17320 );
17321
17322 editor.update_in(cx, |editor, window, cx| {
17323 editor.move_to_beginning(&MoveToBeginning, window, cx);
17324 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17325 });
17326
17327 let breakpoints = editor.update(cx, |editor, cx| {
17328 editor
17329 .breakpoint_store()
17330 .as_ref()
17331 .unwrap()
17332 .read(cx)
17333 .all_breakpoints(cx)
17334 .clone()
17335 });
17336
17337 assert_eq!(1, breakpoints.len());
17338 assert_breakpoint(&breakpoints, &abs_path, vec![(3, BreakpointKind::Standard)]);
17339
17340 editor.update_in(cx, |editor, window, cx| {
17341 editor.move_to_end(&MoveToEnd, window, cx);
17342 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17343 });
17344
17345 let breakpoints = editor.update(cx, |editor, cx| {
17346 editor
17347 .breakpoint_store()
17348 .as_ref()
17349 .unwrap()
17350 .read(cx)
17351 .all_breakpoints(cx)
17352 .clone()
17353 });
17354
17355 assert_eq!(0, breakpoints.len());
17356 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17357}
17358
17359#[gpui::test]
17360async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
17361 init_test(cx, |_| {});
17362
17363 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17364
17365 let fs = FakeFs::new(cx.executor());
17366 fs.insert_tree(
17367 path!("/a"),
17368 json!({
17369 "main.rs": sample_text,
17370 }),
17371 )
17372 .await;
17373 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17374 let (workspace, cx) =
17375 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
17376
17377 let worktree_id = workspace.update(cx, |workspace, cx| {
17378 workspace.project().update(cx, |project, cx| {
17379 project.worktrees(cx).next().unwrap().read(cx).id()
17380 })
17381 });
17382
17383 let buffer = project
17384 .update(cx, |project, cx| {
17385 project.open_buffer((worktree_id, "main.rs"), cx)
17386 })
17387 .await
17388 .unwrap();
17389
17390 let (editor, cx) = cx.add_window_view(|window, cx| {
17391 Editor::new(
17392 EditorMode::Full,
17393 MultiBuffer::build_from_buffer(buffer, cx),
17394 Some(project.clone()),
17395 window,
17396 cx,
17397 )
17398 });
17399
17400 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17401 let abs_path = project.read_with(cx, |project, cx| {
17402 project
17403 .absolute_path(&project_path, cx)
17404 .map(|path_buf| Arc::from(path_buf.to_owned()))
17405 .unwrap()
17406 });
17407
17408 editor.update_in(cx, |editor, window, cx| {
17409 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17410 });
17411
17412 let breakpoints = editor.update(cx, |editor, cx| {
17413 editor
17414 .breakpoint_store()
17415 .as_ref()
17416 .unwrap()
17417 .read(cx)
17418 .all_breakpoints(cx)
17419 .clone()
17420 });
17421
17422 assert_breakpoint(
17423 &breakpoints,
17424 &abs_path,
17425 vec![(0, BreakpointKind::Log("hello world".into()))],
17426 );
17427
17428 // Removing a log message from a log breakpoint should remove it
17429 editor.update_in(cx, |editor, window, cx| {
17430 add_log_breakpoint_at_cursor(editor, "", window, cx);
17431 });
17432
17433 let breakpoints = editor.update(cx, |editor, cx| {
17434 editor
17435 .breakpoint_store()
17436 .as_ref()
17437 .unwrap()
17438 .read(cx)
17439 .all_breakpoints(cx)
17440 .clone()
17441 });
17442
17443 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17444
17445 editor.update_in(cx, |editor, window, cx| {
17446 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17447 editor.move_to_end(&MoveToEnd, window, cx);
17448 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17449 // Not adding a log message to a standard breakpoint shouldn't remove it
17450 add_log_breakpoint_at_cursor(editor, "", window, cx);
17451 });
17452
17453 let breakpoints = editor.update(cx, |editor, cx| {
17454 editor
17455 .breakpoint_store()
17456 .as_ref()
17457 .unwrap()
17458 .read(cx)
17459 .all_breakpoints(cx)
17460 .clone()
17461 });
17462
17463 assert_breakpoint(
17464 &breakpoints,
17465 &abs_path,
17466 vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17467 );
17468
17469 editor.update_in(cx, |editor, window, cx| {
17470 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17471 });
17472
17473 let breakpoints = editor.update(cx, |editor, cx| {
17474 editor
17475 .breakpoint_store()
17476 .as_ref()
17477 .unwrap()
17478 .read(cx)
17479 .all_breakpoints(cx)
17480 .clone()
17481 });
17482
17483 assert_breakpoint(
17484 &breakpoints,
17485 &abs_path,
17486 vec![
17487 (0, BreakpointKind::Standard),
17488 (3, BreakpointKind::Log("hello world".into())),
17489 ],
17490 );
17491
17492 editor.update_in(cx, |editor, window, cx| {
17493 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
17494 });
17495
17496 let breakpoints = editor.update(cx, |editor, cx| {
17497 editor
17498 .breakpoint_store()
17499 .as_ref()
17500 .unwrap()
17501 .read(cx)
17502 .all_breakpoints(cx)
17503 .clone()
17504 });
17505
17506 assert_breakpoint(
17507 &breakpoints,
17508 &abs_path,
17509 vec![
17510 (0, BreakpointKind::Standard),
17511 (3, BreakpointKind::Log("hello Earth !!".into())),
17512 ],
17513 );
17514}
17515
17516#[gpui::test]
17517async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17518 init_test(cx, |_| {});
17519 let capabilities = lsp::ServerCapabilities {
17520 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17521 prepare_provider: Some(true),
17522 work_done_progress_options: Default::default(),
17523 })),
17524 ..Default::default()
17525 };
17526 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17527
17528 cx.set_state(indoc! {"
17529 struct Fˇoo {}
17530 "});
17531
17532 cx.update_editor(|editor, _, cx| {
17533 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17534 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17535 editor.highlight_background::<DocumentHighlightRead>(
17536 &[highlight_range],
17537 |c| c.editor_document_highlight_read_background,
17538 cx,
17539 );
17540 });
17541
17542 let mut prepare_rename_handler =
17543 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17544 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17545 start: lsp::Position {
17546 line: 0,
17547 character: 7,
17548 },
17549 end: lsp::Position {
17550 line: 0,
17551 character: 10,
17552 },
17553 })))
17554 });
17555 let prepare_rename_task = cx
17556 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17557 .expect("Prepare rename was not started");
17558 prepare_rename_handler.next().await.unwrap();
17559 prepare_rename_task.await.expect("Prepare rename failed");
17560
17561 let mut rename_handler =
17562 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17563 let edit = lsp::TextEdit {
17564 range: lsp::Range {
17565 start: lsp::Position {
17566 line: 0,
17567 character: 7,
17568 },
17569 end: lsp::Position {
17570 line: 0,
17571 character: 10,
17572 },
17573 },
17574 new_text: "FooRenamed".to_string(),
17575 };
17576 Ok(Some(lsp::WorkspaceEdit::new(
17577 // Specify the same edit twice
17578 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17579 )))
17580 });
17581 let rename_task = cx
17582 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17583 .expect("Confirm rename was not started");
17584 rename_handler.next().await.unwrap();
17585 rename_task.await.expect("Confirm rename failed");
17586 cx.run_until_parked();
17587
17588 // Despite two edits, only one is actually applied as those are identical
17589 cx.assert_editor_state(indoc! {"
17590 struct FooRenamedˇ {}
17591 "});
17592}
17593
17594#[gpui::test]
17595async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17596 init_test(cx, |_| {});
17597 // These capabilities indicate that the server does not support prepare rename.
17598 let capabilities = lsp::ServerCapabilities {
17599 rename_provider: Some(lsp::OneOf::Left(true)),
17600 ..Default::default()
17601 };
17602 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17603
17604 cx.set_state(indoc! {"
17605 struct Fˇoo {}
17606 "});
17607
17608 cx.update_editor(|editor, _window, cx| {
17609 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17610 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17611 editor.highlight_background::<DocumentHighlightRead>(
17612 &[highlight_range],
17613 |c| c.editor_document_highlight_read_background,
17614 cx,
17615 );
17616 });
17617
17618 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17619 .expect("Prepare rename was not started")
17620 .await
17621 .expect("Prepare rename failed");
17622
17623 let mut rename_handler =
17624 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17625 let edit = lsp::TextEdit {
17626 range: lsp::Range {
17627 start: lsp::Position {
17628 line: 0,
17629 character: 7,
17630 },
17631 end: lsp::Position {
17632 line: 0,
17633 character: 10,
17634 },
17635 },
17636 new_text: "FooRenamed".to_string(),
17637 };
17638 Ok(Some(lsp::WorkspaceEdit::new(
17639 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17640 )))
17641 });
17642 let rename_task = cx
17643 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17644 .expect("Confirm rename was not started");
17645 rename_handler.next().await.unwrap();
17646 rename_task.await.expect("Confirm rename failed");
17647 cx.run_until_parked();
17648
17649 // Correct range is renamed, as `surrounding_word` is used to find it.
17650 cx.assert_editor_state(indoc! {"
17651 struct FooRenamedˇ {}
17652 "});
17653}
17654
17655#[gpui::test]
17656async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17657 init_test(cx, |_| {});
17658 let mut cx = EditorTestContext::new(cx).await;
17659
17660 let language = Arc::new(
17661 Language::new(
17662 LanguageConfig::default(),
17663 Some(tree_sitter_html::LANGUAGE.into()),
17664 )
17665 .with_brackets_query(
17666 r#"
17667 ("<" @open "/>" @close)
17668 ("</" @open ">" @close)
17669 ("<" @open ">" @close)
17670 ("\"" @open "\"" @close)
17671 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17672 "#,
17673 )
17674 .unwrap(),
17675 );
17676 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17677
17678 cx.set_state(indoc! {"
17679 <span>ˇ</span>
17680 "});
17681 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17682 cx.assert_editor_state(indoc! {"
17683 <span>
17684 ˇ
17685 </span>
17686 "});
17687
17688 cx.set_state(indoc! {"
17689 <span><span></span>ˇ</span>
17690 "});
17691 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17692 cx.assert_editor_state(indoc! {"
17693 <span><span></span>
17694 ˇ</span>
17695 "});
17696
17697 cx.set_state(indoc! {"
17698 <span>ˇ
17699 </span>
17700 "});
17701 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17702 cx.assert_editor_state(indoc! {"
17703 <span>
17704 ˇ
17705 </span>
17706 "});
17707}
17708
17709#[gpui::test(iterations = 10)]
17710async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
17711 init_test(cx, |_| {});
17712
17713 let fs = FakeFs::new(cx.executor());
17714 fs.insert_tree(
17715 path!("/dir"),
17716 json!({
17717 "a.ts": "a",
17718 }),
17719 )
17720 .await;
17721
17722 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
17723 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17724 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17725
17726 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17727 language_registry.add(Arc::new(Language::new(
17728 LanguageConfig {
17729 name: "TypeScript".into(),
17730 matcher: LanguageMatcher {
17731 path_suffixes: vec!["ts".to_string()],
17732 ..Default::default()
17733 },
17734 ..Default::default()
17735 },
17736 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17737 )));
17738 let mut fake_language_servers = language_registry.register_fake_lsp(
17739 "TypeScript",
17740 FakeLspAdapter {
17741 capabilities: lsp::ServerCapabilities {
17742 code_lens_provider: Some(lsp::CodeLensOptions {
17743 resolve_provider: Some(true),
17744 }),
17745 execute_command_provider: Some(lsp::ExecuteCommandOptions {
17746 commands: vec!["_the/command".to_string()],
17747 ..lsp::ExecuteCommandOptions::default()
17748 }),
17749 ..lsp::ServerCapabilities::default()
17750 },
17751 ..FakeLspAdapter::default()
17752 },
17753 );
17754
17755 let (buffer, _handle) = project
17756 .update(cx, |p, cx| {
17757 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
17758 })
17759 .await
17760 .unwrap();
17761 cx.executor().run_until_parked();
17762
17763 let fake_server = fake_language_servers.next().await.unwrap();
17764
17765 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
17766 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
17767 drop(buffer_snapshot);
17768 let actions = cx
17769 .update_window(*workspace, |_, window, cx| {
17770 project.code_actions(&buffer, anchor..anchor, window, cx)
17771 })
17772 .unwrap();
17773
17774 fake_server
17775 .handle_request::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
17776 Ok(Some(vec![
17777 lsp::CodeLens {
17778 range: lsp::Range::default(),
17779 command: Some(lsp::Command {
17780 title: "Code lens command".to_owned(),
17781 command: "_the/command".to_owned(),
17782 arguments: None,
17783 }),
17784 data: None,
17785 },
17786 lsp::CodeLens {
17787 range: lsp::Range::default(),
17788 command: Some(lsp::Command {
17789 title: "Command not in capabilities".to_owned(),
17790 command: "not in capabilities".to_owned(),
17791 arguments: None,
17792 }),
17793 data: None,
17794 },
17795 lsp::CodeLens {
17796 range: lsp::Range {
17797 start: lsp::Position {
17798 line: 1,
17799 character: 1,
17800 },
17801 end: lsp::Position {
17802 line: 1,
17803 character: 1,
17804 },
17805 },
17806 command: Some(lsp::Command {
17807 title: "Command not in range".to_owned(),
17808 command: "_the/command".to_owned(),
17809 arguments: None,
17810 }),
17811 data: None,
17812 },
17813 ]))
17814 })
17815 .next()
17816 .await;
17817
17818 let actions = actions.await.unwrap();
17819 assert_eq!(
17820 actions.len(),
17821 1,
17822 "Should have only one valid action for the 0..0 range"
17823 );
17824 let action = actions[0].clone();
17825 let apply = project.update(cx, |project, cx| {
17826 project.apply_code_action(buffer.clone(), action, true, cx)
17827 });
17828
17829 // Resolving the code action does not populate its edits. In absence of
17830 // edits, we must execute the given command.
17831 fake_server.handle_request::<lsp::request::CodeLensResolve, _, _>(|mut lens, _| async move {
17832 let lens_command = lens.command.as_mut().expect("should have a command");
17833 assert_eq!(lens_command.title, "Code lens command");
17834 lens_command.arguments = Some(vec![json!("the-argument")]);
17835 Ok(lens)
17836 });
17837
17838 // While executing the command, the language server sends the editor
17839 // a `workspaceEdit` request.
17840 fake_server
17841 .handle_request::<lsp::request::ExecuteCommand, _, _>({
17842 let fake = fake_server.clone();
17843 move |params, _| {
17844 assert_eq!(params.command, "_the/command");
17845 let fake = fake.clone();
17846 async move {
17847 fake.server
17848 .request::<lsp::request::ApplyWorkspaceEdit>(
17849 lsp::ApplyWorkspaceEditParams {
17850 label: None,
17851 edit: lsp::WorkspaceEdit {
17852 changes: Some(
17853 [(
17854 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
17855 vec![lsp::TextEdit {
17856 range: lsp::Range::new(
17857 lsp::Position::new(0, 0),
17858 lsp::Position::new(0, 0),
17859 ),
17860 new_text: "X".into(),
17861 }],
17862 )]
17863 .into_iter()
17864 .collect(),
17865 ),
17866 ..Default::default()
17867 },
17868 },
17869 )
17870 .await
17871 .unwrap();
17872 Ok(Some(json!(null)))
17873 }
17874 }
17875 })
17876 .next()
17877 .await;
17878
17879 // Applying the code lens command returns a project transaction containing the edits
17880 // sent by the language server in its `workspaceEdit` request.
17881 let transaction = apply.await.unwrap();
17882 assert!(transaction.0.contains_key(&buffer));
17883 buffer.update(cx, |buffer, cx| {
17884 assert_eq!(buffer.text(), "Xa");
17885 buffer.undo(cx);
17886 assert_eq!(buffer.text(), "a");
17887 });
17888}
17889
17890mod autoclose_tags {
17891 use super::*;
17892 use language::language_settings::JsxTagAutoCloseSettings;
17893 use languages::language;
17894
17895 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17896 init_test(cx, |settings| {
17897 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17898 });
17899
17900 let mut cx = EditorTestContext::new(cx).await;
17901 cx.update_buffer(|buffer, cx| {
17902 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17903
17904 buffer.set_language(Some(language), cx)
17905 });
17906
17907 cx
17908 }
17909
17910 macro_rules! check {
17911 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17912 #[gpui::test]
17913 async fn $name(cx: &mut TestAppContext) {
17914 let mut cx = test_setup(cx).await;
17915 cx.set_state($initial);
17916 cx.run_until_parked();
17917
17918 cx.update_editor(|editor, window, cx| {
17919 editor.handle_input($input, window, cx);
17920 });
17921 cx.run_until_parked();
17922 cx.assert_editor_state($expected);
17923 }
17924 };
17925 }
17926
17927 check!(
17928 test_basic,
17929 "<divˇ" + ">" => "<div>ˇ</div>"
17930 );
17931
17932 check!(
17933 test_basic_nested,
17934 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17935 );
17936
17937 check!(
17938 test_basic_ignore_already_closed,
17939 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17940 );
17941
17942 check!(
17943 test_doesnt_autoclose_closing_tag,
17944 "</divˇ" + ">" => "</div>ˇ"
17945 );
17946
17947 check!(
17948 test_jsx_attr,
17949 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17950 );
17951
17952 check!(
17953 test_ignores_closing_tags_in_expr_block,
17954 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17955 );
17956
17957 check!(
17958 test_doesnt_autoclose_on_gt_in_expr,
17959 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17960 );
17961
17962 check!(
17963 test_ignores_closing_tags_with_different_tag_names,
17964 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17965 );
17966
17967 check!(
17968 test_autocloses_in_jsx_expression,
17969 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17970 );
17971
17972 check!(
17973 test_doesnt_autoclose_already_closed_in_jsx_expression,
17974 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17975 );
17976
17977 check!(
17978 test_autocloses_fragment,
17979 "<ˇ" + ">" => "<>ˇ</>"
17980 );
17981
17982 check!(
17983 test_does_not_include_type_argument_in_autoclose_tag_name,
17984 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17985 );
17986
17987 check!(
17988 test_does_not_autoclose_doctype,
17989 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17990 );
17991
17992 check!(
17993 test_does_not_autoclose_comment,
17994 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17995 );
17996
17997 check!(
17998 test_multi_cursor_autoclose_same_tag,
17999 r#"
18000 <divˇ
18001 <divˇ
18002 "#
18003 + ">" =>
18004 r#"
18005 <div>ˇ</div>
18006 <div>ˇ</div>
18007 "#
18008 );
18009
18010 check!(
18011 test_multi_cursor_autoclose_different_tags,
18012 r#"
18013 <divˇ
18014 <spanˇ
18015 "#
18016 + ">" =>
18017 r#"
18018 <div>ˇ</div>
18019 <span>ˇ</span>
18020 "#
18021 );
18022
18023 check!(
18024 test_multi_cursor_autoclose_some_dont_autoclose_others,
18025 r#"
18026 <divˇ
18027 <div /ˇ
18028 <spanˇ</span>
18029 <!DOCTYPE htmlˇ
18030 </headˇ
18031 <Component<T>ˇ
18032 ˇ
18033 "#
18034 + ">" =>
18035 r#"
18036 <div>ˇ</div>
18037 <div />ˇ
18038 <span>ˇ</span>
18039 <!DOCTYPE html>ˇ
18040 </head>ˇ
18041 <Component<T>>ˇ</Component>
18042 >ˇ
18043 "#
18044 );
18045
18046 check!(
18047 test_doesnt_mess_up_trailing_text,
18048 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
18049 );
18050
18051 #[gpui::test]
18052 async fn test_multibuffer(cx: &mut TestAppContext) {
18053 init_test(cx, |settings| {
18054 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
18055 });
18056
18057 let buffer_a = cx.new(|cx| {
18058 let mut buf = language::Buffer::local("<div", cx);
18059 buf.set_language(
18060 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18061 cx,
18062 );
18063 buf
18064 });
18065 let buffer_b = cx.new(|cx| {
18066 let mut buf = language::Buffer::local("<pre", cx);
18067 buf.set_language(
18068 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18069 cx,
18070 );
18071 buf
18072 });
18073 let buffer_c = cx.new(|cx| {
18074 let buf = language::Buffer::local("<span", cx);
18075 buf
18076 });
18077 let buffer = cx.new(|cx| {
18078 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
18079 buf.push_excerpts(
18080 buffer_a,
18081 [ExcerptRange {
18082 context: text::Anchor::MIN..text::Anchor::MAX,
18083 primary: None,
18084 }],
18085 cx,
18086 );
18087 buf.push_excerpts(
18088 buffer_b,
18089 [ExcerptRange {
18090 context: text::Anchor::MIN..text::Anchor::MAX,
18091 primary: None,
18092 }],
18093 cx,
18094 );
18095 buf.push_excerpts(
18096 buffer_c,
18097 [ExcerptRange {
18098 context: text::Anchor::MIN..text::Anchor::MAX,
18099 primary: None,
18100 }],
18101 cx,
18102 );
18103 buf
18104 });
18105 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18106
18107 let mut cx = EditorTestContext::for_editor(editor, cx).await;
18108
18109 cx.update_editor(|editor, window, cx| {
18110 editor.change_selections(None, window, cx, |selections| {
18111 selections.select(vec![
18112 Selection::from_offset(4),
18113 Selection::from_offset(9),
18114 Selection::from_offset(15),
18115 ])
18116 })
18117 });
18118 cx.run_until_parked();
18119
18120 cx.update_editor(|editor, window, cx| {
18121 editor.handle_input(">", window, cx);
18122 });
18123 cx.run_until_parked();
18124
18125 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
18126 }
18127}
18128
18129fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
18130 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
18131 point..point
18132}
18133
18134fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
18135 let (text, ranges) = marked_text_ranges(marked_text, true);
18136 assert_eq!(editor.text(cx), text);
18137 assert_eq!(
18138 editor.selections.ranges(cx),
18139 ranges,
18140 "Assert selections are {}",
18141 marked_text
18142 );
18143}
18144
18145pub fn handle_signature_help_request(
18146 cx: &mut EditorLspTestContext,
18147 mocked_response: lsp::SignatureHelp,
18148) -> impl Future<Output = ()> {
18149 let mut request =
18150 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
18151 let mocked_response = mocked_response.clone();
18152 async move { Ok(Some(mocked_response)) }
18153 });
18154
18155 async move {
18156 request.next().await;
18157 }
18158}
18159
18160/// Handle completion request passing a marked string specifying where the completion
18161/// should be triggered from using '|' character, what range should be replaced, and what completions
18162/// should be returned using '<' and '>' to delimit the range
18163pub fn handle_completion_request(
18164 cx: &mut EditorLspTestContext,
18165 marked_string: &str,
18166 completions: Vec<&'static str>,
18167 counter: Arc<AtomicUsize>,
18168) -> impl Future<Output = ()> {
18169 let complete_from_marker: TextRangeMarker = '|'.into();
18170 let replace_range_marker: TextRangeMarker = ('<', '>').into();
18171 let (_, mut marked_ranges) = marked_text_ranges_by(
18172 marked_string,
18173 vec![complete_from_marker.clone(), replace_range_marker.clone()],
18174 );
18175
18176 let complete_from_position =
18177 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
18178 let replace_range =
18179 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
18180
18181 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
18182 let completions = completions.clone();
18183 counter.fetch_add(1, atomic::Ordering::Release);
18184 async move {
18185 assert_eq!(params.text_document_position.text_document.uri, url.clone());
18186 assert_eq!(
18187 params.text_document_position.position,
18188 complete_from_position
18189 );
18190 Ok(Some(lsp::CompletionResponse::Array(
18191 completions
18192 .iter()
18193 .map(|completion_text| lsp::CompletionItem {
18194 label: completion_text.to_string(),
18195 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18196 range: replace_range,
18197 new_text: completion_text.to_string(),
18198 })),
18199 ..Default::default()
18200 })
18201 .collect(),
18202 )))
18203 }
18204 });
18205
18206 async move {
18207 request.next().await;
18208 }
18209}
18210
18211fn handle_resolve_completion_request(
18212 cx: &mut EditorLspTestContext,
18213 edits: Option<Vec<(&'static str, &'static str)>>,
18214) -> impl Future<Output = ()> {
18215 let edits = edits.map(|edits| {
18216 edits
18217 .iter()
18218 .map(|(marked_string, new_text)| {
18219 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
18220 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
18221 lsp::TextEdit::new(replace_range, new_text.to_string())
18222 })
18223 .collect::<Vec<_>>()
18224 });
18225
18226 let mut request =
18227 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18228 let edits = edits.clone();
18229 async move {
18230 Ok(lsp::CompletionItem {
18231 additional_text_edits: edits,
18232 ..Default::default()
18233 })
18234 }
18235 });
18236
18237 async move {
18238 request.next().await;
18239 }
18240}
18241
18242pub(crate) fn update_test_language_settings(
18243 cx: &mut TestAppContext,
18244 f: impl Fn(&mut AllLanguageSettingsContent),
18245) {
18246 cx.update(|cx| {
18247 SettingsStore::update_global(cx, |store, cx| {
18248 store.update_user_settings::<AllLanguageSettings>(cx, f);
18249 });
18250 });
18251}
18252
18253pub(crate) fn update_test_project_settings(
18254 cx: &mut TestAppContext,
18255 f: impl Fn(&mut ProjectSettings),
18256) {
18257 cx.update(|cx| {
18258 SettingsStore::update_global(cx, |store, cx| {
18259 store.update_user_settings::<ProjectSettings>(cx, f);
18260 });
18261 });
18262}
18263
18264pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
18265 cx.update(|cx| {
18266 assets::Assets.load_test_fonts(cx);
18267 let store = SettingsStore::test(cx);
18268 cx.set_global(store);
18269 theme::init(theme::LoadThemes::JustBase, cx);
18270 release_channel::init(SemanticVersion::default(), cx);
18271 client::init_settings(cx);
18272 language::init(cx);
18273 Project::init_settings(cx);
18274 workspace::init_settings(cx);
18275 crate::init(cx);
18276 });
18277
18278 update_test_language_settings(cx, f);
18279}
18280
18281#[track_caller]
18282fn assert_hunk_revert(
18283 not_reverted_text_with_selections: &str,
18284 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
18285 expected_reverted_text_with_selections: &str,
18286 base_text: &str,
18287 cx: &mut EditorLspTestContext,
18288) {
18289 cx.set_state(not_reverted_text_with_selections);
18290 cx.set_head_text(base_text);
18291 cx.executor().run_until_parked();
18292
18293 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
18294 let snapshot = editor.snapshot(window, cx);
18295 let reverted_hunk_statuses = snapshot
18296 .buffer_snapshot
18297 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
18298 .map(|hunk| hunk.status().kind)
18299 .collect::<Vec<_>>();
18300
18301 editor.git_restore(&Default::default(), window, cx);
18302 reverted_hunk_statuses
18303 });
18304 cx.executor().run_until_parked();
18305 cx.assert_editor_state(expected_reverted_text_with_selections);
18306 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
18307}