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 async move {
9316 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9317 cx.background_executor()
9318 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9319 .await;
9320 }
9321 Ok(Some(lsp::CompletionResponse::Array(vec![
9322 lsp::CompletionItem {
9323 label: "first".into(),
9324 ..lsp::CompletionItem::default()
9325 },
9326 lsp::CompletionItem {
9327 label: "last".into(),
9328 ..lsp::CompletionItem::default()
9329 },
9330 ])))
9331 }
9332 });
9333
9334 cx.set_state(indoc! {"
9335 oneˇ
9336 two
9337 three
9338 "});
9339 cx.simulate_keystroke(".");
9340 cx.executor().run_until_parked();
9341 cx.condition(|editor, _| editor.context_menu_visible())
9342 .await;
9343 cx.update_editor(|editor, window, cx| {
9344 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9345 {
9346 assert_eq!(
9347 completion_menu_entries(&menu),
9348 &["first", "last"],
9349 "When LSP server is fast to reply, no fallback word completions are used"
9350 );
9351 } else {
9352 panic!("expected completion menu to be open");
9353 }
9354 editor.cancel(&Cancel, window, cx);
9355 });
9356 cx.executor().run_until_parked();
9357 cx.condition(|editor, _| !editor.context_menu_visible())
9358 .await;
9359
9360 throttle_completions.store(true, atomic::Ordering::Release);
9361 cx.simulate_keystroke(".");
9362 cx.executor()
9363 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9364 cx.executor().run_until_parked();
9365 cx.condition(|editor, _| editor.context_menu_visible())
9366 .await;
9367 cx.update_editor(|editor, _, _| {
9368 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9369 {
9370 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9371 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9372 } else {
9373 panic!("expected completion menu to be open");
9374 }
9375 });
9376}
9377
9378#[gpui::test]
9379async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9380 init_test(cx, |language_settings| {
9381 language_settings.defaults.completions = Some(CompletionSettings {
9382 words: WordsCompletionMode::Enabled,
9383 lsp: true,
9384 lsp_fetch_timeout_ms: 0,
9385 });
9386 });
9387
9388 let mut cx = EditorLspTestContext::new_rust(
9389 lsp::ServerCapabilities {
9390 completion_provider: Some(lsp::CompletionOptions {
9391 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9392 ..lsp::CompletionOptions::default()
9393 }),
9394 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9395 ..lsp::ServerCapabilities::default()
9396 },
9397 cx,
9398 )
9399 .await;
9400
9401 let _completion_requests_handler =
9402 cx.lsp
9403 .server
9404 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9405 Ok(Some(lsp::CompletionResponse::Array(vec![
9406 lsp::CompletionItem {
9407 label: "first".into(),
9408 ..lsp::CompletionItem::default()
9409 },
9410 lsp::CompletionItem {
9411 label: "last".into(),
9412 ..lsp::CompletionItem::default()
9413 },
9414 ])))
9415 });
9416
9417 cx.set_state(indoc! {"ˇ
9418 first
9419 last
9420 second
9421 "});
9422 cx.simulate_keystroke(".");
9423 cx.executor().run_until_parked();
9424 cx.condition(|editor, _| editor.context_menu_visible())
9425 .await;
9426 cx.update_editor(|editor, _, _| {
9427 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9428 {
9429 assert_eq!(
9430 completion_menu_entries(&menu),
9431 &["first", "last", "second"],
9432 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9433 );
9434 } else {
9435 panic!("expected completion menu to be open");
9436 }
9437 });
9438}
9439
9440#[gpui::test]
9441async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
9442 init_test(cx, |language_settings| {
9443 language_settings.defaults.completions = Some(CompletionSettings {
9444 words: WordsCompletionMode::Disabled,
9445 lsp: true,
9446 lsp_fetch_timeout_ms: 0,
9447 });
9448 });
9449
9450 let mut cx = EditorLspTestContext::new_rust(
9451 lsp::ServerCapabilities {
9452 completion_provider: Some(lsp::CompletionOptions {
9453 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9454 ..lsp::CompletionOptions::default()
9455 }),
9456 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9457 ..lsp::ServerCapabilities::default()
9458 },
9459 cx,
9460 )
9461 .await;
9462
9463 let _completion_requests_handler =
9464 cx.lsp
9465 .server
9466 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9467 panic!("LSP completions should not be queried when dealing with word completions")
9468 });
9469
9470 cx.set_state(indoc! {"ˇ
9471 first
9472 last
9473 second
9474 "});
9475 cx.update_editor(|editor, window, cx| {
9476 editor.show_word_completions(&ShowWordCompletions, window, cx);
9477 });
9478 cx.executor().run_until_parked();
9479 cx.condition(|editor, _| editor.context_menu_visible())
9480 .await;
9481 cx.update_editor(|editor, _, _| {
9482 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9483 {
9484 assert_eq!(
9485 completion_menu_entries(&menu),
9486 &["first", "last", "second"],
9487 "`ShowWordCompletions` action should show word completions"
9488 );
9489 } else {
9490 panic!("expected completion menu to be open");
9491 }
9492 });
9493
9494 cx.simulate_keystroke("s");
9495 cx.executor().run_until_parked();
9496 cx.condition(|editor, _| editor.context_menu_visible())
9497 .await;
9498 cx.update_editor(|editor, _, _| {
9499 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9500 {
9501 assert_eq!(
9502 completion_menu_entries(&menu),
9503 &["second"],
9504 "After showing word completions, further editing should filter them and not query the LSP"
9505 );
9506 } else {
9507 panic!("expected completion menu to be open");
9508 }
9509 });
9510}
9511
9512#[gpui::test]
9513async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
9514 init_test(cx, |language_settings| {
9515 language_settings.defaults.completions = Some(CompletionSettings {
9516 words: WordsCompletionMode::Fallback,
9517 lsp: false,
9518 lsp_fetch_timeout_ms: 0,
9519 });
9520 });
9521
9522 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9523
9524 cx.set_state(indoc! {"ˇ
9525 0_usize
9526 let
9527 33
9528 4.5f32
9529 "});
9530 cx.update_editor(|editor, window, cx| {
9531 editor.show_completions(&ShowCompletions::default(), window, cx);
9532 });
9533 cx.executor().run_until_parked();
9534 cx.condition(|editor, _| editor.context_menu_visible())
9535 .await;
9536 cx.update_editor(|editor, window, cx| {
9537 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9538 {
9539 assert_eq!(
9540 completion_menu_entries(&menu),
9541 &["let"],
9542 "With no digits in the completion query, no digits should be in the word completions"
9543 );
9544 } else {
9545 panic!("expected completion menu to be open");
9546 }
9547 editor.cancel(&Cancel, window, cx);
9548 });
9549
9550 cx.set_state(indoc! {"3ˇ
9551 0_usize
9552 let
9553 3
9554 33.35f32
9555 "});
9556 cx.update_editor(|editor, window, cx| {
9557 editor.show_completions(&ShowCompletions::default(), window, cx);
9558 });
9559 cx.executor().run_until_parked();
9560 cx.condition(|editor, _| editor.context_menu_visible())
9561 .await;
9562 cx.update_editor(|editor, _, _| {
9563 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9564 {
9565 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
9566 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
9567 } else {
9568 panic!("expected completion menu to be open");
9569 }
9570 });
9571}
9572
9573#[gpui::test]
9574async fn test_multiline_completion(cx: &mut TestAppContext) {
9575 init_test(cx, |_| {});
9576
9577 let fs = FakeFs::new(cx.executor());
9578 fs.insert_tree(
9579 path!("/a"),
9580 json!({
9581 "main.ts": "a",
9582 }),
9583 )
9584 .await;
9585
9586 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9587 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9588 let typescript_language = Arc::new(Language::new(
9589 LanguageConfig {
9590 name: "TypeScript".into(),
9591 matcher: LanguageMatcher {
9592 path_suffixes: vec!["ts".to_string()],
9593 ..LanguageMatcher::default()
9594 },
9595 line_comments: vec!["// ".into()],
9596 ..LanguageConfig::default()
9597 },
9598 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9599 ));
9600 language_registry.add(typescript_language.clone());
9601 let mut fake_servers = language_registry.register_fake_lsp(
9602 "TypeScript",
9603 FakeLspAdapter {
9604 capabilities: lsp::ServerCapabilities {
9605 completion_provider: Some(lsp::CompletionOptions {
9606 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9607 ..lsp::CompletionOptions::default()
9608 }),
9609 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9610 ..lsp::ServerCapabilities::default()
9611 },
9612 // Emulate vtsls label generation
9613 label_for_completion: Some(Box::new(|item, _| {
9614 let text = if let Some(description) = item
9615 .label_details
9616 .as_ref()
9617 .and_then(|label_details| label_details.description.as_ref())
9618 {
9619 format!("{} {}", item.label, description)
9620 } else if let Some(detail) = &item.detail {
9621 format!("{} {}", item.label, detail)
9622 } else {
9623 item.label.clone()
9624 };
9625 let len = text.len();
9626 Some(language::CodeLabel {
9627 text,
9628 runs: Vec::new(),
9629 filter_range: 0..len,
9630 })
9631 })),
9632 ..FakeLspAdapter::default()
9633 },
9634 );
9635 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9636 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9637 let worktree_id = workspace
9638 .update(cx, |workspace, _window, cx| {
9639 workspace.project().update(cx, |project, cx| {
9640 project.worktrees(cx).next().unwrap().read(cx).id()
9641 })
9642 })
9643 .unwrap();
9644 let _buffer = project
9645 .update(cx, |project, cx| {
9646 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9647 })
9648 .await
9649 .unwrap();
9650 let editor = workspace
9651 .update(cx, |workspace, window, cx| {
9652 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9653 })
9654 .unwrap()
9655 .await
9656 .unwrap()
9657 .downcast::<Editor>()
9658 .unwrap();
9659 let fake_server = fake_servers.next().await.unwrap();
9660
9661 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9662 let multiline_label_2 = "a\nb\nc\n";
9663 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9664 let multiline_description = "d\ne\nf\n";
9665 let multiline_detail_2 = "g\nh\ni\n";
9666
9667 let mut completion_handle =
9668 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9669 Ok(Some(lsp::CompletionResponse::Array(vec![
9670 lsp::CompletionItem {
9671 label: multiline_label.to_string(),
9672 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9673 range: lsp::Range {
9674 start: lsp::Position {
9675 line: params.text_document_position.position.line,
9676 character: params.text_document_position.position.character,
9677 },
9678 end: lsp::Position {
9679 line: params.text_document_position.position.line,
9680 character: params.text_document_position.position.character,
9681 },
9682 },
9683 new_text: "new_text_1".to_string(),
9684 })),
9685 ..lsp::CompletionItem::default()
9686 },
9687 lsp::CompletionItem {
9688 label: "single line label 1".to_string(),
9689 detail: Some(multiline_detail.to_string()),
9690 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9691 range: lsp::Range {
9692 start: lsp::Position {
9693 line: params.text_document_position.position.line,
9694 character: params.text_document_position.position.character,
9695 },
9696 end: lsp::Position {
9697 line: params.text_document_position.position.line,
9698 character: params.text_document_position.position.character,
9699 },
9700 },
9701 new_text: "new_text_2".to_string(),
9702 })),
9703 ..lsp::CompletionItem::default()
9704 },
9705 lsp::CompletionItem {
9706 label: "single line label 2".to_string(),
9707 label_details: Some(lsp::CompletionItemLabelDetails {
9708 description: Some(multiline_description.to_string()),
9709 detail: None,
9710 }),
9711 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9712 range: lsp::Range {
9713 start: lsp::Position {
9714 line: params.text_document_position.position.line,
9715 character: params.text_document_position.position.character,
9716 },
9717 end: lsp::Position {
9718 line: params.text_document_position.position.line,
9719 character: params.text_document_position.position.character,
9720 },
9721 },
9722 new_text: "new_text_2".to_string(),
9723 })),
9724 ..lsp::CompletionItem::default()
9725 },
9726 lsp::CompletionItem {
9727 label: multiline_label_2.to_string(),
9728 detail: Some(multiline_detail_2.to_string()),
9729 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9730 range: lsp::Range {
9731 start: lsp::Position {
9732 line: params.text_document_position.position.line,
9733 character: params.text_document_position.position.character,
9734 },
9735 end: lsp::Position {
9736 line: params.text_document_position.position.line,
9737 character: params.text_document_position.position.character,
9738 },
9739 },
9740 new_text: "new_text_3".to_string(),
9741 })),
9742 ..lsp::CompletionItem::default()
9743 },
9744 lsp::CompletionItem {
9745 label: "Label with many spaces and \t but without newlines".to_string(),
9746 detail: Some(
9747 "Details with many spaces and \t but without newlines".to_string(),
9748 ),
9749 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9750 range: lsp::Range {
9751 start: lsp::Position {
9752 line: params.text_document_position.position.line,
9753 character: params.text_document_position.position.character,
9754 },
9755 end: lsp::Position {
9756 line: params.text_document_position.position.line,
9757 character: params.text_document_position.position.character,
9758 },
9759 },
9760 new_text: "new_text_4".to_string(),
9761 })),
9762 ..lsp::CompletionItem::default()
9763 },
9764 ])))
9765 });
9766
9767 editor.update_in(cx, |editor, window, cx| {
9768 cx.focus_self(window);
9769 editor.move_to_end(&MoveToEnd, window, cx);
9770 editor.handle_input(".", window, cx);
9771 });
9772 cx.run_until_parked();
9773 completion_handle.next().await.unwrap();
9774
9775 editor.update(cx, |editor, _| {
9776 assert!(editor.context_menu_visible());
9777 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9778 {
9779 let completion_labels = menu
9780 .completions
9781 .borrow()
9782 .iter()
9783 .map(|c| c.label.text.clone())
9784 .collect::<Vec<_>>();
9785 assert_eq!(
9786 completion_labels,
9787 &[
9788 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9789 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9790 "single line label 2 d e f ",
9791 "a b c g h i ",
9792 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9793 ],
9794 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9795 );
9796
9797 for completion in menu
9798 .completions
9799 .borrow()
9800 .iter() {
9801 assert_eq!(
9802 completion.label.filter_range,
9803 0..completion.label.text.len(),
9804 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9805 );
9806 }
9807
9808 } else {
9809 panic!("expected completion menu to be open");
9810 }
9811 });
9812}
9813
9814#[gpui::test]
9815async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9816 init_test(cx, |_| {});
9817 let mut cx = EditorLspTestContext::new_rust(
9818 lsp::ServerCapabilities {
9819 completion_provider: Some(lsp::CompletionOptions {
9820 trigger_characters: Some(vec![".".to_string()]),
9821 ..Default::default()
9822 }),
9823 ..Default::default()
9824 },
9825 cx,
9826 )
9827 .await;
9828 cx.lsp
9829 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9830 Ok(Some(lsp::CompletionResponse::Array(vec![
9831 lsp::CompletionItem {
9832 label: "first".into(),
9833 ..Default::default()
9834 },
9835 lsp::CompletionItem {
9836 label: "last".into(),
9837 ..Default::default()
9838 },
9839 ])))
9840 });
9841 cx.set_state("variableˇ");
9842 cx.simulate_keystroke(".");
9843 cx.executor().run_until_parked();
9844
9845 cx.update_editor(|editor, _, _| {
9846 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9847 {
9848 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9849 } else {
9850 panic!("expected completion menu to be open");
9851 }
9852 });
9853
9854 cx.update_editor(|editor, window, cx| {
9855 editor.move_page_down(&MovePageDown::default(), window, cx);
9856 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9857 {
9858 assert!(
9859 menu.selected_item == 1,
9860 "expected PageDown to select the last item from the context menu"
9861 );
9862 } else {
9863 panic!("expected completion menu to stay open after PageDown");
9864 }
9865 });
9866
9867 cx.update_editor(|editor, window, cx| {
9868 editor.move_page_up(&MovePageUp::default(), window, cx);
9869 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9870 {
9871 assert!(
9872 menu.selected_item == 0,
9873 "expected PageUp to select the first item from the context menu"
9874 );
9875 } else {
9876 panic!("expected completion menu to stay open after PageUp");
9877 }
9878 });
9879}
9880
9881#[gpui::test]
9882async fn test_completion_sort(cx: &mut TestAppContext) {
9883 init_test(cx, |_| {});
9884 let mut cx = EditorLspTestContext::new_rust(
9885 lsp::ServerCapabilities {
9886 completion_provider: Some(lsp::CompletionOptions {
9887 trigger_characters: Some(vec![".".to_string()]),
9888 ..Default::default()
9889 }),
9890 ..Default::default()
9891 },
9892 cx,
9893 )
9894 .await;
9895 cx.lsp
9896 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9897 Ok(Some(lsp::CompletionResponse::Array(vec![
9898 lsp::CompletionItem {
9899 label: "Range".into(),
9900 sort_text: Some("a".into()),
9901 ..Default::default()
9902 },
9903 lsp::CompletionItem {
9904 label: "r".into(),
9905 sort_text: Some("b".into()),
9906 ..Default::default()
9907 },
9908 lsp::CompletionItem {
9909 label: "ret".into(),
9910 sort_text: Some("c".into()),
9911 ..Default::default()
9912 },
9913 lsp::CompletionItem {
9914 label: "return".into(),
9915 sort_text: Some("d".into()),
9916 ..Default::default()
9917 },
9918 lsp::CompletionItem {
9919 label: "slice".into(),
9920 sort_text: Some("d".into()),
9921 ..Default::default()
9922 },
9923 ])))
9924 });
9925 cx.set_state("rˇ");
9926 cx.executor().run_until_parked();
9927 cx.update_editor(|editor, window, cx| {
9928 editor.show_completions(
9929 &ShowCompletions {
9930 trigger: Some("r".into()),
9931 },
9932 window,
9933 cx,
9934 );
9935 });
9936 cx.executor().run_until_parked();
9937
9938 cx.update_editor(|editor, _, _| {
9939 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9940 {
9941 assert_eq!(
9942 completion_menu_entries(&menu),
9943 &["r", "ret", "Range", "return"]
9944 );
9945 } else {
9946 panic!("expected completion menu to be open");
9947 }
9948 });
9949}
9950
9951#[gpui::test]
9952async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9953 init_test(cx, |_| {});
9954
9955 let mut cx = EditorLspTestContext::new_rust(
9956 lsp::ServerCapabilities {
9957 completion_provider: Some(lsp::CompletionOptions {
9958 trigger_characters: Some(vec![".".to_string()]),
9959 resolve_provider: Some(true),
9960 ..Default::default()
9961 }),
9962 ..Default::default()
9963 },
9964 cx,
9965 )
9966 .await;
9967
9968 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9969 cx.simulate_keystroke(".");
9970 let completion_item = lsp::CompletionItem {
9971 label: "Some".into(),
9972 kind: Some(lsp::CompletionItemKind::SNIPPET),
9973 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9974 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9975 kind: lsp::MarkupKind::Markdown,
9976 value: "```rust\nSome(2)\n```".to_string(),
9977 })),
9978 deprecated: Some(false),
9979 sort_text: Some("Some".to_string()),
9980 filter_text: Some("Some".to_string()),
9981 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9982 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9983 range: lsp::Range {
9984 start: lsp::Position {
9985 line: 0,
9986 character: 22,
9987 },
9988 end: lsp::Position {
9989 line: 0,
9990 character: 22,
9991 },
9992 },
9993 new_text: "Some(2)".to_string(),
9994 })),
9995 additional_text_edits: Some(vec![lsp::TextEdit {
9996 range: lsp::Range {
9997 start: lsp::Position {
9998 line: 0,
9999 character: 20,
10000 },
10001 end: lsp::Position {
10002 line: 0,
10003 character: 22,
10004 },
10005 },
10006 new_text: "".to_string(),
10007 }]),
10008 ..Default::default()
10009 };
10010
10011 let closure_completion_item = completion_item.clone();
10012 let counter = Arc::new(AtomicUsize::new(0));
10013 let counter_clone = counter.clone();
10014 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10015 let task_completion_item = closure_completion_item.clone();
10016 counter_clone.fetch_add(1, atomic::Ordering::Release);
10017 async move {
10018 Ok(Some(lsp::CompletionResponse::Array(vec![
10019 task_completion_item,
10020 ])))
10021 }
10022 });
10023
10024 cx.condition(|editor, _| editor.context_menu_visible())
10025 .await;
10026 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
10027 assert!(request.next().await.is_some());
10028 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10029
10030 cx.simulate_keystroke("S");
10031 cx.simulate_keystroke("o");
10032 cx.simulate_keystroke("m");
10033 cx.condition(|editor, _| editor.context_menu_visible())
10034 .await;
10035 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
10036 assert!(request.next().await.is_some());
10037 assert!(request.next().await.is_some());
10038 assert!(request.next().await.is_some());
10039 request.close();
10040 assert!(request.next().await.is_none());
10041 assert_eq!(
10042 counter.load(atomic::Ordering::Acquire),
10043 4,
10044 "With the completions menu open, only one LSP request should happen per input"
10045 );
10046}
10047
10048#[gpui::test]
10049async fn test_toggle_comment(cx: &mut TestAppContext) {
10050 init_test(cx, |_| {});
10051 let mut cx = EditorTestContext::new(cx).await;
10052 let language = Arc::new(Language::new(
10053 LanguageConfig {
10054 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10055 ..Default::default()
10056 },
10057 Some(tree_sitter_rust::LANGUAGE.into()),
10058 ));
10059 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10060
10061 // If multiple selections intersect a line, the line is only toggled once.
10062 cx.set_state(indoc! {"
10063 fn a() {
10064 «//b();
10065 ˇ»// «c();
10066 //ˇ» d();
10067 }
10068 "});
10069
10070 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10071
10072 cx.assert_editor_state(indoc! {"
10073 fn a() {
10074 «b();
10075 c();
10076 ˇ» d();
10077 }
10078 "});
10079
10080 // The comment prefix is inserted at the same column for every line in a
10081 // selection.
10082 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10083
10084 cx.assert_editor_state(indoc! {"
10085 fn a() {
10086 // «b();
10087 // c();
10088 ˇ»// d();
10089 }
10090 "});
10091
10092 // If a selection ends at the beginning of a line, that line is not toggled.
10093 cx.set_selections_state(indoc! {"
10094 fn a() {
10095 // b();
10096 «// c();
10097 ˇ» // d();
10098 }
10099 "});
10100
10101 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10102
10103 cx.assert_editor_state(indoc! {"
10104 fn a() {
10105 // b();
10106 «c();
10107 ˇ» // d();
10108 }
10109 "});
10110
10111 // If a selection span a single line and is empty, the line is toggled.
10112 cx.set_state(indoc! {"
10113 fn a() {
10114 a();
10115 b();
10116 ˇ
10117 }
10118 "});
10119
10120 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10121
10122 cx.assert_editor_state(indoc! {"
10123 fn a() {
10124 a();
10125 b();
10126 //•ˇ
10127 }
10128 "});
10129
10130 // If a selection span multiple lines, empty lines are not toggled.
10131 cx.set_state(indoc! {"
10132 fn a() {
10133 «a();
10134
10135 c();ˇ»
10136 }
10137 "});
10138
10139 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10140
10141 cx.assert_editor_state(indoc! {"
10142 fn a() {
10143 // «a();
10144
10145 // c();ˇ»
10146 }
10147 "});
10148
10149 // If a selection includes multiple comment prefixes, all lines are uncommented.
10150 cx.set_state(indoc! {"
10151 fn a() {
10152 «// a();
10153 /// b();
10154 //! c();ˇ»
10155 }
10156 "});
10157
10158 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10159
10160 cx.assert_editor_state(indoc! {"
10161 fn a() {
10162 «a();
10163 b();
10164 c();ˇ»
10165 }
10166 "});
10167}
10168
10169#[gpui::test]
10170async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10171 init_test(cx, |_| {});
10172 let mut cx = EditorTestContext::new(cx).await;
10173 let language = Arc::new(Language::new(
10174 LanguageConfig {
10175 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10176 ..Default::default()
10177 },
10178 Some(tree_sitter_rust::LANGUAGE.into()),
10179 ));
10180 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10181
10182 let toggle_comments = &ToggleComments {
10183 advance_downwards: false,
10184 ignore_indent: true,
10185 };
10186
10187 // If multiple selections intersect a line, the line is only toggled once.
10188 cx.set_state(indoc! {"
10189 fn a() {
10190 // «b();
10191 // c();
10192 // ˇ» d();
10193 }
10194 "});
10195
10196 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10197
10198 cx.assert_editor_state(indoc! {"
10199 fn a() {
10200 «b();
10201 c();
10202 ˇ» d();
10203 }
10204 "});
10205
10206 // The comment prefix is inserted at the beginning of each line
10207 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10208
10209 cx.assert_editor_state(indoc! {"
10210 fn a() {
10211 // «b();
10212 // c();
10213 // ˇ» d();
10214 }
10215 "});
10216
10217 // If a selection ends at the beginning of a line, that line is not toggled.
10218 cx.set_selections_state(indoc! {"
10219 fn a() {
10220 // b();
10221 // «c();
10222 ˇ»// d();
10223 }
10224 "});
10225
10226 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10227
10228 cx.assert_editor_state(indoc! {"
10229 fn a() {
10230 // b();
10231 «c();
10232 ˇ»// d();
10233 }
10234 "});
10235
10236 // If a selection span a single line and is empty, the line is toggled.
10237 cx.set_state(indoc! {"
10238 fn a() {
10239 a();
10240 b();
10241 ˇ
10242 }
10243 "});
10244
10245 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10246
10247 cx.assert_editor_state(indoc! {"
10248 fn a() {
10249 a();
10250 b();
10251 //ˇ
10252 }
10253 "});
10254
10255 // If a selection span multiple lines, empty lines are not toggled.
10256 cx.set_state(indoc! {"
10257 fn a() {
10258 «a();
10259
10260 c();ˇ»
10261 }
10262 "});
10263
10264 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10265
10266 cx.assert_editor_state(indoc! {"
10267 fn a() {
10268 // «a();
10269
10270 // c();ˇ»
10271 }
10272 "});
10273
10274 // If a selection includes multiple comment prefixes, all lines are uncommented.
10275 cx.set_state(indoc! {"
10276 fn a() {
10277 // «a();
10278 /// b();
10279 //! c();ˇ»
10280 }
10281 "});
10282
10283 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10284
10285 cx.assert_editor_state(indoc! {"
10286 fn a() {
10287 «a();
10288 b();
10289 c();ˇ»
10290 }
10291 "});
10292}
10293
10294#[gpui::test]
10295async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10296 init_test(cx, |_| {});
10297
10298 let language = Arc::new(Language::new(
10299 LanguageConfig {
10300 line_comments: vec!["// ".into()],
10301 ..Default::default()
10302 },
10303 Some(tree_sitter_rust::LANGUAGE.into()),
10304 ));
10305
10306 let mut cx = EditorTestContext::new(cx).await;
10307
10308 cx.language_registry().add(language.clone());
10309 cx.update_buffer(|buffer, cx| {
10310 buffer.set_language(Some(language), cx);
10311 });
10312
10313 let toggle_comments = &ToggleComments {
10314 advance_downwards: true,
10315 ignore_indent: false,
10316 };
10317
10318 // Single cursor on one line -> advance
10319 // Cursor moves horizontally 3 characters as well on non-blank line
10320 cx.set_state(indoc!(
10321 "fn a() {
10322 ˇdog();
10323 cat();
10324 }"
10325 ));
10326 cx.update_editor(|editor, window, cx| {
10327 editor.toggle_comments(toggle_comments, window, cx);
10328 });
10329 cx.assert_editor_state(indoc!(
10330 "fn a() {
10331 // dog();
10332 catˇ();
10333 }"
10334 ));
10335
10336 // Single selection on one line -> don't advance
10337 cx.set_state(indoc!(
10338 "fn a() {
10339 «dog()ˇ»;
10340 cat();
10341 }"
10342 ));
10343 cx.update_editor(|editor, window, cx| {
10344 editor.toggle_comments(toggle_comments, window, cx);
10345 });
10346 cx.assert_editor_state(indoc!(
10347 "fn a() {
10348 // «dog()ˇ»;
10349 cat();
10350 }"
10351 ));
10352
10353 // Multiple cursors on one line -> advance
10354 cx.set_state(indoc!(
10355 "fn a() {
10356 ˇdˇog();
10357 cat();
10358 }"
10359 ));
10360 cx.update_editor(|editor, window, cx| {
10361 editor.toggle_comments(toggle_comments, window, cx);
10362 });
10363 cx.assert_editor_state(indoc!(
10364 "fn a() {
10365 // dog();
10366 catˇ(ˇ);
10367 }"
10368 ));
10369
10370 // Multiple cursors on one line, with selection -> don't advance
10371 cx.set_state(indoc!(
10372 "fn a() {
10373 ˇdˇog«()ˇ»;
10374 cat();
10375 }"
10376 ));
10377 cx.update_editor(|editor, window, cx| {
10378 editor.toggle_comments(toggle_comments, window, cx);
10379 });
10380 cx.assert_editor_state(indoc!(
10381 "fn a() {
10382 // ˇdˇog«()ˇ»;
10383 cat();
10384 }"
10385 ));
10386
10387 // Single cursor on one line -> advance
10388 // Cursor moves to column 0 on blank line
10389 cx.set_state(indoc!(
10390 "fn a() {
10391 ˇdog();
10392
10393 cat();
10394 }"
10395 ));
10396 cx.update_editor(|editor, window, cx| {
10397 editor.toggle_comments(toggle_comments, window, cx);
10398 });
10399 cx.assert_editor_state(indoc!(
10400 "fn a() {
10401 // dog();
10402 ˇ
10403 cat();
10404 }"
10405 ));
10406
10407 // Single cursor on one line -> advance
10408 // Cursor starts and ends at column 0
10409 cx.set_state(indoc!(
10410 "fn a() {
10411 ˇ dog();
10412 cat();
10413 }"
10414 ));
10415 cx.update_editor(|editor, window, cx| {
10416 editor.toggle_comments(toggle_comments, window, cx);
10417 });
10418 cx.assert_editor_state(indoc!(
10419 "fn a() {
10420 // dog();
10421 ˇ cat();
10422 }"
10423 ));
10424}
10425
10426#[gpui::test]
10427async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10428 init_test(cx, |_| {});
10429
10430 let mut cx = EditorTestContext::new(cx).await;
10431
10432 let html_language = Arc::new(
10433 Language::new(
10434 LanguageConfig {
10435 name: "HTML".into(),
10436 block_comment: Some(("<!-- ".into(), " -->".into())),
10437 ..Default::default()
10438 },
10439 Some(tree_sitter_html::LANGUAGE.into()),
10440 )
10441 .with_injection_query(
10442 r#"
10443 (script_element
10444 (raw_text) @injection.content
10445 (#set! injection.language "javascript"))
10446 "#,
10447 )
10448 .unwrap(),
10449 );
10450
10451 let javascript_language = Arc::new(Language::new(
10452 LanguageConfig {
10453 name: "JavaScript".into(),
10454 line_comments: vec!["// ".into()],
10455 ..Default::default()
10456 },
10457 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10458 ));
10459
10460 cx.language_registry().add(html_language.clone());
10461 cx.language_registry().add(javascript_language.clone());
10462 cx.update_buffer(|buffer, cx| {
10463 buffer.set_language(Some(html_language), cx);
10464 });
10465
10466 // Toggle comments for empty selections
10467 cx.set_state(
10468 &r#"
10469 <p>A</p>ˇ
10470 <p>B</p>ˇ
10471 <p>C</p>ˇ
10472 "#
10473 .unindent(),
10474 );
10475 cx.update_editor(|editor, window, cx| {
10476 editor.toggle_comments(&ToggleComments::default(), window, cx)
10477 });
10478 cx.assert_editor_state(
10479 &r#"
10480 <!-- <p>A</p>ˇ -->
10481 <!-- <p>B</p>ˇ -->
10482 <!-- <p>C</p>ˇ -->
10483 "#
10484 .unindent(),
10485 );
10486 cx.update_editor(|editor, window, cx| {
10487 editor.toggle_comments(&ToggleComments::default(), window, cx)
10488 });
10489 cx.assert_editor_state(
10490 &r#"
10491 <p>A</p>ˇ
10492 <p>B</p>ˇ
10493 <p>C</p>ˇ
10494 "#
10495 .unindent(),
10496 );
10497
10498 // Toggle comments for mixture of empty and non-empty selections, where
10499 // multiple selections occupy a given line.
10500 cx.set_state(
10501 &r#"
10502 <p>A«</p>
10503 <p>ˇ»B</p>ˇ
10504 <p>C«</p>
10505 <p>ˇ»D</p>ˇ
10506 "#
10507 .unindent(),
10508 );
10509
10510 cx.update_editor(|editor, window, cx| {
10511 editor.toggle_comments(&ToggleComments::default(), window, cx)
10512 });
10513 cx.assert_editor_state(
10514 &r#"
10515 <!-- <p>A«</p>
10516 <p>ˇ»B</p>ˇ -->
10517 <!-- <p>C«</p>
10518 <p>ˇ»D</p>ˇ -->
10519 "#
10520 .unindent(),
10521 );
10522 cx.update_editor(|editor, window, cx| {
10523 editor.toggle_comments(&ToggleComments::default(), window, cx)
10524 });
10525 cx.assert_editor_state(
10526 &r#"
10527 <p>A«</p>
10528 <p>ˇ»B</p>ˇ
10529 <p>C«</p>
10530 <p>ˇ»D</p>ˇ
10531 "#
10532 .unindent(),
10533 );
10534
10535 // Toggle comments when different languages are active for different
10536 // selections.
10537 cx.set_state(
10538 &r#"
10539 ˇ<script>
10540 ˇvar x = new Y();
10541 ˇ</script>
10542 "#
10543 .unindent(),
10544 );
10545 cx.executor().run_until_parked();
10546 cx.update_editor(|editor, window, cx| {
10547 editor.toggle_comments(&ToggleComments::default(), window, cx)
10548 });
10549 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10550 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10551 cx.assert_editor_state(
10552 &r#"
10553 <!-- ˇ<script> -->
10554 // ˇvar x = new Y();
10555 <!-- ˇ</script> -->
10556 "#
10557 .unindent(),
10558 );
10559}
10560
10561#[gpui::test]
10562fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10563 init_test(cx, |_| {});
10564
10565 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10566 let multibuffer = cx.new(|cx| {
10567 let mut multibuffer = MultiBuffer::new(ReadWrite);
10568 multibuffer.push_excerpts(
10569 buffer.clone(),
10570 [
10571 ExcerptRange {
10572 context: Point::new(0, 0)..Point::new(0, 4),
10573 primary: None,
10574 },
10575 ExcerptRange {
10576 context: Point::new(1, 0)..Point::new(1, 4),
10577 primary: None,
10578 },
10579 ],
10580 cx,
10581 );
10582 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10583 multibuffer
10584 });
10585
10586 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10587 editor.update_in(cx, |editor, window, cx| {
10588 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10589 editor.change_selections(None, window, cx, |s| {
10590 s.select_ranges([
10591 Point::new(0, 0)..Point::new(0, 0),
10592 Point::new(1, 0)..Point::new(1, 0),
10593 ])
10594 });
10595
10596 editor.handle_input("X", window, cx);
10597 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10598 assert_eq!(
10599 editor.selections.ranges(cx),
10600 [
10601 Point::new(0, 1)..Point::new(0, 1),
10602 Point::new(1, 1)..Point::new(1, 1),
10603 ]
10604 );
10605
10606 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10607 editor.change_selections(None, window, cx, |s| {
10608 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10609 });
10610 editor.backspace(&Default::default(), window, cx);
10611 assert_eq!(editor.text(cx), "Xa\nbbb");
10612 assert_eq!(
10613 editor.selections.ranges(cx),
10614 [Point::new(1, 0)..Point::new(1, 0)]
10615 );
10616
10617 editor.change_selections(None, window, cx, |s| {
10618 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10619 });
10620 editor.backspace(&Default::default(), window, cx);
10621 assert_eq!(editor.text(cx), "X\nbb");
10622 assert_eq!(
10623 editor.selections.ranges(cx),
10624 [Point::new(0, 1)..Point::new(0, 1)]
10625 );
10626 });
10627}
10628
10629#[gpui::test]
10630fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10631 init_test(cx, |_| {});
10632
10633 let markers = vec![('[', ']').into(), ('(', ')').into()];
10634 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10635 indoc! {"
10636 [aaaa
10637 (bbbb]
10638 cccc)",
10639 },
10640 markers.clone(),
10641 );
10642 let excerpt_ranges = markers.into_iter().map(|marker| {
10643 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10644 ExcerptRange {
10645 context,
10646 primary: None,
10647 }
10648 });
10649 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10650 let multibuffer = cx.new(|cx| {
10651 let mut multibuffer = MultiBuffer::new(ReadWrite);
10652 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10653 multibuffer
10654 });
10655
10656 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10657 editor.update_in(cx, |editor, window, cx| {
10658 let (expected_text, selection_ranges) = marked_text_ranges(
10659 indoc! {"
10660 aaaa
10661 bˇbbb
10662 bˇbbˇb
10663 cccc"
10664 },
10665 true,
10666 );
10667 assert_eq!(editor.text(cx), expected_text);
10668 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10669
10670 editor.handle_input("X", window, cx);
10671
10672 let (expected_text, expected_selections) = marked_text_ranges(
10673 indoc! {"
10674 aaaa
10675 bXˇbbXb
10676 bXˇbbXˇb
10677 cccc"
10678 },
10679 false,
10680 );
10681 assert_eq!(editor.text(cx), expected_text);
10682 assert_eq!(editor.selections.ranges(cx), expected_selections);
10683
10684 editor.newline(&Newline, window, cx);
10685 let (expected_text, expected_selections) = marked_text_ranges(
10686 indoc! {"
10687 aaaa
10688 bX
10689 ˇbbX
10690 b
10691 bX
10692 ˇbbX
10693 ˇb
10694 cccc"
10695 },
10696 false,
10697 );
10698 assert_eq!(editor.text(cx), expected_text);
10699 assert_eq!(editor.selections.ranges(cx), expected_selections);
10700 });
10701}
10702
10703#[gpui::test]
10704fn test_refresh_selections(cx: &mut TestAppContext) {
10705 init_test(cx, |_| {});
10706
10707 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10708 let mut excerpt1_id = None;
10709 let multibuffer = cx.new(|cx| {
10710 let mut multibuffer = MultiBuffer::new(ReadWrite);
10711 excerpt1_id = multibuffer
10712 .push_excerpts(
10713 buffer.clone(),
10714 [
10715 ExcerptRange {
10716 context: Point::new(0, 0)..Point::new(1, 4),
10717 primary: None,
10718 },
10719 ExcerptRange {
10720 context: Point::new(1, 0)..Point::new(2, 4),
10721 primary: None,
10722 },
10723 ],
10724 cx,
10725 )
10726 .into_iter()
10727 .next();
10728 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10729 multibuffer
10730 });
10731
10732 let editor = cx.add_window(|window, cx| {
10733 let mut editor = build_editor(multibuffer.clone(), window, cx);
10734 let snapshot = editor.snapshot(window, cx);
10735 editor.change_selections(None, window, cx, |s| {
10736 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10737 });
10738 editor.begin_selection(
10739 Point::new(2, 1).to_display_point(&snapshot),
10740 true,
10741 1,
10742 window,
10743 cx,
10744 );
10745 assert_eq!(
10746 editor.selections.ranges(cx),
10747 [
10748 Point::new(1, 3)..Point::new(1, 3),
10749 Point::new(2, 1)..Point::new(2, 1),
10750 ]
10751 );
10752 editor
10753 });
10754
10755 // Refreshing selections is a no-op when excerpts haven't changed.
10756 _ = editor.update(cx, |editor, window, cx| {
10757 editor.change_selections(None, window, cx, |s| s.refresh());
10758 assert_eq!(
10759 editor.selections.ranges(cx),
10760 [
10761 Point::new(1, 3)..Point::new(1, 3),
10762 Point::new(2, 1)..Point::new(2, 1),
10763 ]
10764 );
10765 });
10766
10767 multibuffer.update(cx, |multibuffer, cx| {
10768 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10769 });
10770 _ = editor.update(cx, |editor, window, cx| {
10771 // Removing an excerpt causes the first selection to become degenerate.
10772 assert_eq!(
10773 editor.selections.ranges(cx),
10774 [
10775 Point::new(0, 0)..Point::new(0, 0),
10776 Point::new(0, 1)..Point::new(0, 1)
10777 ]
10778 );
10779
10780 // Refreshing selections will relocate the first selection to the original buffer
10781 // location.
10782 editor.change_selections(None, window, cx, |s| s.refresh());
10783 assert_eq!(
10784 editor.selections.ranges(cx),
10785 [
10786 Point::new(0, 1)..Point::new(0, 1),
10787 Point::new(0, 3)..Point::new(0, 3)
10788 ]
10789 );
10790 assert!(editor.selections.pending_anchor().is_some());
10791 });
10792}
10793
10794#[gpui::test]
10795fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10796 init_test(cx, |_| {});
10797
10798 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10799 let mut excerpt1_id = None;
10800 let multibuffer = cx.new(|cx| {
10801 let mut multibuffer = MultiBuffer::new(ReadWrite);
10802 excerpt1_id = multibuffer
10803 .push_excerpts(
10804 buffer.clone(),
10805 [
10806 ExcerptRange {
10807 context: Point::new(0, 0)..Point::new(1, 4),
10808 primary: None,
10809 },
10810 ExcerptRange {
10811 context: Point::new(1, 0)..Point::new(2, 4),
10812 primary: None,
10813 },
10814 ],
10815 cx,
10816 )
10817 .into_iter()
10818 .next();
10819 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10820 multibuffer
10821 });
10822
10823 let editor = cx.add_window(|window, cx| {
10824 let mut editor = build_editor(multibuffer.clone(), window, cx);
10825 let snapshot = editor.snapshot(window, cx);
10826 editor.begin_selection(
10827 Point::new(1, 3).to_display_point(&snapshot),
10828 false,
10829 1,
10830 window,
10831 cx,
10832 );
10833 assert_eq!(
10834 editor.selections.ranges(cx),
10835 [Point::new(1, 3)..Point::new(1, 3)]
10836 );
10837 editor
10838 });
10839
10840 multibuffer.update(cx, |multibuffer, cx| {
10841 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10842 });
10843 _ = editor.update(cx, |editor, window, cx| {
10844 assert_eq!(
10845 editor.selections.ranges(cx),
10846 [Point::new(0, 0)..Point::new(0, 0)]
10847 );
10848
10849 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10850 editor.change_selections(None, window, cx, |s| s.refresh());
10851 assert_eq!(
10852 editor.selections.ranges(cx),
10853 [Point::new(0, 3)..Point::new(0, 3)]
10854 );
10855 assert!(editor.selections.pending_anchor().is_some());
10856 });
10857}
10858
10859#[gpui::test]
10860async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10861 init_test(cx, |_| {});
10862
10863 let language = Arc::new(
10864 Language::new(
10865 LanguageConfig {
10866 brackets: BracketPairConfig {
10867 pairs: vec![
10868 BracketPair {
10869 start: "{".to_string(),
10870 end: "}".to_string(),
10871 close: true,
10872 surround: true,
10873 newline: true,
10874 },
10875 BracketPair {
10876 start: "/* ".to_string(),
10877 end: " */".to_string(),
10878 close: true,
10879 surround: true,
10880 newline: true,
10881 },
10882 ],
10883 ..Default::default()
10884 },
10885 ..Default::default()
10886 },
10887 Some(tree_sitter_rust::LANGUAGE.into()),
10888 )
10889 .with_indents_query("")
10890 .unwrap(),
10891 );
10892
10893 let text = concat!(
10894 "{ }\n", //
10895 " x\n", //
10896 " /* */\n", //
10897 "x\n", //
10898 "{{} }\n", //
10899 );
10900
10901 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10902 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10903 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10904 editor
10905 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10906 .await;
10907
10908 editor.update_in(cx, |editor, window, cx| {
10909 editor.change_selections(None, window, cx, |s| {
10910 s.select_display_ranges([
10911 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10912 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10913 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10914 ])
10915 });
10916 editor.newline(&Newline, window, cx);
10917
10918 assert_eq!(
10919 editor.buffer().read(cx).read(cx).text(),
10920 concat!(
10921 "{ \n", // Suppress rustfmt
10922 "\n", //
10923 "}\n", //
10924 " x\n", //
10925 " /* \n", //
10926 " \n", //
10927 " */\n", //
10928 "x\n", //
10929 "{{} \n", //
10930 "}\n", //
10931 )
10932 );
10933 });
10934}
10935
10936#[gpui::test]
10937fn test_highlighted_ranges(cx: &mut TestAppContext) {
10938 init_test(cx, |_| {});
10939
10940 let editor = cx.add_window(|window, cx| {
10941 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10942 build_editor(buffer.clone(), window, cx)
10943 });
10944
10945 _ = editor.update(cx, |editor, window, cx| {
10946 struct Type1;
10947 struct Type2;
10948
10949 let buffer = editor.buffer.read(cx).snapshot(cx);
10950
10951 let anchor_range =
10952 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10953
10954 editor.highlight_background::<Type1>(
10955 &[
10956 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10957 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10958 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10959 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10960 ],
10961 |_| Hsla::red(),
10962 cx,
10963 );
10964 editor.highlight_background::<Type2>(
10965 &[
10966 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10967 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10968 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10969 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10970 ],
10971 |_| Hsla::green(),
10972 cx,
10973 );
10974
10975 let snapshot = editor.snapshot(window, cx);
10976 let mut highlighted_ranges = editor.background_highlights_in_range(
10977 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10978 &snapshot,
10979 cx.theme().colors(),
10980 );
10981 // Enforce a consistent ordering based on color without relying on the ordering of the
10982 // highlight's `TypeId` which is non-executor.
10983 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10984 assert_eq!(
10985 highlighted_ranges,
10986 &[
10987 (
10988 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10989 Hsla::red(),
10990 ),
10991 (
10992 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10993 Hsla::red(),
10994 ),
10995 (
10996 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10997 Hsla::green(),
10998 ),
10999 (
11000 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
11001 Hsla::green(),
11002 ),
11003 ]
11004 );
11005 assert_eq!(
11006 editor.background_highlights_in_range(
11007 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
11008 &snapshot,
11009 cx.theme().colors(),
11010 ),
11011 &[(
11012 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11013 Hsla::red(),
11014 )]
11015 );
11016 });
11017}
11018
11019#[gpui::test]
11020async fn test_following(cx: &mut TestAppContext) {
11021 init_test(cx, |_| {});
11022
11023 let fs = FakeFs::new(cx.executor());
11024 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11025
11026 let buffer = project.update(cx, |project, cx| {
11027 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
11028 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
11029 });
11030 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
11031 let follower = cx.update(|cx| {
11032 cx.open_window(
11033 WindowOptions {
11034 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
11035 gpui::Point::new(px(0.), px(0.)),
11036 gpui::Point::new(px(10.), px(80.)),
11037 ))),
11038 ..Default::default()
11039 },
11040 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
11041 )
11042 .unwrap()
11043 });
11044
11045 let is_still_following = Rc::new(RefCell::new(true));
11046 let follower_edit_event_count = Rc::new(RefCell::new(0));
11047 let pending_update = Rc::new(RefCell::new(None));
11048 let leader_entity = leader.root(cx).unwrap();
11049 let follower_entity = follower.root(cx).unwrap();
11050 _ = follower.update(cx, {
11051 let update = pending_update.clone();
11052 let is_still_following = is_still_following.clone();
11053 let follower_edit_event_count = follower_edit_event_count.clone();
11054 |_, window, cx| {
11055 cx.subscribe_in(
11056 &leader_entity,
11057 window,
11058 move |_, leader, event, window, cx| {
11059 leader.read(cx).add_event_to_update_proto(
11060 event,
11061 &mut update.borrow_mut(),
11062 window,
11063 cx,
11064 );
11065 },
11066 )
11067 .detach();
11068
11069 cx.subscribe_in(
11070 &follower_entity,
11071 window,
11072 move |_, _, event: &EditorEvent, _window, _cx| {
11073 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11074 *is_still_following.borrow_mut() = false;
11075 }
11076
11077 if let EditorEvent::BufferEdited = event {
11078 *follower_edit_event_count.borrow_mut() += 1;
11079 }
11080 },
11081 )
11082 .detach();
11083 }
11084 });
11085
11086 // Update the selections only
11087 _ = leader.update(cx, |leader, window, cx| {
11088 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11089 });
11090 follower
11091 .update(cx, |follower, window, cx| {
11092 follower.apply_update_proto(
11093 &project,
11094 pending_update.borrow_mut().take().unwrap(),
11095 window,
11096 cx,
11097 )
11098 })
11099 .unwrap()
11100 .await
11101 .unwrap();
11102 _ = follower.update(cx, |follower, _, cx| {
11103 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11104 });
11105 assert!(*is_still_following.borrow());
11106 assert_eq!(*follower_edit_event_count.borrow(), 0);
11107
11108 // Update the scroll position only
11109 _ = leader.update(cx, |leader, window, cx| {
11110 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11111 });
11112 follower
11113 .update(cx, |follower, window, cx| {
11114 follower.apply_update_proto(
11115 &project,
11116 pending_update.borrow_mut().take().unwrap(),
11117 window,
11118 cx,
11119 )
11120 })
11121 .unwrap()
11122 .await
11123 .unwrap();
11124 assert_eq!(
11125 follower
11126 .update(cx, |follower, _, cx| follower.scroll_position(cx))
11127 .unwrap(),
11128 gpui::Point::new(1.5, 3.5)
11129 );
11130 assert!(*is_still_following.borrow());
11131 assert_eq!(*follower_edit_event_count.borrow(), 0);
11132
11133 // Update the selections and scroll position. The follower's scroll position is updated
11134 // via autoscroll, not via the leader's exact scroll position.
11135 _ = leader.update(cx, |leader, window, cx| {
11136 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11137 leader.request_autoscroll(Autoscroll::newest(), cx);
11138 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11139 });
11140 follower
11141 .update(cx, |follower, window, cx| {
11142 follower.apply_update_proto(
11143 &project,
11144 pending_update.borrow_mut().take().unwrap(),
11145 window,
11146 cx,
11147 )
11148 })
11149 .unwrap()
11150 .await
11151 .unwrap();
11152 _ = follower.update(cx, |follower, _, cx| {
11153 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11154 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11155 });
11156 assert!(*is_still_following.borrow());
11157
11158 // Creating a pending selection that precedes another selection
11159 _ = leader.update(cx, |leader, window, cx| {
11160 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11161 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11162 });
11163 follower
11164 .update(cx, |follower, window, cx| {
11165 follower.apply_update_proto(
11166 &project,
11167 pending_update.borrow_mut().take().unwrap(),
11168 window,
11169 cx,
11170 )
11171 })
11172 .unwrap()
11173 .await
11174 .unwrap();
11175 _ = follower.update(cx, |follower, _, cx| {
11176 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11177 });
11178 assert!(*is_still_following.borrow());
11179
11180 // Extend the pending selection so that it surrounds another selection
11181 _ = leader.update(cx, |leader, window, cx| {
11182 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11183 });
11184 follower
11185 .update(cx, |follower, window, cx| {
11186 follower.apply_update_proto(
11187 &project,
11188 pending_update.borrow_mut().take().unwrap(),
11189 window,
11190 cx,
11191 )
11192 })
11193 .unwrap()
11194 .await
11195 .unwrap();
11196 _ = follower.update(cx, |follower, _, cx| {
11197 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11198 });
11199
11200 // Scrolling locally breaks the follow
11201 _ = follower.update(cx, |follower, window, cx| {
11202 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11203 follower.set_scroll_anchor(
11204 ScrollAnchor {
11205 anchor: top_anchor,
11206 offset: gpui::Point::new(0.0, 0.5),
11207 },
11208 window,
11209 cx,
11210 );
11211 });
11212 assert!(!(*is_still_following.borrow()));
11213}
11214
11215#[gpui::test]
11216async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11217 init_test(cx, |_| {});
11218
11219 let fs = FakeFs::new(cx.executor());
11220 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11221 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11222 let pane = workspace
11223 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11224 .unwrap();
11225
11226 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11227
11228 let leader = pane.update_in(cx, |_, window, cx| {
11229 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11230 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11231 });
11232
11233 // Start following the editor when it has no excerpts.
11234 let mut state_message =
11235 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11236 let workspace_entity = workspace.root(cx).unwrap();
11237 let follower_1 = cx
11238 .update_window(*workspace.deref(), |_, window, cx| {
11239 Editor::from_state_proto(
11240 workspace_entity,
11241 ViewId {
11242 creator: Default::default(),
11243 id: 0,
11244 },
11245 &mut state_message,
11246 window,
11247 cx,
11248 )
11249 })
11250 .unwrap()
11251 .unwrap()
11252 .await
11253 .unwrap();
11254
11255 let update_message = Rc::new(RefCell::new(None));
11256 follower_1.update_in(cx, {
11257 let update = update_message.clone();
11258 |_, window, cx| {
11259 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11260 leader.read(cx).add_event_to_update_proto(
11261 event,
11262 &mut update.borrow_mut(),
11263 window,
11264 cx,
11265 );
11266 })
11267 .detach();
11268 }
11269 });
11270
11271 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11272 (
11273 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11274 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11275 )
11276 });
11277
11278 // Insert some excerpts.
11279 leader.update(cx, |leader, cx| {
11280 leader.buffer.update(cx, |multibuffer, cx| {
11281 let excerpt_ids = multibuffer.push_excerpts(
11282 buffer_1.clone(),
11283 [
11284 ExcerptRange {
11285 context: 1..6,
11286 primary: None,
11287 },
11288 ExcerptRange {
11289 context: 12..15,
11290 primary: None,
11291 },
11292 ExcerptRange {
11293 context: 0..3,
11294 primary: None,
11295 },
11296 ],
11297 cx,
11298 );
11299 multibuffer.insert_excerpts_after(
11300 excerpt_ids[0],
11301 buffer_2.clone(),
11302 [
11303 ExcerptRange {
11304 context: 8..12,
11305 primary: None,
11306 },
11307 ExcerptRange {
11308 context: 0..6,
11309 primary: None,
11310 },
11311 ],
11312 cx,
11313 );
11314 });
11315 });
11316
11317 // Apply the update of adding the excerpts.
11318 follower_1
11319 .update_in(cx, |follower, window, cx| {
11320 follower.apply_update_proto(
11321 &project,
11322 update_message.borrow().clone().unwrap(),
11323 window,
11324 cx,
11325 )
11326 })
11327 .await
11328 .unwrap();
11329 assert_eq!(
11330 follower_1.update(cx, |editor, cx| editor.text(cx)),
11331 leader.update(cx, |editor, cx| editor.text(cx))
11332 );
11333 update_message.borrow_mut().take();
11334
11335 // Start following separately after it already has excerpts.
11336 let mut state_message =
11337 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11338 let workspace_entity = workspace.root(cx).unwrap();
11339 let follower_2 = cx
11340 .update_window(*workspace.deref(), |_, window, cx| {
11341 Editor::from_state_proto(
11342 workspace_entity,
11343 ViewId {
11344 creator: Default::default(),
11345 id: 0,
11346 },
11347 &mut state_message,
11348 window,
11349 cx,
11350 )
11351 })
11352 .unwrap()
11353 .unwrap()
11354 .await
11355 .unwrap();
11356 assert_eq!(
11357 follower_2.update(cx, |editor, cx| editor.text(cx)),
11358 leader.update(cx, |editor, cx| editor.text(cx))
11359 );
11360
11361 // Remove some excerpts.
11362 leader.update(cx, |leader, cx| {
11363 leader.buffer.update(cx, |multibuffer, cx| {
11364 let excerpt_ids = multibuffer.excerpt_ids();
11365 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11366 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11367 });
11368 });
11369
11370 // Apply the update of removing the excerpts.
11371 follower_1
11372 .update_in(cx, |follower, window, cx| {
11373 follower.apply_update_proto(
11374 &project,
11375 update_message.borrow().clone().unwrap(),
11376 window,
11377 cx,
11378 )
11379 })
11380 .await
11381 .unwrap();
11382 follower_2
11383 .update_in(cx, |follower, window, cx| {
11384 follower.apply_update_proto(
11385 &project,
11386 update_message.borrow().clone().unwrap(),
11387 window,
11388 cx,
11389 )
11390 })
11391 .await
11392 .unwrap();
11393 update_message.borrow_mut().take();
11394 assert_eq!(
11395 follower_1.update(cx, |editor, cx| editor.text(cx)),
11396 leader.update(cx, |editor, cx| editor.text(cx))
11397 );
11398}
11399
11400#[gpui::test]
11401async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11402 init_test(cx, |_| {});
11403
11404 let mut cx = EditorTestContext::new(cx).await;
11405 let lsp_store =
11406 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11407
11408 cx.set_state(indoc! {"
11409 ˇfn func(abc def: i32) -> u32 {
11410 }
11411 "});
11412
11413 cx.update(|_, cx| {
11414 lsp_store.update(cx, |lsp_store, cx| {
11415 lsp_store
11416 .update_diagnostics(
11417 LanguageServerId(0),
11418 lsp::PublishDiagnosticsParams {
11419 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11420 version: None,
11421 diagnostics: vec![
11422 lsp::Diagnostic {
11423 range: lsp::Range::new(
11424 lsp::Position::new(0, 11),
11425 lsp::Position::new(0, 12),
11426 ),
11427 severity: Some(lsp::DiagnosticSeverity::ERROR),
11428 ..Default::default()
11429 },
11430 lsp::Diagnostic {
11431 range: lsp::Range::new(
11432 lsp::Position::new(0, 12),
11433 lsp::Position::new(0, 15),
11434 ),
11435 severity: Some(lsp::DiagnosticSeverity::ERROR),
11436 ..Default::default()
11437 },
11438 lsp::Diagnostic {
11439 range: lsp::Range::new(
11440 lsp::Position::new(0, 25),
11441 lsp::Position::new(0, 28),
11442 ),
11443 severity: Some(lsp::DiagnosticSeverity::ERROR),
11444 ..Default::default()
11445 },
11446 ],
11447 },
11448 &[],
11449 cx,
11450 )
11451 .unwrap()
11452 });
11453 });
11454
11455 executor.run_until_parked();
11456
11457 cx.update_editor(|editor, window, cx| {
11458 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11459 });
11460
11461 cx.assert_editor_state(indoc! {"
11462 fn func(abc def: i32) -> ˇu32 {
11463 }
11464 "});
11465
11466 cx.update_editor(|editor, window, cx| {
11467 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11468 });
11469
11470 cx.assert_editor_state(indoc! {"
11471 fn func(abc ˇdef: i32) -> u32 {
11472 }
11473 "});
11474
11475 cx.update_editor(|editor, window, cx| {
11476 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11477 });
11478
11479 cx.assert_editor_state(indoc! {"
11480 fn func(abcˇ def: i32) -> u32 {
11481 }
11482 "});
11483
11484 cx.update_editor(|editor, window, cx| {
11485 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11486 });
11487
11488 cx.assert_editor_state(indoc! {"
11489 fn func(abc def: i32) -> ˇu32 {
11490 }
11491 "});
11492}
11493
11494#[gpui::test]
11495async fn cycle_through_same_place_diagnostics(
11496 executor: BackgroundExecutor,
11497 cx: &mut TestAppContext,
11498) {
11499 init_test(cx, |_| {});
11500
11501 let mut cx = EditorTestContext::new(cx).await;
11502 let lsp_store =
11503 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11504
11505 cx.set_state(indoc! {"
11506 ˇfn func(abc def: i32) -> u32 {
11507 }
11508 "});
11509
11510 cx.update(|_, cx| {
11511 lsp_store.update(cx, |lsp_store, cx| {
11512 lsp_store
11513 .update_diagnostics(
11514 LanguageServerId(0),
11515 lsp::PublishDiagnosticsParams {
11516 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11517 version: None,
11518 diagnostics: vec![
11519 lsp::Diagnostic {
11520 range: lsp::Range::new(
11521 lsp::Position::new(0, 11),
11522 lsp::Position::new(0, 12),
11523 ),
11524 severity: Some(lsp::DiagnosticSeverity::ERROR),
11525 ..Default::default()
11526 },
11527 lsp::Diagnostic {
11528 range: lsp::Range::new(
11529 lsp::Position::new(0, 12),
11530 lsp::Position::new(0, 15),
11531 ),
11532 severity: Some(lsp::DiagnosticSeverity::ERROR),
11533 ..Default::default()
11534 },
11535 lsp::Diagnostic {
11536 range: lsp::Range::new(
11537 lsp::Position::new(0, 12),
11538 lsp::Position::new(0, 15),
11539 ),
11540 severity: Some(lsp::DiagnosticSeverity::ERROR),
11541 ..Default::default()
11542 },
11543 lsp::Diagnostic {
11544 range: lsp::Range::new(
11545 lsp::Position::new(0, 25),
11546 lsp::Position::new(0, 28),
11547 ),
11548 severity: Some(lsp::DiagnosticSeverity::ERROR),
11549 ..Default::default()
11550 },
11551 ],
11552 },
11553 &[],
11554 cx,
11555 )
11556 .unwrap()
11557 });
11558 });
11559 executor.run_until_parked();
11560
11561 //// Backward
11562
11563 // Fourth diagnostic
11564 cx.update_editor(|editor, window, cx| {
11565 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11566 });
11567 cx.assert_editor_state(indoc! {"
11568 fn func(abc def: i32) -> ˇu32 {
11569 }
11570 "});
11571
11572 // Third diagnostic
11573 cx.update_editor(|editor, window, cx| {
11574 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11575 });
11576 cx.assert_editor_state(indoc! {"
11577 fn func(abc ˇdef: i32) -> u32 {
11578 }
11579 "});
11580
11581 // Second diagnostic, same place
11582 cx.update_editor(|editor, window, cx| {
11583 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11584 });
11585 cx.assert_editor_state(indoc! {"
11586 fn func(abc ˇdef: i32) -> u32 {
11587 }
11588 "});
11589
11590 // First diagnostic
11591 cx.update_editor(|editor, window, cx| {
11592 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11593 });
11594 cx.assert_editor_state(indoc! {"
11595 fn func(abcˇ def: i32) -> u32 {
11596 }
11597 "});
11598
11599 // Wrapped over, fourth diagnostic
11600 cx.update_editor(|editor, window, cx| {
11601 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11602 });
11603 cx.assert_editor_state(indoc! {"
11604 fn func(abc def: i32) -> ˇu32 {
11605 }
11606 "});
11607
11608 cx.update_editor(|editor, window, cx| {
11609 editor.move_to_beginning(&MoveToBeginning, window, cx);
11610 });
11611 cx.assert_editor_state(indoc! {"
11612 ˇfn func(abc def: i32) -> u32 {
11613 }
11614 "});
11615
11616 //// Forward
11617
11618 // First diagnostic
11619 cx.update_editor(|editor, window, cx| {
11620 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11621 });
11622 cx.assert_editor_state(indoc! {"
11623 fn func(abcˇ def: i32) -> u32 {
11624 }
11625 "});
11626
11627 // Second diagnostic
11628 cx.update_editor(|editor, window, cx| {
11629 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11630 });
11631 cx.assert_editor_state(indoc! {"
11632 fn func(abc ˇdef: i32) -> u32 {
11633 }
11634 "});
11635
11636 // Third diagnostic, same place
11637 cx.update_editor(|editor, window, cx| {
11638 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11639 });
11640 cx.assert_editor_state(indoc! {"
11641 fn func(abc ˇdef: i32) -> u32 {
11642 }
11643 "});
11644
11645 // Fourth diagnostic
11646 cx.update_editor(|editor, window, cx| {
11647 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11648 });
11649 cx.assert_editor_state(indoc! {"
11650 fn func(abc def: i32) -> ˇu32 {
11651 }
11652 "});
11653
11654 // Wrapped around, first diagnostic
11655 cx.update_editor(|editor, window, cx| {
11656 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11657 });
11658 cx.assert_editor_state(indoc! {"
11659 fn func(abcˇ def: i32) -> u32 {
11660 }
11661 "});
11662}
11663
11664#[gpui::test]
11665async fn active_diagnostics_dismiss_after_invalidation(
11666 executor: BackgroundExecutor,
11667 cx: &mut TestAppContext,
11668) {
11669 init_test(cx, |_| {});
11670
11671 let mut cx = EditorTestContext::new(cx).await;
11672 let lsp_store =
11673 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11674
11675 cx.set_state(indoc! {"
11676 ˇfn func(abc def: i32) -> u32 {
11677 }
11678 "});
11679
11680 let message = "Something's wrong!";
11681 cx.update(|_, cx| {
11682 lsp_store.update(cx, |lsp_store, cx| {
11683 lsp_store
11684 .update_diagnostics(
11685 LanguageServerId(0),
11686 lsp::PublishDiagnosticsParams {
11687 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11688 version: None,
11689 diagnostics: vec![lsp::Diagnostic {
11690 range: lsp::Range::new(
11691 lsp::Position::new(0, 11),
11692 lsp::Position::new(0, 12),
11693 ),
11694 severity: Some(lsp::DiagnosticSeverity::ERROR),
11695 message: message.to_string(),
11696 ..Default::default()
11697 }],
11698 },
11699 &[],
11700 cx,
11701 )
11702 .unwrap()
11703 });
11704 });
11705 executor.run_until_parked();
11706
11707 cx.update_editor(|editor, window, cx| {
11708 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11709 assert_eq!(
11710 editor
11711 .active_diagnostics
11712 .as_ref()
11713 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11714 Some(message),
11715 "Should have a diagnostics group activated"
11716 );
11717 });
11718 cx.assert_editor_state(indoc! {"
11719 fn func(abcˇ def: i32) -> u32 {
11720 }
11721 "});
11722
11723 cx.update(|_, cx| {
11724 lsp_store.update(cx, |lsp_store, cx| {
11725 lsp_store
11726 .update_diagnostics(
11727 LanguageServerId(0),
11728 lsp::PublishDiagnosticsParams {
11729 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11730 version: None,
11731 diagnostics: Vec::new(),
11732 },
11733 &[],
11734 cx,
11735 )
11736 .unwrap()
11737 });
11738 });
11739 executor.run_until_parked();
11740 cx.update_editor(|editor, _, _| {
11741 assert_eq!(
11742 editor.active_diagnostics, None,
11743 "After no diagnostics set to the editor, no diagnostics should be active"
11744 );
11745 });
11746 cx.assert_editor_state(indoc! {"
11747 fn func(abcˇ def: i32) -> u32 {
11748 }
11749 "});
11750
11751 cx.update_editor(|editor, window, cx| {
11752 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11753 assert_eq!(
11754 editor.active_diagnostics, None,
11755 "Should be no diagnostics to go to and activate"
11756 );
11757 });
11758 cx.assert_editor_state(indoc! {"
11759 fn func(abcˇ def: i32) -> u32 {
11760 }
11761 "});
11762}
11763
11764#[gpui::test]
11765async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11766 init_test(cx, |_| {});
11767
11768 let mut cx = EditorTestContext::new(cx).await;
11769
11770 cx.set_state(indoc! {"
11771 fn func(abˇc def: i32) -> u32 {
11772 }
11773 "});
11774 let lsp_store =
11775 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11776
11777 cx.update(|_, cx| {
11778 lsp_store.update(cx, |lsp_store, cx| {
11779 lsp_store.update_diagnostics(
11780 LanguageServerId(0),
11781 lsp::PublishDiagnosticsParams {
11782 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11783 version: None,
11784 diagnostics: vec![lsp::Diagnostic {
11785 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11786 severity: Some(lsp::DiagnosticSeverity::ERROR),
11787 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11788 ..Default::default()
11789 }],
11790 },
11791 &[],
11792 cx,
11793 )
11794 })
11795 }).unwrap();
11796 cx.run_until_parked();
11797 cx.update_editor(|editor, window, cx| {
11798 hover_popover::hover(editor, &Default::default(), window, cx)
11799 });
11800 cx.run_until_parked();
11801 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11802}
11803
11804#[gpui::test]
11805async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11806 init_test(cx, |_| {});
11807
11808 let mut cx = EditorTestContext::new(cx).await;
11809
11810 let diff_base = r#"
11811 use some::mod;
11812
11813 const A: u32 = 42;
11814
11815 fn main() {
11816 println!("hello");
11817
11818 println!("world");
11819 }
11820 "#
11821 .unindent();
11822
11823 // Edits are modified, removed, modified, added
11824 cx.set_state(
11825 &r#"
11826 use some::modified;
11827
11828 ˇ
11829 fn main() {
11830 println!("hello there");
11831
11832 println!("around the");
11833 println!("world");
11834 }
11835 "#
11836 .unindent(),
11837 );
11838
11839 cx.set_head_text(&diff_base);
11840 executor.run_until_parked();
11841
11842 cx.update_editor(|editor, window, cx| {
11843 //Wrap around the bottom of the buffer
11844 for _ in 0..3 {
11845 editor.go_to_next_hunk(&GoToHunk, window, cx);
11846 }
11847 });
11848
11849 cx.assert_editor_state(
11850 &r#"
11851 ˇuse some::modified;
11852
11853
11854 fn main() {
11855 println!("hello there");
11856
11857 println!("around the");
11858 println!("world");
11859 }
11860 "#
11861 .unindent(),
11862 );
11863
11864 cx.update_editor(|editor, window, cx| {
11865 //Wrap around the top of the buffer
11866 for _ in 0..2 {
11867 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11868 }
11869 });
11870
11871 cx.assert_editor_state(
11872 &r#"
11873 use some::modified;
11874
11875
11876 fn main() {
11877 ˇ println!("hello there");
11878
11879 println!("around the");
11880 println!("world");
11881 }
11882 "#
11883 .unindent(),
11884 );
11885
11886 cx.update_editor(|editor, window, cx| {
11887 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11888 });
11889
11890 cx.assert_editor_state(
11891 &r#"
11892 use some::modified;
11893
11894 ˇ
11895 fn main() {
11896 println!("hello there");
11897
11898 println!("around the");
11899 println!("world");
11900 }
11901 "#
11902 .unindent(),
11903 );
11904
11905 cx.update_editor(|editor, window, cx| {
11906 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11907 });
11908
11909 cx.assert_editor_state(
11910 &r#"
11911 ˇuse some::modified;
11912
11913
11914 fn main() {
11915 println!("hello there");
11916
11917 println!("around the");
11918 println!("world");
11919 }
11920 "#
11921 .unindent(),
11922 );
11923
11924 cx.update_editor(|editor, window, cx| {
11925 for _ in 0..2 {
11926 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11927 }
11928 });
11929
11930 cx.assert_editor_state(
11931 &r#"
11932 use some::modified;
11933
11934
11935 fn main() {
11936 ˇ println!("hello there");
11937
11938 println!("around the");
11939 println!("world");
11940 }
11941 "#
11942 .unindent(),
11943 );
11944
11945 cx.update_editor(|editor, window, cx| {
11946 editor.fold(&Fold, window, cx);
11947 });
11948
11949 cx.update_editor(|editor, window, cx| {
11950 editor.go_to_next_hunk(&GoToHunk, window, cx);
11951 });
11952
11953 cx.assert_editor_state(
11954 &r#"
11955 ˇuse some::modified;
11956
11957
11958 fn main() {
11959 println!("hello there");
11960
11961 println!("around the");
11962 println!("world");
11963 }
11964 "#
11965 .unindent(),
11966 );
11967}
11968
11969#[test]
11970fn test_split_words() {
11971 fn split(text: &str) -> Vec<&str> {
11972 split_words(text).collect()
11973 }
11974
11975 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11976 assert_eq!(split("hello_world"), &["hello_", "world"]);
11977 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11978 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11979 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11980 assert_eq!(split("helloworld"), &["helloworld"]);
11981
11982 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11983}
11984
11985#[gpui::test]
11986async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11987 init_test(cx, |_| {});
11988
11989 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11990 let mut assert = |before, after| {
11991 let _state_context = cx.set_state(before);
11992 cx.run_until_parked();
11993 cx.update_editor(|editor, window, cx| {
11994 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11995 });
11996 cx.run_until_parked();
11997 cx.assert_editor_state(after);
11998 };
11999
12000 // Outside bracket jumps to outside of matching bracket
12001 assert("console.logˇ(var);", "console.log(var)ˇ;");
12002 assert("console.log(var)ˇ;", "console.logˇ(var);");
12003
12004 // Inside bracket jumps to inside of matching bracket
12005 assert("console.log(ˇvar);", "console.log(varˇ);");
12006 assert("console.log(varˇ);", "console.log(ˇvar);");
12007
12008 // When outside a bracket and inside, favor jumping to the inside bracket
12009 assert(
12010 "console.log('foo', [1, 2, 3]ˇ);",
12011 "console.log(ˇ'foo', [1, 2, 3]);",
12012 );
12013 assert(
12014 "console.log(ˇ'foo', [1, 2, 3]);",
12015 "console.log('foo', [1, 2, 3]ˇ);",
12016 );
12017
12018 // Bias forward if two options are equally likely
12019 assert(
12020 "let result = curried_fun()ˇ();",
12021 "let result = curried_fun()()ˇ;",
12022 );
12023
12024 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
12025 assert(
12026 indoc! {"
12027 function test() {
12028 console.log('test')ˇ
12029 }"},
12030 indoc! {"
12031 function test() {
12032 console.logˇ('test')
12033 }"},
12034 );
12035}
12036
12037#[gpui::test]
12038async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
12039 init_test(cx, |_| {});
12040
12041 let fs = FakeFs::new(cx.executor());
12042 fs.insert_tree(
12043 path!("/a"),
12044 json!({
12045 "main.rs": "fn main() { let a = 5; }",
12046 "other.rs": "// Test file",
12047 }),
12048 )
12049 .await;
12050 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12051
12052 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12053 language_registry.add(Arc::new(Language::new(
12054 LanguageConfig {
12055 name: "Rust".into(),
12056 matcher: LanguageMatcher {
12057 path_suffixes: vec!["rs".to_string()],
12058 ..Default::default()
12059 },
12060 brackets: BracketPairConfig {
12061 pairs: vec![BracketPair {
12062 start: "{".to_string(),
12063 end: "}".to_string(),
12064 close: true,
12065 surround: true,
12066 newline: true,
12067 }],
12068 disabled_scopes_by_bracket_ix: Vec::new(),
12069 },
12070 ..Default::default()
12071 },
12072 Some(tree_sitter_rust::LANGUAGE.into()),
12073 )));
12074 let mut fake_servers = language_registry.register_fake_lsp(
12075 "Rust",
12076 FakeLspAdapter {
12077 capabilities: lsp::ServerCapabilities {
12078 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12079 first_trigger_character: "{".to_string(),
12080 more_trigger_character: None,
12081 }),
12082 ..Default::default()
12083 },
12084 ..Default::default()
12085 },
12086 );
12087
12088 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12089
12090 let cx = &mut VisualTestContext::from_window(*workspace, cx);
12091
12092 let worktree_id = workspace
12093 .update(cx, |workspace, _, cx| {
12094 workspace.project().update(cx, |project, cx| {
12095 project.worktrees(cx).next().unwrap().read(cx).id()
12096 })
12097 })
12098 .unwrap();
12099
12100 let buffer = project
12101 .update(cx, |project, cx| {
12102 project.open_local_buffer(path!("/a/main.rs"), cx)
12103 })
12104 .await
12105 .unwrap();
12106 let editor_handle = workspace
12107 .update(cx, |workspace, window, cx| {
12108 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12109 })
12110 .unwrap()
12111 .await
12112 .unwrap()
12113 .downcast::<Editor>()
12114 .unwrap();
12115
12116 cx.executor().start_waiting();
12117 let fake_server = fake_servers.next().await.unwrap();
12118
12119 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
12120 assert_eq!(
12121 params.text_document_position.text_document.uri,
12122 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12123 );
12124 assert_eq!(
12125 params.text_document_position.position,
12126 lsp::Position::new(0, 21),
12127 );
12128
12129 Ok(Some(vec![lsp::TextEdit {
12130 new_text: "]".to_string(),
12131 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12132 }]))
12133 });
12134
12135 editor_handle.update_in(cx, |editor, window, cx| {
12136 window.focus(&editor.focus_handle(cx));
12137 editor.change_selections(None, window, cx, |s| {
12138 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12139 });
12140 editor.handle_input("{", window, cx);
12141 });
12142
12143 cx.executor().run_until_parked();
12144
12145 buffer.update(cx, |buffer, _| {
12146 assert_eq!(
12147 buffer.text(),
12148 "fn main() { let a = {5}; }",
12149 "No extra braces from on type formatting should appear in the buffer"
12150 )
12151 });
12152}
12153
12154#[gpui::test]
12155async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12156 init_test(cx, |_| {});
12157
12158 let fs = FakeFs::new(cx.executor());
12159 fs.insert_tree(
12160 path!("/a"),
12161 json!({
12162 "main.rs": "fn main() { let a = 5; }",
12163 "other.rs": "// Test file",
12164 }),
12165 )
12166 .await;
12167
12168 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12169
12170 let server_restarts = Arc::new(AtomicUsize::new(0));
12171 let closure_restarts = Arc::clone(&server_restarts);
12172 let language_server_name = "test language server";
12173 let language_name: LanguageName = "Rust".into();
12174
12175 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12176 language_registry.add(Arc::new(Language::new(
12177 LanguageConfig {
12178 name: language_name.clone(),
12179 matcher: LanguageMatcher {
12180 path_suffixes: vec!["rs".to_string()],
12181 ..Default::default()
12182 },
12183 ..Default::default()
12184 },
12185 Some(tree_sitter_rust::LANGUAGE.into()),
12186 )));
12187 let mut fake_servers = language_registry.register_fake_lsp(
12188 "Rust",
12189 FakeLspAdapter {
12190 name: language_server_name,
12191 initialization_options: Some(json!({
12192 "testOptionValue": true
12193 })),
12194 initializer: Some(Box::new(move |fake_server| {
12195 let task_restarts = Arc::clone(&closure_restarts);
12196 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
12197 task_restarts.fetch_add(1, atomic::Ordering::Release);
12198 futures::future::ready(Ok(()))
12199 });
12200 })),
12201 ..Default::default()
12202 },
12203 );
12204
12205 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12206 let _buffer = project
12207 .update(cx, |project, cx| {
12208 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12209 })
12210 .await
12211 .unwrap();
12212 let _fake_server = fake_servers.next().await.unwrap();
12213 update_test_language_settings(cx, |language_settings| {
12214 language_settings.languages.insert(
12215 language_name.clone(),
12216 LanguageSettingsContent {
12217 tab_size: NonZeroU32::new(8),
12218 ..Default::default()
12219 },
12220 );
12221 });
12222 cx.executor().run_until_parked();
12223 assert_eq!(
12224 server_restarts.load(atomic::Ordering::Acquire),
12225 0,
12226 "Should not restart LSP server on an unrelated change"
12227 );
12228
12229 update_test_project_settings(cx, |project_settings| {
12230 project_settings.lsp.insert(
12231 "Some other server name".into(),
12232 LspSettings {
12233 binary: None,
12234 settings: None,
12235 initialization_options: Some(json!({
12236 "some other init value": false
12237 })),
12238 },
12239 );
12240 });
12241 cx.executor().run_until_parked();
12242 assert_eq!(
12243 server_restarts.load(atomic::Ordering::Acquire),
12244 0,
12245 "Should not restart LSP server on an unrelated LSP settings change"
12246 );
12247
12248 update_test_project_settings(cx, |project_settings| {
12249 project_settings.lsp.insert(
12250 language_server_name.into(),
12251 LspSettings {
12252 binary: None,
12253 settings: None,
12254 initialization_options: Some(json!({
12255 "anotherInitValue": false
12256 })),
12257 },
12258 );
12259 });
12260 cx.executor().run_until_parked();
12261 assert_eq!(
12262 server_restarts.load(atomic::Ordering::Acquire),
12263 1,
12264 "Should restart LSP server on a related LSP settings change"
12265 );
12266
12267 update_test_project_settings(cx, |project_settings| {
12268 project_settings.lsp.insert(
12269 language_server_name.into(),
12270 LspSettings {
12271 binary: None,
12272 settings: None,
12273 initialization_options: Some(json!({
12274 "anotherInitValue": false
12275 })),
12276 },
12277 );
12278 });
12279 cx.executor().run_until_parked();
12280 assert_eq!(
12281 server_restarts.load(atomic::Ordering::Acquire),
12282 1,
12283 "Should not restart LSP server on a related LSP settings change that is the same"
12284 );
12285
12286 update_test_project_settings(cx, |project_settings| {
12287 project_settings.lsp.insert(
12288 language_server_name.into(),
12289 LspSettings {
12290 binary: None,
12291 settings: None,
12292 initialization_options: None,
12293 },
12294 );
12295 });
12296 cx.executor().run_until_parked();
12297 assert_eq!(
12298 server_restarts.load(atomic::Ordering::Acquire),
12299 2,
12300 "Should restart LSP server on another related LSP settings change"
12301 );
12302}
12303
12304#[gpui::test]
12305async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12306 init_test(cx, |_| {});
12307
12308 let mut cx = EditorLspTestContext::new_rust(
12309 lsp::ServerCapabilities {
12310 completion_provider: Some(lsp::CompletionOptions {
12311 trigger_characters: Some(vec![".".to_string()]),
12312 resolve_provider: Some(true),
12313 ..Default::default()
12314 }),
12315 ..Default::default()
12316 },
12317 cx,
12318 )
12319 .await;
12320
12321 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12322 cx.simulate_keystroke(".");
12323 let completion_item = lsp::CompletionItem {
12324 label: "some".into(),
12325 kind: Some(lsp::CompletionItemKind::SNIPPET),
12326 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12327 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12328 kind: lsp::MarkupKind::Markdown,
12329 value: "```rust\nSome(2)\n```".to_string(),
12330 })),
12331 deprecated: Some(false),
12332 sort_text: Some("fffffff2".to_string()),
12333 filter_text: Some("some".to_string()),
12334 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12335 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12336 range: lsp::Range {
12337 start: lsp::Position {
12338 line: 0,
12339 character: 22,
12340 },
12341 end: lsp::Position {
12342 line: 0,
12343 character: 22,
12344 },
12345 },
12346 new_text: "Some(2)".to_string(),
12347 })),
12348 additional_text_edits: Some(vec![lsp::TextEdit {
12349 range: lsp::Range {
12350 start: lsp::Position {
12351 line: 0,
12352 character: 20,
12353 },
12354 end: lsp::Position {
12355 line: 0,
12356 character: 22,
12357 },
12358 },
12359 new_text: "".to_string(),
12360 }]),
12361 ..Default::default()
12362 };
12363
12364 let closure_completion_item = completion_item.clone();
12365 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12366 let task_completion_item = closure_completion_item.clone();
12367 async move {
12368 Ok(Some(lsp::CompletionResponse::Array(vec![
12369 task_completion_item,
12370 ])))
12371 }
12372 });
12373
12374 request.next().await;
12375
12376 cx.condition(|editor, _| editor.context_menu_visible())
12377 .await;
12378 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12379 editor
12380 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12381 .unwrap()
12382 });
12383 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12384
12385 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12386 let task_completion_item = completion_item.clone();
12387 async move { Ok(task_completion_item) }
12388 })
12389 .next()
12390 .await
12391 .unwrap();
12392 apply_additional_edits.await.unwrap();
12393 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12394}
12395
12396#[gpui::test]
12397async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12398 init_test(cx, |_| {});
12399
12400 let mut cx = EditorLspTestContext::new_rust(
12401 lsp::ServerCapabilities {
12402 completion_provider: Some(lsp::CompletionOptions {
12403 trigger_characters: Some(vec![".".to_string()]),
12404 resolve_provider: Some(true),
12405 ..Default::default()
12406 }),
12407 ..Default::default()
12408 },
12409 cx,
12410 )
12411 .await;
12412
12413 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12414 cx.simulate_keystroke(".");
12415
12416 let item1 = lsp::CompletionItem {
12417 label: "method id()".to_string(),
12418 filter_text: Some("id".to_string()),
12419 detail: None,
12420 documentation: None,
12421 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12422 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12423 new_text: ".id".to_string(),
12424 })),
12425 ..lsp::CompletionItem::default()
12426 };
12427
12428 let item2 = lsp::CompletionItem {
12429 label: "other".to_string(),
12430 filter_text: Some("other".to_string()),
12431 detail: None,
12432 documentation: None,
12433 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12434 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12435 new_text: ".other".to_string(),
12436 })),
12437 ..lsp::CompletionItem::default()
12438 };
12439
12440 let item1 = item1.clone();
12441 cx.handle_request::<lsp::request::Completion, _, _>({
12442 let item1 = item1.clone();
12443 move |_, _, _| {
12444 let item1 = item1.clone();
12445 let item2 = item2.clone();
12446 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12447 }
12448 })
12449 .next()
12450 .await;
12451
12452 cx.condition(|editor, _| editor.context_menu_visible())
12453 .await;
12454 cx.update_editor(|editor, _, _| {
12455 let context_menu = editor.context_menu.borrow_mut();
12456 let context_menu = context_menu
12457 .as_ref()
12458 .expect("Should have the context menu deployed");
12459 match context_menu {
12460 CodeContextMenu::Completions(completions_menu) => {
12461 let completions = completions_menu.completions.borrow_mut();
12462 assert_eq!(
12463 completions
12464 .iter()
12465 .map(|completion| &completion.label.text)
12466 .collect::<Vec<_>>(),
12467 vec!["method id()", "other"]
12468 )
12469 }
12470 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12471 }
12472 });
12473
12474 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12475 let item1 = item1.clone();
12476 move |_, item_to_resolve, _| {
12477 let item1 = item1.clone();
12478 async move {
12479 if item1 == item_to_resolve {
12480 Ok(lsp::CompletionItem {
12481 label: "method id()".to_string(),
12482 filter_text: Some("id".to_string()),
12483 detail: Some("Now resolved!".to_string()),
12484 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12485 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12486 range: lsp::Range::new(
12487 lsp::Position::new(0, 22),
12488 lsp::Position::new(0, 22),
12489 ),
12490 new_text: ".id".to_string(),
12491 })),
12492 ..lsp::CompletionItem::default()
12493 })
12494 } else {
12495 Ok(item_to_resolve)
12496 }
12497 }
12498 }
12499 })
12500 .next()
12501 .await
12502 .unwrap();
12503 cx.run_until_parked();
12504
12505 cx.update_editor(|editor, window, cx| {
12506 editor.context_menu_next(&Default::default(), window, cx);
12507 });
12508
12509 cx.update_editor(|editor, _, _| {
12510 let context_menu = editor.context_menu.borrow_mut();
12511 let context_menu = context_menu
12512 .as_ref()
12513 .expect("Should have the context menu deployed");
12514 match context_menu {
12515 CodeContextMenu::Completions(completions_menu) => {
12516 let completions = completions_menu.completions.borrow_mut();
12517 assert_eq!(
12518 completions
12519 .iter()
12520 .map(|completion| &completion.label.text)
12521 .collect::<Vec<_>>(),
12522 vec!["method id() Now resolved!", "other"],
12523 "Should update first completion label, but not second as the filter text did not match."
12524 );
12525 }
12526 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12527 }
12528 });
12529}
12530
12531#[gpui::test]
12532async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12533 init_test(cx, |_| {});
12534
12535 let mut cx = EditorLspTestContext::new_rust(
12536 lsp::ServerCapabilities {
12537 completion_provider: Some(lsp::CompletionOptions {
12538 trigger_characters: Some(vec![".".to_string()]),
12539 resolve_provider: Some(true),
12540 ..Default::default()
12541 }),
12542 ..Default::default()
12543 },
12544 cx,
12545 )
12546 .await;
12547
12548 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12549 cx.simulate_keystroke(".");
12550
12551 let unresolved_item_1 = lsp::CompletionItem {
12552 label: "id".to_string(),
12553 filter_text: Some("id".to_string()),
12554 detail: None,
12555 documentation: None,
12556 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12557 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12558 new_text: ".id".to_string(),
12559 })),
12560 ..lsp::CompletionItem::default()
12561 };
12562 let resolved_item_1 = lsp::CompletionItem {
12563 additional_text_edits: Some(vec![lsp::TextEdit {
12564 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12565 new_text: "!!".to_string(),
12566 }]),
12567 ..unresolved_item_1.clone()
12568 };
12569 let unresolved_item_2 = lsp::CompletionItem {
12570 label: "other".to_string(),
12571 filter_text: Some("other".to_string()),
12572 detail: None,
12573 documentation: None,
12574 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12575 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12576 new_text: ".other".to_string(),
12577 })),
12578 ..lsp::CompletionItem::default()
12579 };
12580 let resolved_item_2 = lsp::CompletionItem {
12581 additional_text_edits: Some(vec![lsp::TextEdit {
12582 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12583 new_text: "??".to_string(),
12584 }]),
12585 ..unresolved_item_2.clone()
12586 };
12587
12588 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12589 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12590 cx.lsp
12591 .server
12592 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12593 let unresolved_item_1 = unresolved_item_1.clone();
12594 let resolved_item_1 = resolved_item_1.clone();
12595 let unresolved_item_2 = unresolved_item_2.clone();
12596 let resolved_item_2 = resolved_item_2.clone();
12597 let resolve_requests_1 = resolve_requests_1.clone();
12598 let resolve_requests_2 = resolve_requests_2.clone();
12599 move |unresolved_request, _| {
12600 let unresolved_item_1 = unresolved_item_1.clone();
12601 let resolved_item_1 = resolved_item_1.clone();
12602 let unresolved_item_2 = unresolved_item_2.clone();
12603 let resolved_item_2 = resolved_item_2.clone();
12604 let resolve_requests_1 = resolve_requests_1.clone();
12605 let resolve_requests_2 = resolve_requests_2.clone();
12606 async move {
12607 if unresolved_request == unresolved_item_1 {
12608 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12609 Ok(resolved_item_1.clone())
12610 } else if unresolved_request == unresolved_item_2 {
12611 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12612 Ok(resolved_item_2.clone())
12613 } else {
12614 panic!("Unexpected completion item {unresolved_request:?}")
12615 }
12616 }
12617 }
12618 })
12619 .detach();
12620
12621 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12622 let unresolved_item_1 = unresolved_item_1.clone();
12623 let unresolved_item_2 = unresolved_item_2.clone();
12624 async move {
12625 Ok(Some(lsp::CompletionResponse::Array(vec![
12626 unresolved_item_1,
12627 unresolved_item_2,
12628 ])))
12629 }
12630 })
12631 .next()
12632 .await;
12633
12634 cx.condition(|editor, _| editor.context_menu_visible())
12635 .await;
12636 cx.update_editor(|editor, _, _| {
12637 let context_menu = editor.context_menu.borrow_mut();
12638 let context_menu = context_menu
12639 .as_ref()
12640 .expect("Should have the context menu deployed");
12641 match context_menu {
12642 CodeContextMenu::Completions(completions_menu) => {
12643 let completions = completions_menu.completions.borrow_mut();
12644 assert_eq!(
12645 completions
12646 .iter()
12647 .map(|completion| &completion.label.text)
12648 .collect::<Vec<_>>(),
12649 vec!["id", "other"]
12650 )
12651 }
12652 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12653 }
12654 });
12655 cx.run_until_parked();
12656
12657 cx.update_editor(|editor, window, cx| {
12658 editor.context_menu_next(&ContextMenuNext, window, cx);
12659 });
12660 cx.run_until_parked();
12661 cx.update_editor(|editor, window, cx| {
12662 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12663 });
12664 cx.run_until_parked();
12665 cx.update_editor(|editor, window, cx| {
12666 editor.context_menu_next(&ContextMenuNext, window, cx);
12667 });
12668 cx.run_until_parked();
12669 cx.update_editor(|editor, window, cx| {
12670 editor
12671 .compose_completion(&ComposeCompletion::default(), window, cx)
12672 .expect("No task returned")
12673 })
12674 .await
12675 .expect("Completion failed");
12676 cx.run_until_parked();
12677
12678 cx.update_editor(|editor, _, cx| {
12679 assert_eq!(
12680 resolve_requests_1.load(atomic::Ordering::Acquire),
12681 1,
12682 "Should always resolve once despite multiple selections"
12683 );
12684 assert_eq!(
12685 resolve_requests_2.load(atomic::Ordering::Acquire),
12686 1,
12687 "Should always resolve once after multiple selections and applying the completion"
12688 );
12689 assert_eq!(
12690 editor.text(cx),
12691 "fn main() { let a = ??.other; }",
12692 "Should use resolved data when applying the completion"
12693 );
12694 });
12695}
12696
12697#[gpui::test]
12698async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12699 init_test(cx, |_| {});
12700
12701 let item_0 = lsp::CompletionItem {
12702 label: "abs".into(),
12703 insert_text: Some("abs".into()),
12704 data: Some(json!({ "very": "special"})),
12705 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12706 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12707 lsp::InsertReplaceEdit {
12708 new_text: "abs".to_string(),
12709 insert: lsp::Range::default(),
12710 replace: lsp::Range::default(),
12711 },
12712 )),
12713 ..lsp::CompletionItem::default()
12714 };
12715 let items = iter::once(item_0.clone())
12716 .chain((11..51).map(|i| lsp::CompletionItem {
12717 label: format!("item_{}", i),
12718 insert_text: Some(format!("item_{}", i)),
12719 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12720 ..lsp::CompletionItem::default()
12721 }))
12722 .collect::<Vec<_>>();
12723
12724 let default_commit_characters = vec!["?".to_string()];
12725 let default_data = json!({ "default": "data"});
12726 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12727 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12728 let default_edit_range = lsp::Range {
12729 start: lsp::Position {
12730 line: 0,
12731 character: 5,
12732 },
12733 end: lsp::Position {
12734 line: 0,
12735 character: 5,
12736 },
12737 };
12738
12739 let mut cx = EditorLspTestContext::new_rust(
12740 lsp::ServerCapabilities {
12741 completion_provider: Some(lsp::CompletionOptions {
12742 trigger_characters: Some(vec![".".to_string()]),
12743 resolve_provider: Some(true),
12744 ..Default::default()
12745 }),
12746 ..Default::default()
12747 },
12748 cx,
12749 )
12750 .await;
12751
12752 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12753 cx.simulate_keystroke(".");
12754
12755 let completion_data = default_data.clone();
12756 let completion_characters = default_commit_characters.clone();
12757 let completion_items = items.clone();
12758 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12759 let default_data = completion_data.clone();
12760 let default_commit_characters = completion_characters.clone();
12761 let items = completion_items.clone();
12762 async move {
12763 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12764 items,
12765 item_defaults: Some(lsp::CompletionListItemDefaults {
12766 data: Some(default_data.clone()),
12767 commit_characters: Some(default_commit_characters.clone()),
12768 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12769 default_edit_range,
12770 )),
12771 insert_text_format: Some(default_insert_text_format),
12772 insert_text_mode: Some(default_insert_text_mode),
12773 }),
12774 ..lsp::CompletionList::default()
12775 })))
12776 }
12777 })
12778 .next()
12779 .await;
12780
12781 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12782 cx.lsp
12783 .server
12784 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12785 let closure_resolved_items = resolved_items.clone();
12786 move |item_to_resolve, _| {
12787 let closure_resolved_items = closure_resolved_items.clone();
12788 async move {
12789 closure_resolved_items.lock().push(item_to_resolve.clone());
12790 Ok(item_to_resolve)
12791 }
12792 }
12793 })
12794 .detach();
12795
12796 cx.condition(|editor, _| editor.context_menu_visible())
12797 .await;
12798 cx.run_until_parked();
12799 cx.update_editor(|editor, _, _| {
12800 let menu = editor.context_menu.borrow_mut();
12801 match menu.as_ref().expect("should have the completions menu") {
12802 CodeContextMenu::Completions(completions_menu) => {
12803 assert_eq!(
12804 completions_menu
12805 .entries
12806 .borrow()
12807 .iter()
12808 .map(|mat| mat.string.clone())
12809 .collect::<Vec<String>>(),
12810 items
12811 .iter()
12812 .map(|completion| completion.label.clone())
12813 .collect::<Vec<String>>()
12814 );
12815 }
12816 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12817 }
12818 });
12819 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12820 // with 4 from the end.
12821 assert_eq!(
12822 *resolved_items.lock(),
12823 [&items[0..16], &items[items.len() - 4..items.len()]]
12824 .concat()
12825 .iter()
12826 .cloned()
12827 .map(|mut item| {
12828 if item.data.is_none() {
12829 item.data = Some(default_data.clone());
12830 }
12831 item
12832 })
12833 .collect::<Vec<lsp::CompletionItem>>(),
12834 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12835 );
12836 resolved_items.lock().clear();
12837
12838 cx.update_editor(|editor, window, cx| {
12839 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12840 });
12841 cx.run_until_parked();
12842 // Completions that have already been resolved are skipped.
12843 assert_eq!(
12844 *resolved_items.lock(),
12845 items[items.len() - 16..items.len() - 4]
12846 .iter()
12847 .cloned()
12848 .map(|mut item| {
12849 if item.data.is_none() {
12850 item.data = Some(default_data.clone());
12851 }
12852 item
12853 })
12854 .collect::<Vec<lsp::CompletionItem>>()
12855 );
12856 resolved_items.lock().clear();
12857}
12858
12859#[gpui::test]
12860async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12861 init_test(cx, |_| {});
12862
12863 let mut cx = EditorLspTestContext::new(
12864 Language::new(
12865 LanguageConfig {
12866 matcher: LanguageMatcher {
12867 path_suffixes: vec!["jsx".into()],
12868 ..Default::default()
12869 },
12870 overrides: [(
12871 "element".into(),
12872 LanguageConfigOverride {
12873 word_characters: Override::Set(['-'].into_iter().collect()),
12874 ..Default::default()
12875 },
12876 )]
12877 .into_iter()
12878 .collect(),
12879 ..Default::default()
12880 },
12881 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12882 )
12883 .with_override_query("(jsx_self_closing_element) @element")
12884 .unwrap(),
12885 lsp::ServerCapabilities {
12886 completion_provider: Some(lsp::CompletionOptions {
12887 trigger_characters: Some(vec![":".to_string()]),
12888 ..Default::default()
12889 }),
12890 ..Default::default()
12891 },
12892 cx,
12893 )
12894 .await;
12895
12896 cx.lsp
12897 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12898 Ok(Some(lsp::CompletionResponse::Array(vec![
12899 lsp::CompletionItem {
12900 label: "bg-blue".into(),
12901 ..Default::default()
12902 },
12903 lsp::CompletionItem {
12904 label: "bg-red".into(),
12905 ..Default::default()
12906 },
12907 lsp::CompletionItem {
12908 label: "bg-yellow".into(),
12909 ..Default::default()
12910 },
12911 ])))
12912 });
12913
12914 cx.set_state(r#"<p class="bgˇ" />"#);
12915
12916 // Trigger completion when typing a dash, because the dash is an extra
12917 // word character in the 'element' scope, which contains the cursor.
12918 cx.simulate_keystroke("-");
12919 cx.executor().run_until_parked();
12920 cx.update_editor(|editor, _, _| {
12921 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12922 {
12923 assert_eq!(
12924 completion_menu_entries(&menu),
12925 &["bg-red", "bg-blue", "bg-yellow"]
12926 );
12927 } else {
12928 panic!("expected completion menu to be open");
12929 }
12930 });
12931
12932 cx.simulate_keystroke("l");
12933 cx.executor().run_until_parked();
12934 cx.update_editor(|editor, _, _| {
12935 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12936 {
12937 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12938 } else {
12939 panic!("expected completion menu to be open");
12940 }
12941 });
12942
12943 // When filtering completions, consider the character after the '-' to
12944 // be the start of a subword.
12945 cx.set_state(r#"<p class="yelˇ" />"#);
12946 cx.simulate_keystroke("l");
12947 cx.executor().run_until_parked();
12948 cx.update_editor(|editor, _, _| {
12949 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12950 {
12951 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12952 } else {
12953 panic!("expected completion menu to be open");
12954 }
12955 });
12956}
12957
12958fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12959 let entries = menu.entries.borrow();
12960 entries.iter().map(|mat| mat.string.clone()).collect()
12961}
12962
12963#[gpui::test]
12964async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12965 init_test(cx, |settings| {
12966 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12967 FormatterList(vec![Formatter::Prettier].into()),
12968 ))
12969 });
12970
12971 let fs = FakeFs::new(cx.executor());
12972 fs.insert_file(path!("/file.ts"), Default::default()).await;
12973
12974 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12975 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12976
12977 language_registry.add(Arc::new(Language::new(
12978 LanguageConfig {
12979 name: "TypeScript".into(),
12980 matcher: LanguageMatcher {
12981 path_suffixes: vec!["ts".to_string()],
12982 ..Default::default()
12983 },
12984 ..Default::default()
12985 },
12986 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12987 )));
12988 update_test_language_settings(cx, |settings| {
12989 settings.defaults.prettier = Some(PrettierSettings {
12990 allowed: true,
12991 ..PrettierSettings::default()
12992 });
12993 });
12994
12995 let test_plugin = "test_plugin";
12996 let _ = language_registry.register_fake_lsp(
12997 "TypeScript",
12998 FakeLspAdapter {
12999 prettier_plugins: vec![test_plugin],
13000 ..Default::default()
13001 },
13002 );
13003
13004 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
13005 let buffer = project
13006 .update(cx, |project, cx| {
13007 project.open_local_buffer(path!("/file.ts"), cx)
13008 })
13009 .await
13010 .unwrap();
13011
13012 let buffer_text = "one\ntwo\nthree\n";
13013 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13014 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13015 editor.update_in(cx, |editor, window, cx| {
13016 editor.set_text(buffer_text, window, cx)
13017 });
13018
13019 editor
13020 .update_in(cx, |editor, window, cx| {
13021 editor.perform_format(
13022 project.clone(),
13023 FormatTrigger::Manual,
13024 FormatTarget::Buffers,
13025 window,
13026 cx,
13027 )
13028 })
13029 .unwrap()
13030 .await;
13031 assert_eq!(
13032 editor.update(cx, |editor, cx| editor.text(cx)),
13033 buffer_text.to_string() + prettier_format_suffix,
13034 "Test prettier formatting was not applied to the original buffer text",
13035 );
13036
13037 update_test_language_settings(cx, |settings| {
13038 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
13039 });
13040 let format = editor.update_in(cx, |editor, window, cx| {
13041 editor.perform_format(
13042 project.clone(),
13043 FormatTrigger::Manual,
13044 FormatTarget::Buffers,
13045 window,
13046 cx,
13047 )
13048 });
13049 format.await.unwrap();
13050 assert_eq!(
13051 editor.update(cx, |editor, cx| editor.text(cx)),
13052 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
13053 "Autoformatting (via test prettier) was not applied to the original buffer text",
13054 );
13055}
13056
13057#[gpui::test]
13058async fn test_addition_reverts(cx: &mut TestAppContext) {
13059 init_test(cx, |_| {});
13060 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13061 let base_text = indoc! {r#"
13062 struct Row;
13063 struct Row1;
13064 struct Row2;
13065
13066 struct Row4;
13067 struct Row5;
13068 struct Row6;
13069
13070 struct Row8;
13071 struct Row9;
13072 struct Row10;"#};
13073
13074 // When addition hunks are not adjacent to carets, no hunk revert is performed
13075 assert_hunk_revert(
13076 indoc! {r#"struct Row;
13077 struct Row1;
13078 struct Row1.1;
13079 struct Row1.2;
13080 struct Row2;ˇ
13081
13082 struct Row4;
13083 struct Row5;
13084 struct Row6;
13085
13086 struct Row8;
13087 ˇstruct Row9;
13088 struct Row9.1;
13089 struct Row9.2;
13090 struct Row9.3;
13091 struct Row10;"#},
13092 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13093 indoc! {r#"struct Row;
13094 struct Row1;
13095 struct Row1.1;
13096 struct Row1.2;
13097 struct Row2;ˇ
13098
13099 struct Row4;
13100 struct Row5;
13101 struct Row6;
13102
13103 struct Row8;
13104 ˇstruct Row9;
13105 struct Row9.1;
13106 struct Row9.2;
13107 struct Row9.3;
13108 struct Row10;"#},
13109 base_text,
13110 &mut cx,
13111 );
13112 // Same for selections
13113 assert_hunk_revert(
13114 indoc! {r#"struct Row;
13115 struct Row1;
13116 struct Row2;
13117 struct Row2.1;
13118 struct Row2.2;
13119 «ˇ
13120 struct Row4;
13121 struct» Row5;
13122 «struct Row6;
13123 ˇ»
13124 struct Row9.1;
13125 struct Row9.2;
13126 struct Row9.3;
13127 struct Row8;
13128 struct Row9;
13129 struct Row10;"#},
13130 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13131 indoc! {r#"struct Row;
13132 struct Row1;
13133 struct Row2;
13134 struct Row2.1;
13135 struct Row2.2;
13136 «ˇ
13137 struct Row4;
13138 struct» Row5;
13139 «struct Row6;
13140 ˇ»
13141 struct Row9.1;
13142 struct Row9.2;
13143 struct Row9.3;
13144 struct Row8;
13145 struct Row9;
13146 struct Row10;"#},
13147 base_text,
13148 &mut cx,
13149 );
13150
13151 // When carets and selections intersect the addition hunks, those are reverted.
13152 // Adjacent carets got merged.
13153 assert_hunk_revert(
13154 indoc! {r#"struct Row;
13155 ˇ// something on the top
13156 struct Row1;
13157 struct Row2;
13158 struct Roˇw3.1;
13159 struct Row2.2;
13160 struct Row2.3;ˇ
13161
13162 struct Row4;
13163 struct ˇRow5.1;
13164 struct Row5.2;
13165 struct «Rowˇ»5.3;
13166 struct Row5;
13167 struct Row6;
13168 ˇ
13169 struct Row9.1;
13170 struct «Rowˇ»9.2;
13171 struct «ˇRow»9.3;
13172 struct Row8;
13173 struct Row9;
13174 «ˇ// something on bottom»
13175 struct Row10;"#},
13176 vec![
13177 DiffHunkStatusKind::Added,
13178 DiffHunkStatusKind::Added,
13179 DiffHunkStatusKind::Added,
13180 DiffHunkStatusKind::Added,
13181 DiffHunkStatusKind::Added,
13182 ],
13183 indoc! {r#"struct Row;
13184 ˇstruct Row1;
13185 struct Row2;
13186 ˇ
13187 struct Row4;
13188 ˇstruct Row5;
13189 struct Row6;
13190 ˇ
13191 ˇstruct Row8;
13192 struct Row9;
13193 ˇstruct Row10;"#},
13194 base_text,
13195 &mut cx,
13196 );
13197}
13198
13199#[gpui::test]
13200async fn test_modification_reverts(cx: &mut TestAppContext) {
13201 init_test(cx, |_| {});
13202 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13203 let base_text = indoc! {r#"
13204 struct Row;
13205 struct Row1;
13206 struct Row2;
13207
13208 struct Row4;
13209 struct Row5;
13210 struct Row6;
13211
13212 struct Row8;
13213 struct Row9;
13214 struct Row10;"#};
13215
13216 // Modification hunks behave the same as the addition ones.
13217 assert_hunk_revert(
13218 indoc! {r#"struct Row;
13219 struct Row1;
13220 struct Row33;
13221 ˇ
13222 struct Row4;
13223 struct Row5;
13224 struct Row6;
13225 ˇ
13226 struct Row99;
13227 struct Row9;
13228 struct Row10;"#},
13229 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13230 indoc! {r#"struct Row;
13231 struct Row1;
13232 struct Row33;
13233 ˇ
13234 struct Row4;
13235 struct Row5;
13236 struct Row6;
13237 ˇ
13238 struct Row99;
13239 struct Row9;
13240 struct Row10;"#},
13241 base_text,
13242 &mut cx,
13243 );
13244 assert_hunk_revert(
13245 indoc! {r#"struct Row;
13246 struct Row1;
13247 struct Row33;
13248 «ˇ
13249 struct Row4;
13250 struct» Row5;
13251 «struct Row6;
13252 ˇ»
13253 struct Row99;
13254 struct Row9;
13255 struct Row10;"#},
13256 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13257 indoc! {r#"struct Row;
13258 struct Row1;
13259 struct Row33;
13260 «ˇ
13261 struct Row4;
13262 struct» Row5;
13263 «struct Row6;
13264 ˇ»
13265 struct Row99;
13266 struct Row9;
13267 struct Row10;"#},
13268 base_text,
13269 &mut cx,
13270 );
13271
13272 assert_hunk_revert(
13273 indoc! {r#"ˇstruct Row1.1;
13274 struct Row1;
13275 «ˇstr»uct Row22;
13276
13277 struct ˇRow44;
13278 struct Row5;
13279 struct «Rˇ»ow66;ˇ
13280
13281 «struˇ»ct Row88;
13282 struct Row9;
13283 struct Row1011;ˇ"#},
13284 vec![
13285 DiffHunkStatusKind::Modified,
13286 DiffHunkStatusKind::Modified,
13287 DiffHunkStatusKind::Modified,
13288 DiffHunkStatusKind::Modified,
13289 DiffHunkStatusKind::Modified,
13290 DiffHunkStatusKind::Modified,
13291 ],
13292 indoc! {r#"struct Row;
13293 ˇstruct Row1;
13294 struct Row2;
13295 ˇ
13296 struct Row4;
13297 ˇstruct Row5;
13298 struct Row6;
13299 ˇ
13300 struct Row8;
13301 ˇstruct Row9;
13302 struct Row10;ˇ"#},
13303 base_text,
13304 &mut cx,
13305 );
13306}
13307
13308#[gpui::test]
13309async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13310 init_test(cx, |_| {});
13311 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13312 let base_text = indoc! {r#"
13313 one
13314
13315 two
13316 three
13317 "#};
13318
13319 cx.set_head_text(base_text);
13320 cx.set_state("\nˇ\n");
13321 cx.executor().run_until_parked();
13322 cx.update_editor(|editor, _window, cx| {
13323 editor.expand_selected_diff_hunks(cx);
13324 });
13325 cx.executor().run_until_parked();
13326 cx.update_editor(|editor, window, cx| {
13327 editor.backspace(&Default::default(), window, cx);
13328 });
13329 cx.run_until_parked();
13330 cx.assert_state_with_diff(
13331 indoc! {r#"
13332
13333 - two
13334 - threeˇ
13335 +
13336 "#}
13337 .to_string(),
13338 );
13339}
13340
13341#[gpui::test]
13342async fn test_deletion_reverts(cx: &mut TestAppContext) {
13343 init_test(cx, |_| {});
13344 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13345 let base_text = indoc! {r#"struct Row;
13346struct Row1;
13347struct Row2;
13348
13349struct Row4;
13350struct Row5;
13351struct Row6;
13352
13353struct Row8;
13354struct Row9;
13355struct Row10;"#};
13356
13357 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13358 assert_hunk_revert(
13359 indoc! {r#"struct Row;
13360 struct Row2;
13361
13362 ˇstruct Row4;
13363 struct Row5;
13364 struct Row6;
13365 ˇ
13366 struct Row8;
13367 struct Row10;"#},
13368 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13369 indoc! {r#"struct Row;
13370 struct Row2;
13371
13372 ˇstruct Row4;
13373 struct Row5;
13374 struct Row6;
13375 ˇ
13376 struct Row8;
13377 struct Row10;"#},
13378 base_text,
13379 &mut cx,
13380 );
13381 assert_hunk_revert(
13382 indoc! {r#"struct Row;
13383 struct Row2;
13384
13385 «ˇstruct Row4;
13386 struct» Row5;
13387 «struct Row6;
13388 ˇ»
13389 struct Row8;
13390 struct Row10;"#},
13391 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13392 indoc! {r#"struct Row;
13393 struct Row2;
13394
13395 «ˇstruct Row4;
13396 struct» Row5;
13397 «struct Row6;
13398 ˇ»
13399 struct Row8;
13400 struct Row10;"#},
13401 base_text,
13402 &mut cx,
13403 );
13404
13405 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13406 assert_hunk_revert(
13407 indoc! {r#"struct Row;
13408 ˇstruct Row2;
13409
13410 struct Row4;
13411 struct Row5;
13412 struct Row6;
13413
13414 struct Row8;ˇ
13415 struct Row10;"#},
13416 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13417 indoc! {r#"struct Row;
13418 struct Row1;
13419 ˇstruct Row2;
13420
13421 struct Row4;
13422 struct Row5;
13423 struct Row6;
13424
13425 struct Row8;ˇ
13426 struct Row9;
13427 struct Row10;"#},
13428 base_text,
13429 &mut cx,
13430 );
13431 assert_hunk_revert(
13432 indoc! {r#"struct Row;
13433 struct Row2«ˇ;
13434 struct Row4;
13435 struct» Row5;
13436 «struct Row6;
13437
13438 struct Row8;ˇ»
13439 struct Row10;"#},
13440 vec![
13441 DiffHunkStatusKind::Deleted,
13442 DiffHunkStatusKind::Deleted,
13443 DiffHunkStatusKind::Deleted,
13444 ],
13445 indoc! {r#"struct Row;
13446 struct Row1;
13447 struct Row2«ˇ;
13448
13449 struct Row4;
13450 struct» Row5;
13451 «struct Row6;
13452
13453 struct Row8;ˇ»
13454 struct Row9;
13455 struct Row10;"#},
13456 base_text,
13457 &mut cx,
13458 );
13459}
13460
13461#[gpui::test]
13462async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13463 init_test(cx, |_| {});
13464
13465 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13466 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13467 let base_text_3 =
13468 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13469
13470 let text_1 = edit_first_char_of_every_line(base_text_1);
13471 let text_2 = edit_first_char_of_every_line(base_text_2);
13472 let text_3 = edit_first_char_of_every_line(base_text_3);
13473
13474 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13475 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13476 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13477
13478 let multibuffer = cx.new(|cx| {
13479 let mut multibuffer = MultiBuffer::new(ReadWrite);
13480 multibuffer.push_excerpts(
13481 buffer_1.clone(),
13482 [
13483 ExcerptRange {
13484 context: Point::new(0, 0)..Point::new(3, 0),
13485 primary: None,
13486 },
13487 ExcerptRange {
13488 context: Point::new(5, 0)..Point::new(7, 0),
13489 primary: None,
13490 },
13491 ExcerptRange {
13492 context: Point::new(9, 0)..Point::new(10, 4),
13493 primary: None,
13494 },
13495 ],
13496 cx,
13497 );
13498 multibuffer.push_excerpts(
13499 buffer_2.clone(),
13500 [
13501 ExcerptRange {
13502 context: Point::new(0, 0)..Point::new(3, 0),
13503 primary: None,
13504 },
13505 ExcerptRange {
13506 context: Point::new(5, 0)..Point::new(7, 0),
13507 primary: None,
13508 },
13509 ExcerptRange {
13510 context: Point::new(9, 0)..Point::new(10, 4),
13511 primary: None,
13512 },
13513 ],
13514 cx,
13515 );
13516 multibuffer.push_excerpts(
13517 buffer_3.clone(),
13518 [
13519 ExcerptRange {
13520 context: Point::new(0, 0)..Point::new(3, 0),
13521 primary: None,
13522 },
13523 ExcerptRange {
13524 context: Point::new(5, 0)..Point::new(7, 0),
13525 primary: None,
13526 },
13527 ExcerptRange {
13528 context: Point::new(9, 0)..Point::new(10, 4),
13529 primary: None,
13530 },
13531 ],
13532 cx,
13533 );
13534 multibuffer
13535 });
13536
13537 let fs = FakeFs::new(cx.executor());
13538 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13539 let (editor, cx) = cx
13540 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13541 editor.update_in(cx, |editor, _window, cx| {
13542 for (buffer, diff_base) in [
13543 (buffer_1.clone(), base_text_1),
13544 (buffer_2.clone(), base_text_2),
13545 (buffer_3.clone(), base_text_3),
13546 ] {
13547 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13548 editor
13549 .buffer
13550 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13551 }
13552 });
13553 cx.executor().run_until_parked();
13554
13555 editor.update_in(cx, |editor, window, cx| {
13556 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}");
13557 editor.select_all(&SelectAll, window, cx);
13558 editor.git_restore(&Default::default(), window, cx);
13559 });
13560 cx.executor().run_until_parked();
13561
13562 // When all ranges are selected, all buffer hunks are reverted.
13563 editor.update(cx, |editor, cx| {
13564 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");
13565 });
13566 buffer_1.update(cx, |buffer, _| {
13567 assert_eq!(buffer.text(), base_text_1);
13568 });
13569 buffer_2.update(cx, |buffer, _| {
13570 assert_eq!(buffer.text(), base_text_2);
13571 });
13572 buffer_3.update(cx, |buffer, _| {
13573 assert_eq!(buffer.text(), base_text_3);
13574 });
13575
13576 editor.update_in(cx, |editor, window, cx| {
13577 editor.undo(&Default::default(), window, cx);
13578 });
13579
13580 editor.update_in(cx, |editor, window, cx| {
13581 editor.change_selections(None, window, cx, |s| {
13582 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13583 });
13584 editor.git_restore(&Default::default(), window, cx);
13585 });
13586
13587 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13588 // but not affect buffer_2 and its related excerpts.
13589 editor.update(cx, |editor, cx| {
13590 assert_eq!(
13591 editor.text(cx),
13592 "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}"
13593 );
13594 });
13595 buffer_1.update(cx, |buffer, _| {
13596 assert_eq!(buffer.text(), base_text_1);
13597 });
13598 buffer_2.update(cx, |buffer, _| {
13599 assert_eq!(
13600 buffer.text(),
13601 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13602 );
13603 });
13604 buffer_3.update(cx, |buffer, _| {
13605 assert_eq!(
13606 buffer.text(),
13607 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13608 );
13609 });
13610
13611 fn edit_first_char_of_every_line(text: &str) -> String {
13612 text.split('\n')
13613 .map(|line| format!("X{}", &line[1..]))
13614 .collect::<Vec<_>>()
13615 .join("\n")
13616 }
13617}
13618
13619#[gpui::test]
13620async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13621 init_test(cx, |_| {});
13622
13623 let cols = 4;
13624 let rows = 10;
13625 let sample_text_1 = sample_text(rows, cols, 'a');
13626 assert_eq!(
13627 sample_text_1,
13628 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13629 );
13630 let sample_text_2 = sample_text(rows, cols, 'l');
13631 assert_eq!(
13632 sample_text_2,
13633 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13634 );
13635 let sample_text_3 = sample_text(rows, cols, 'v');
13636 assert_eq!(
13637 sample_text_3,
13638 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13639 );
13640
13641 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13642 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13643 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13644
13645 let multi_buffer = cx.new(|cx| {
13646 let mut multibuffer = MultiBuffer::new(ReadWrite);
13647 multibuffer.push_excerpts(
13648 buffer_1.clone(),
13649 [
13650 ExcerptRange {
13651 context: Point::new(0, 0)..Point::new(3, 0),
13652 primary: None,
13653 },
13654 ExcerptRange {
13655 context: Point::new(5, 0)..Point::new(7, 0),
13656 primary: None,
13657 },
13658 ExcerptRange {
13659 context: Point::new(9, 0)..Point::new(10, 4),
13660 primary: None,
13661 },
13662 ],
13663 cx,
13664 );
13665 multibuffer.push_excerpts(
13666 buffer_2.clone(),
13667 [
13668 ExcerptRange {
13669 context: Point::new(0, 0)..Point::new(3, 0),
13670 primary: None,
13671 },
13672 ExcerptRange {
13673 context: Point::new(5, 0)..Point::new(7, 0),
13674 primary: None,
13675 },
13676 ExcerptRange {
13677 context: Point::new(9, 0)..Point::new(10, 4),
13678 primary: None,
13679 },
13680 ],
13681 cx,
13682 );
13683 multibuffer.push_excerpts(
13684 buffer_3.clone(),
13685 [
13686 ExcerptRange {
13687 context: Point::new(0, 0)..Point::new(3, 0),
13688 primary: None,
13689 },
13690 ExcerptRange {
13691 context: Point::new(5, 0)..Point::new(7, 0),
13692 primary: None,
13693 },
13694 ExcerptRange {
13695 context: Point::new(9, 0)..Point::new(10, 4),
13696 primary: None,
13697 },
13698 ],
13699 cx,
13700 );
13701 multibuffer
13702 });
13703
13704 let fs = FakeFs::new(cx.executor());
13705 fs.insert_tree(
13706 "/a",
13707 json!({
13708 "main.rs": sample_text_1,
13709 "other.rs": sample_text_2,
13710 "lib.rs": sample_text_3,
13711 }),
13712 )
13713 .await;
13714 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13715 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13716 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13717 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13718 Editor::new(
13719 EditorMode::Full,
13720 multi_buffer,
13721 Some(project.clone()),
13722 window,
13723 cx,
13724 )
13725 });
13726 let multibuffer_item_id = workspace
13727 .update(cx, |workspace, window, cx| {
13728 assert!(
13729 workspace.active_item(cx).is_none(),
13730 "active item should be None before the first item is added"
13731 );
13732 workspace.add_item_to_active_pane(
13733 Box::new(multi_buffer_editor.clone()),
13734 None,
13735 true,
13736 window,
13737 cx,
13738 );
13739 let active_item = workspace
13740 .active_item(cx)
13741 .expect("should have an active item after adding the multi buffer");
13742 assert!(
13743 !active_item.is_singleton(cx),
13744 "A multi buffer was expected to active after adding"
13745 );
13746 active_item.item_id()
13747 })
13748 .unwrap();
13749 cx.executor().run_until_parked();
13750
13751 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13752 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13753 s.select_ranges(Some(1..2))
13754 });
13755 editor.open_excerpts(&OpenExcerpts, window, cx);
13756 });
13757 cx.executor().run_until_parked();
13758 let first_item_id = workspace
13759 .update(cx, |workspace, window, cx| {
13760 let active_item = workspace
13761 .active_item(cx)
13762 .expect("should have an active item after navigating into the 1st buffer");
13763 let first_item_id = active_item.item_id();
13764 assert_ne!(
13765 first_item_id, multibuffer_item_id,
13766 "Should navigate into the 1st buffer and activate it"
13767 );
13768 assert!(
13769 active_item.is_singleton(cx),
13770 "New active item should be a singleton buffer"
13771 );
13772 assert_eq!(
13773 active_item
13774 .act_as::<Editor>(cx)
13775 .expect("should have navigated into an editor for the 1st buffer")
13776 .read(cx)
13777 .text(cx),
13778 sample_text_1
13779 );
13780
13781 workspace
13782 .go_back(workspace.active_pane().downgrade(), window, cx)
13783 .detach_and_log_err(cx);
13784
13785 first_item_id
13786 })
13787 .unwrap();
13788 cx.executor().run_until_parked();
13789 workspace
13790 .update(cx, |workspace, _, cx| {
13791 let active_item = workspace
13792 .active_item(cx)
13793 .expect("should have an active item after navigating back");
13794 assert_eq!(
13795 active_item.item_id(),
13796 multibuffer_item_id,
13797 "Should navigate back to the multi buffer"
13798 );
13799 assert!(!active_item.is_singleton(cx));
13800 })
13801 .unwrap();
13802
13803 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13804 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13805 s.select_ranges(Some(39..40))
13806 });
13807 editor.open_excerpts(&OpenExcerpts, window, cx);
13808 });
13809 cx.executor().run_until_parked();
13810 let second_item_id = workspace
13811 .update(cx, |workspace, window, cx| {
13812 let active_item = workspace
13813 .active_item(cx)
13814 .expect("should have an active item after navigating into the 2nd buffer");
13815 let second_item_id = active_item.item_id();
13816 assert_ne!(
13817 second_item_id, multibuffer_item_id,
13818 "Should navigate away from the multibuffer"
13819 );
13820 assert_ne!(
13821 second_item_id, first_item_id,
13822 "Should navigate into the 2nd buffer and activate it"
13823 );
13824 assert!(
13825 active_item.is_singleton(cx),
13826 "New active item should be a singleton buffer"
13827 );
13828 assert_eq!(
13829 active_item
13830 .act_as::<Editor>(cx)
13831 .expect("should have navigated into an editor")
13832 .read(cx)
13833 .text(cx),
13834 sample_text_2
13835 );
13836
13837 workspace
13838 .go_back(workspace.active_pane().downgrade(), window, cx)
13839 .detach_and_log_err(cx);
13840
13841 second_item_id
13842 })
13843 .unwrap();
13844 cx.executor().run_until_parked();
13845 workspace
13846 .update(cx, |workspace, _, cx| {
13847 let active_item = workspace
13848 .active_item(cx)
13849 .expect("should have an active item after navigating back from the 2nd buffer");
13850 assert_eq!(
13851 active_item.item_id(),
13852 multibuffer_item_id,
13853 "Should navigate back from the 2nd buffer to the multi buffer"
13854 );
13855 assert!(!active_item.is_singleton(cx));
13856 })
13857 .unwrap();
13858
13859 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13860 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13861 s.select_ranges(Some(70..70))
13862 });
13863 editor.open_excerpts(&OpenExcerpts, window, cx);
13864 });
13865 cx.executor().run_until_parked();
13866 workspace
13867 .update(cx, |workspace, window, cx| {
13868 let active_item = workspace
13869 .active_item(cx)
13870 .expect("should have an active item after navigating into the 3rd buffer");
13871 let third_item_id = active_item.item_id();
13872 assert_ne!(
13873 third_item_id, multibuffer_item_id,
13874 "Should navigate into the 3rd buffer and activate it"
13875 );
13876 assert_ne!(third_item_id, first_item_id);
13877 assert_ne!(third_item_id, second_item_id);
13878 assert!(
13879 active_item.is_singleton(cx),
13880 "New active item should be a singleton buffer"
13881 );
13882 assert_eq!(
13883 active_item
13884 .act_as::<Editor>(cx)
13885 .expect("should have navigated into an editor")
13886 .read(cx)
13887 .text(cx),
13888 sample_text_3
13889 );
13890
13891 workspace
13892 .go_back(workspace.active_pane().downgrade(), window, cx)
13893 .detach_and_log_err(cx);
13894 })
13895 .unwrap();
13896 cx.executor().run_until_parked();
13897 workspace
13898 .update(cx, |workspace, _, cx| {
13899 let active_item = workspace
13900 .active_item(cx)
13901 .expect("should have an active item after navigating back from the 3rd buffer");
13902 assert_eq!(
13903 active_item.item_id(),
13904 multibuffer_item_id,
13905 "Should navigate back from the 3rd buffer to the multi buffer"
13906 );
13907 assert!(!active_item.is_singleton(cx));
13908 })
13909 .unwrap();
13910}
13911
13912#[gpui::test]
13913async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13914 init_test(cx, |_| {});
13915
13916 let mut cx = EditorTestContext::new(cx).await;
13917
13918 let diff_base = r#"
13919 use some::mod;
13920
13921 const A: u32 = 42;
13922
13923 fn main() {
13924 println!("hello");
13925
13926 println!("world");
13927 }
13928 "#
13929 .unindent();
13930
13931 cx.set_state(
13932 &r#"
13933 use some::modified;
13934
13935 ˇ
13936 fn main() {
13937 println!("hello there");
13938
13939 println!("around the");
13940 println!("world");
13941 }
13942 "#
13943 .unindent(),
13944 );
13945
13946 cx.set_head_text(&diff_base);
13947 executor.run_until_parked();
13948
13949 cx.update_editor(|editor, window, cx| {
13950 editor.go_to_next_hunk(&GoToHunk, window, cx);
13951 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13952 });
13953 executor.run_until_parked();
13954 cx.assert_state_with_diff(
13955 r#"
13956 use some::modified;
13957
13958
13959 fn main() {
13960 - println!("hello");
13961 + ˇ println!("hello there");
13962
13963 println!("around the");
13964 println!("world");
13965 }
13966 "#
13967 .unindent(),
13968 );
13969
13970 cx.update_editor(|editor, window, cx| {
13971 for _ in 0..2 {
13972 editor.go_to_next_hunk(&GoToHunk, window, cx);
13973 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13974 }
13975 });
13976 executor.run_until_parked();
13977 cx.assert_state_with_diff(
13978 r#"
13979 - use some::mod;
13980 + ˇuse some::modified;
13981
13982
13983 fn main() {
13984 - println!("hello");
13985 + println!("hello there");
13986
13987 + println!("around the");
13988 println!("world");
13989 }
13990 "#
13991 .unindent(),
13992 );
13993
13994 cx.update_editor(|editor, window, cx| {
13995 editor.go_to_next_hunk(&GoToHunk, window, cx);
13996 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13997 });
13998 executor.run_until_parked();
13999 cx.assert_state_with_diff(
14000 r#"
14001 - use some::mod;
14002 + use some::modified;
14003
14004 - const A: u32 = 42;
14005 ˇ
14006 fn main() {
14007 - println!("hello");
14008 + println!("hello there");
14009
14010 + println!("around the");
14011 println!("world");
14012 }
14013 "#
14014 .unindent(),
14015 );
14016
14017 cx.update_editor(|editor, window, cx| {
14018 editor.cancel(&Cancel, window, cx);
14019 });
14020
14021 cx.assert_state_with_diff(
14022 r#"
14023 use some::modified;
14024
14025 ˇ
14026 fn main() {
14027 println!("hello there");
14028
14029 println!("around the");
14030 println!("world");
14031 }
14032 "#
14033 .unindent(),
14034 );
14035}
14036
14037#[gpui::test]
14038async fn test_diff_base_change_with_expanded_diff_hunks(
14039 executor: BackgroundExecutor,
14040 cx: &mut TestAppContext,
14041) {
14042 init_test(cx, |_| {});
14043
14044 let mut cx = EditorTestContext::new(cx).await;
14045
14046 let diff_base = r#"
14047 use some::mod1;
14048 use some::mod2;
14049
14050 const A: u32 = 42;
14051 const B: u32 = 42;
14052 const C: u32 = 42;
14053
14054 fn main() {
14055 println!("hello");
14056
14057 println!("world");
14058 }
14059 "#
14060 .unindent();
14061
14062 cx.set_state(
14063 &r#"
14064 use some::mod2;
14065
14066 const A: u32 = 42;
14067 const C: u32 = 42;
14068
14069 fn main(ˇ) {
14070 //println!("hello");
14071
14072 println!("world");
14073 //
14074 //
14075 }
14076 "#
14077 .unindent(),
14078 );
14079
14080 cx.set_head_text(&diff_base);
14081 executor.run_until_parked();
14082
14083 cx.update_editor(|editor, window, cx| {
14084 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14085 });
14086 executor.run_until_parked();
14087 cx.assert_state_with_diff(
14088 r#"
14089 - use some::mod1;
14090 use some::mod2;
14091
14092 const A: u32 = 42;
14093 - const B: u32 = 42;
14094 const C: u32 = 42;
14095
14096 fn main(ˇ) {
14097 - println!("hello");
14098 + //println!("hello");
14099
14100 println!("world");
14101 + //
14102 + //
14103 }
14104 "#
14105 .unindent(),
14106 );
14107
14108 cx.set_head_text("new diff base!");
14109 executor.run_until_parked();
14110 cx.assert_state_with_diff(
14111 r#"
14112 - new diff base!
14113 + use some::mod2;
14114 +
14115 + const A: u32 = 42;
14116 + const C: u32 = 42;
14117 +
14118 + fn main(ˇ) {
14119 + //println!("hello");
14120 +
14121 + println!("world");
14122 + //
14123 + //
14124 + }
14125 "#
14126 .unindent(),
14127 );
14128}
14129
14130#[gpui::test]
14131async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14132 init_test(cx, |_| {});
14133
14134 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14135 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14136 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14137 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14138 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14139 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14140
14141 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14142 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14143 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14144
14145 let multi_buffer = cx.new(|cx| {
14146 let mut multibuffer = MultiBuffer::new(ReadWrite);
14147 multibuffer.push_excerpts(
14148 buffer_1.clone(),
14149 [
14150 ExcerptRange {
14151 context: Point::new(0, 0)..Point::new(3, 0),
14152 primary: None,
14153 },
14154 ExcerptRange {
14155 context: Point::new(5, 0)..Point::new(7, 0),
14156 primary: None,
14157 },
14158 ExcerptRange {
14159 context: Point::new(9, 0)..Point::new(10, 3),
14160 primary: None,
14161 },
14162 ],
14163 cx,
14164 );
14165 multibuffer.push_excerpts(
14166 buffer_2.clone(),
14167 [
14168 ExcerptRange {
14169 context: Point::new(0, 0)..Point::new(3, 0),
14170 primary: None,
14171 },
14172 ExcerptRange {
14173 context: Point::new(5, 0)..Point::new(7, 0),
14174 primary: None,
14175 },
14176 ExcerptRange {
14177 context: Point::new(9, 0)..Point::new(10, 3),
14178 primary: None,
14179 },
14180 ],
14181 cx,
14182 );
14183 multibuffer.push_excerpts(
14184 buffer_3.clone(),
14185 [
14186 ExcerptRange {
14187 context: Point::new(0, 0)..Point::new(3, 0),
14188 primary: None,
14189 },
14190 ExcerptRange {
14191 context: Point::new(5, 0)..Point::new(7, 0),
14192 primary: None,
14193 },
14194 ExcerptRange {
14195 context: Point::new(9, 0)..Point::new(10, 3),
14196 primary: None,
14197 },
14198 ],
14199 cx,
14200 );
14201 multibuffer
14202 });
14203
14204 let editor =
14205 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14206 editor
14207 .update(cx, |editor, _window, cx| {
14208 for (buffer, diff_base) in [
14209 (buffer_1.clone(), file_1_old),
14210 (buffer_2.clone(), file_2_old),
14211 (buffer_3.clone(), file_3_old),
14212 ] {
14213 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14214 editor
14215 .buffer
14216 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14217 }
14218 })
14219 .unwrap();
14220
14221 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14222 cx.run_until_parked();
14223
14224 cx.assert_editor_state(
14225 &"
14226 ˇaaa
14227 ccc
14228 ddd
14229
14230 ggg
14231 hhh
14232
14233
14234 lll
14235 mmm
14236 NNN
14237
14238 qqq
14239 rrr
14240
14241 uuu
14242 111
14243 222
14244 333
14245
14246 666
14247 777
14248
14249 000
14250 !!!"
14251 .unindent(),
14252 );
14253
14254 cx.update_editor(|editor, window, cx| {
14255 editor.select_all(&SelectAll, window, cx);
14256 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14257 });
14258 cx.executor().run_until_parked();
14259
14260 cx.assert_state_with_diff(
14261 "
14262 «aaa
14263 - bbb
14264 ccc
14265 ddd
14266
14267 ggg
14268 hhh
14269
14270
14271 lll
14272 mmm
14273 - nnn
14274 + NNN
14275
14276 qqq
14277 rrr
14278
14279 uuu
14280 111
14281 222
14282 333
14283
14284 + 666
14285 777
14286
14287 000
14288 !!!ˇ»"
14289 .unindent(),
14290 );
14291}
14292
14293#[gpui::test]
14294async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14295 init_test(cx, |_| {});
14296
14297 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14298 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14299
14300 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14301 let multi_buffer = cx.new(|cx| {
14302 let mut multibuffer = MultiBuffer::new(ReadWrite);
14303 multibuffer.push_excerpts(
14304 buffer.clone(),
14305 [
14306 ExcerptRange {
14307 context: Point::new(0, 0)..Point::new(2, 0),
14308 primary: None,
14309 },
14310 ExcerptRange {
14311 context: Point::new(4, 0)..Point::new(7, 0),
14312 primary: None,
14313 },
14314 ExcerptRange {
14315 context: Point::new(9, 0)..Point::new(10, 0),
14316 primary: None,
14317 },
14318 ],
14319 cx,
14320 );
14321 multibuffer
14322 });
14323
14324 let editor =
14325 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14326 editor
14327 .update(cx, |editor, _window, cx| {
14328 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14329 editor
14330 .buffer
14331 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14332 })
14333 .unwrap();
14334
14335 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14336 cx.run_until_parked();
14337
14338 cx.update_editor(|editor, window, cx| {
14339 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14340 });
14341 cx.executor().run_until_parked();
14342
14343 // When the start of a hunk coincides with the start of its excerpt,
14344 // the hunk is expanded. When the start of a a hunk is earlier than
14345 // the start of its excerpt, the hunk is not expanded.
14346 cx.assert_state_with_diff(
14347 "
14348 ˇaaa
14349 - bbb
14350 + BBB
14351
14352 - ddd
14353 - eee
14354 + DDD
14355 + EEE
14356 fff
14357
14358 iii
14359 "
14360 .unindent(),
14361 );
14362}
14363
14364#[gpui::test]
14365async fn test_edits_around_expanded_insertion_hunks(
14366 executor: BackgroundExecutor,
14367 cx: &mut TestAppContext,
14368) {
14369 init_test(cx, |_| {});
14370
14371 let mut cx = EditorTestContext::new(cx).await;
14372
14373 let diff_base = r#"
14374 use some::mod1;
14375 use some::mod2;
14376
14377 const A: u32 = 42;
14378
14379 fn main() {
14380 println!("hello");
14381
14382 println!("world");
14383 }
14384 "#
14385 .unindent();
14386 executor.run_until_parked();
14387 cx.set_state(
14388 &r#"
14389 use some::mod1;
14390 use some::mod2;
14391
14392 const A: u32 = 42;
14393 const B: u32 = 42;
14394 const C: u32 = 42;
14395 ˇ
14396
14397 fn main() {
14398 println!("hello");
14399
14400 println!("world");
14401 }
14402 "#
14403 .unindent(),
14404 );
14405
14406 cx.set_head_text(&diff_base);
14407 executor.run_until_parked();
14408
14409 cx.update_editor(|editor, window, cx| {
14410 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14411 });
14412 executor.run_until_parked();
14413
14414 cx.assert_state_with_diff(
14415 r#"
14416 use some::mod1;
14417 use some::mod2;
14418
14419 const A: u32 = 42;
14420 + const B: u32 = 42;
14421 + const C: u32 = 42;
14422 + ˇ
14423
14424 fn main() {
14425 println!("hello");
14426
14427 println!("world");
14428 }
14429 "#
14430 .unindent(),
14431 );
14432
14433 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14434 executor.run_until_parked();
14435
14436 cx.assert_state_with_diff(
14437 r#"
14438 use some::mod1;
14439 use some::mod2;
14440
14441 const A: u32 = 42;
14442 + const B: u32 = 42;
14443 + const C: u32 = 42;
14444 + const D: u32 = 42;
14445 + ˇ
14446
14447 fn main() {
14448 println!("hello");
14449
14450 println!("world");
14451 }
14452 "#
14453 .unindent(),
14454 );
14455
14456 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14457 executor.run_until_parked();
14458
14459 cx.assert_state_with_diff(
14460 r#"
14461 use some::mod1;
14462 use some::mod2;
14463
14464 const A: u32 = 42;
14465 + const B: u32 = 42;
14466 + const C: u32 = 42;
14467 + const D: u32 = 42;
14468 + const E: u32 = 42;
14469 + ˇ
14470
14471 fn main() {
14472 println!("hello");
14473
14474 println!("world");
14475 }
14476 "#
14477 .unindent(),
14478 );
14479
14480 cx.update_editor(|editor, window, cx| {
14481 editor.delete_line(&DeleteLine, window, cx);
14482 });
14483 executor.run_until_parked();
14484
14485 cx.assert_state_with_diff(
14486 r#"
14487 use some::mod1;
14488 use some::mod2;
14489
14490 const A: u32 = 42;
14491 + const B: u32 = 42;
14492 + const C: u32 = 42;
14493 + const D: u32 = 42;
14494 + const E: u32 = 42;
14495 ˇ
14496 fn main() {
14497 println!("hello");
14498
14499 println!("world");
14500 }
14501 "#
14502 .unindent(),
14503 );
14504
14505 cx.update_editor(|editor, window, cx| {
14506 editor.move_up(&MoveUp, window, cx);
14507 editor.delete_line(&DeleteLine, window, cx);
14508 editor.move_up(&MoveUp, window, cx);
14509 editor.delete_line(&DeleteLine, window, cx);
14510 editor.move_up(&MoveUp, window, cx);
14511 editor.delete_line(&DeleteLine, window, cx);
14512 });
14513 executor.run_until_parked();
14514 cx.assert_state_with_diff(
14515 r#"
14516 use some::mod1;
14517 use some::mod2;
14518
14519 const A: u32 = 42;
14520 + const B: u32 = 42;
14521 ˇ
14522 fn main() {
14523 println!("hello");
14524
14525 println!("world");
14526 }
14527 "#
14528 .unindent(),
14529 );
14530
14531 cx.update_editor(|editor, window, cx| {
14532 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14533 editor.delete_line(&DeleteLine, window, cx);
14534 });
14535 executor.run_until_parked();
14536 cx.assert_state_with_diff(
14537 r#"
14538 ˇ
14539 fn main() {
14540 println!("hello");
14541
14542 println!("world");
14543 }
14544 "#
14545 .unindent(),
14546 );
14547}
14548
14549#[gpui::test]
14550async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14551 init_test(cx, |_| {});
14552
14553 let mut cx = EditorTestContext::new(cx).await;
14554 cx.set_head_text(indoc! { "
14555 one
14556 two
14557 three
14558 four
14559 five
14560 "
14561 });
14562 cx.set_state(indoc! { "
14563 one
14564 ˇthree
14565 five
14566 "});
14567 cx.run_until_parked();
14568 cx.update_editor(|editor, window, cx| {
14569 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14570 });
14571 cx.assert_state_with_diff(
14572 indoc! { "
14573 one
14574 - two
14575 ˇthree
14576 - four
14577 five
14578 "}
14579 .to_string(),
14580 );
14581 cx.update_editor(|editor, window, cx| {
14582 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14583 });
14584
14585 cx.assert_state_with_diff(
14586 indoc! { "
14587 one
14588 ˇthree
14589 five
14590 "}
14591 .to_string(),
14592 );
14593
14594 cx.set_state(indoc! { "
14595 one
14596 ˇTWO
14597 three
14598 four
14599 five
14600 "});
14601 cx.run_until_parked();
14602 cx.update_editor(|editor, window, cx| {
14603 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14604 });
14605
14606 cx.assert_state_with_diff(
14607 indoc! { "
14608 one
14609 - two
14610 + ˇTWO
14611 three
14612 four
14613 five
14614 "}
14615 .to_string(),
14616 );
14617 cx.update_editor(|editor, window, cx| {
14618 editor.move_up(&Default::default(), window, cx);
14619 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14620 });
14621 cx.assert_state_with_diff(
14622 indoc! { "
14623 one
14624 ˇTWO
14625 three
14626 four
14627 five
14628 "}
14629 .to_string(),
14630 );
14631}
14632
14633#[gpui::test]
14634async fn test_edits_around_expanded_deletion_hunks(
14635 executor: BackgroundExecutor,
14636 cx: &mut TestAppContext,
14637) {
14638 init_test(cx, |_| {});
14639
14640 let mut cx = EditorTestContext::new(cx).await;
14641
14642 let diff_base = r#"
14643 use some::mod1;
14644 use some::mod2;
14645
14646 const A: u32 = 42;
14647 const B: u32 = 42;
14648 const C: u32 = 42;
14649
14650
14651 fn main() {
14652 println!("hello");
14653
14654 println!("world");
14655 }
14656 "#
14657 .unindent();
14658 executor.run_until_parked();
14659 cx.set_state(
14660 &r#"
14661 use some::mod1;
14662 use some::mod2;
14663
14664 ˇconst B: u32 = 42;
14665 const C: u32 = 42;
14666
14667
14668 fn main() {
14669 println!("hello");
14670
14671 println!("world");
14672 }
14673 "#
14674 .unindent(),
14675 );
14676
14677 cx.set_head_text(&diff_base);
14678 executor.run_until_parked();
14679
14680 cx.update_editor(|editor, window, cx| {
14681 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14682 });
14683 executor.run_until_parked();
14684
14685 cx.assert_state_with_diff(
14686 r#"
14687 use some::mod1;
14688 use some::mod2;
14689
14690 - const A: u32 = 42;
14691 ˇconst B: u32 = 42;
14692 const C: u32 = 42;
14693
14694
14695 fn main() {
14696 println!("hello");
14697
14698 println!("world");
14699 }
14700 "#
14701 .unindent(),
14702 );
14703
14704 cx.update_editor(|editor, window, cx| {
14705 editor.delete_line(&DeleteLine, window, cx);
14706 });
14707 executor.run_until_parked();
14708 cx.assert_state_with_diff(
14709 r#"
14710 use some::mod1;
14711 use some::mod2;
14712
14713 - const A: u32 = 42;
14714 - const B: u32 = 42;
14715 ˇconst C: u32 = 42;
14716
14717
14718 fn main() {
14719 println!("hello");
14720
14721 println!("world");
14722 }
14723 "#
14724 .unindent(),
14725 );
14726
14727 cx.update_editor(|editor, window, cx| {
14728 editor.delete_line(&DeleteLine, window, cx);
14729 });
14730 executor.run_until_parked();
14731 cx.assert_state_with_diff(
14732 r#"
14733 use some::mod1;
14734 use some::mod2;
14735
14736 - const A: u32 = 42;
14737 - const B: u32 = 42;
14738 - const C: u32 = 42;
14739 ˇ
14740
14741 fn main() {
14742 println!("hello");
14743
14744 println!("world");
14745 }
14746 "#
14747 .unindent(),
14748 );
14749
14750 cx.update_editor(|editor, window, cx| {
14751 editor.handle_input("replacement", window, cx);
14752 });
14753 executor.run_until_parked();
14754 cx.assert_state_with_diff(
14755 r#"
14756 use some::mod1;
14757 use some::mod2;
14758
14759 - const A: u32 = 42;
14760 - const B: u32 = 42;
14761 - const C: u32 = 42;
14762 -
14763 + replacementˇ
14764
14765 fn main() {
14766 println!("hello");
14767
14768 println!("world");
14769 }
14770 "#
14771 .unindent(),
14772 );
14773}
14774
14775#[gpui::test]
14776async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14777 init_test(cx, |_| {});
14778
14779 let mut cx = EditorTestContext::new(cx).await;
14780
14781 let base_text = r#"
14782 one
14783 two
14784 three
14785 four
14786 five
14787 "#
14788 .unindent();
14789 executor.run_until_parked();
14790 cx.set_state(
14791 &r#"
14792 one
14793 two
14794 fˇour
14795 five
14796 "#
14797 .unindent(),
14798 );
14799
14800 cx.set_head_text(&base_text);
14801 executor.run_until_parked();
14802
14803 cx.update_editor(|editor, window, cx| {
14804 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14805 });
14806 executor.run_until_parked();
14807
14808 cx.assert_state_with_diff(
14809 r#"
14810 one
14811 two
14812 - three
14813 fˇour
14814 five
14815 "#
14816 .unindent(),
14817 );
14818
14819 cx.update_editor(|editor, window, cx| {
14820 editor.backspace(&Backspace, window, cx);
14821 editor.backspace(&Backspace, window, cx);
14822 });
14823 executor.run_until_parked();
14824 cx.assert_state_with_diff(
14825 r#"
14826 one
14827 two
14828 - threeˇ
14829 - four
14830 + our
14831 five
14832 "#
14833 .unindent(),
14834 );
14835}
14836
14837#[gpui::test]
14838async fn test_edit_after_expanded_modification_hunk(
14839 executor: BackgroundExecutor,
14840 cx: &mut TestAppContext,
14841) {
14842 init_test(cx, |_| {});
14843
14844 let mut cx = EditorTestContext::new(cx).await;
14845
14846 let diff_base = r#"
14847 use some::mod1;
14848 use some::mod2;
14849
14850 const A: u32 = 42;
14851 const B: u32 = 42;
14852 const C: u32 = 42;
14853 const D: u32 = 42;
14854
14855
14856 fn main() {
14857 println!("hello");
14858
14859 println!("world");
14860 }"#
14861 .unindent();
14862
14863 cx.set_state(
14864 &r#"
14865 use some::mod1;
14866 use some::mod2;
14867
14868 const A: u32 = 42;
14869 const B: u32 = 42;
14870 const C: u32 = 43ˇ
14871 const D: u32 = 42;
14872
14873
14874 fn main() {
14875 println!("hello");
14876
14877 println!("world");
14878 }"#
14879 .unindent(),
14880 );
14881
14882 cx.set_head_text(&diff_base);
14883 executor.run_until_parked();
14884 cx.update_editor(|editor, window, cx| {
14885 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14886 });
14887 executor.run_until_parked();
14888
14889 cx.assert_state_with_diff(
14890 r#"
14891 use some::mod1;
14892 use some::mod2;
14893
14894 const A: u32 = 42;
14895 const B: u32 = 42;
14896 - const C: u32 = 42;
14897 + const C: u32 = 43ˇ
14898 const D: u32 = 42;
14899
14900
14901 fn main() {
14902 println!("hello");
14903
14904 println!("world");
14905 }"#
14906 .unindent(),
14907 );
14908
14909 cx.update_editor(|editor, window, cx| {
14910 editor.handle_input("\nnew_line\n", window, cx);
14911 });
14912 executor.run_until_parked();
14913
14914 cx.assert_state_with_diff(
14915 r#"
14916 use some::mod1;
14917 use some::mod2;
14918
14919 const A: u32 = 42;
14920 const B: u32 = 42;
14921 - const C: u32 = 42;
14922 + const C: u32 = 43
14923 + new_line
14924 + ˇ
14925 const D: u32 = 42;
14926
14927
14928 fn main() {
14929 println!("hello");
14930
14931 println!("world");
14932 }"#
14933 .unindent(),
14934 );
14935}
14936
14937#[gpui::test]
14938async fn test_stage_and_unstage_added_file_hunk(
14939 executor: BackgroundExecutor,
14940 cx: &mut TestAppContext,
14941) {
14942 init_test(cx, |_| {});
14943
14944 let mut cx = EditorTestContext::new(cx).await;
14945 cx.update_editor(|editor, _, cx| {
14946 editor.set_expand_all_diff_hunks(cx);
14947 });
14948
14949 let working_copy = r#"
14950 ˇfn main() {
14951 println!("hello, world!");
14952 }
14953 "#
14954 .unindent();
14955
14956 cx.set_state(&working_copy);
14957 executor.run_until_parked();
14958
14959 cx.assert_state_with_diff(
14960 r#"
14961 + ˇfn main() {
14962 + println!("hello, world!");
14963 + }
14964 "#
14965 .unindent(),
14966 );
14967 cx.assert_index_text(None);
14968
14969 cx.update_editor(|editor, window, cx| {
14970 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14971 });
14972 executor.run_until_parked();
14973 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14974 cx.assert_state_with_diff(
14975 r#"
14976 + ˇfn main() {
14977 + println!("hello, world!");
14978 + }
14979 "#
14980 .unindent(),
14981 );
14982
14983 cx.update_editor(|editor, window, cx| {
14984 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14985 });
14986 executor.run_until_parked();
14987 cx.assert_index_text(None);
14988}
14989
14990async fn setup_indent_guides_editor(
14991 text: &str,
14992 cx: &mut TestAppContext,
14993) -> (BufferId, EditorTestContext) {
14994 init_test(cx, |_| {});
14995
14996 let mut cx = EditorTestContext::new(cx).await;
14997
14998 let buffer_id = cx.update_editor(|editor, window, cx| {
14999 editor.set_text(text, window, cx);
15000 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15001
15002 buffer_ids[0]
15003 });
15004
15005 (buffer_id, cx)
15006}
15007
15008fn assert_indent_guides(
15009 range: Range<u32>,
15010 expected: Vec<IndentGuide>,
15011 active_indices: Option<Vec<usize>>,
15012 cx: &mut EditorTestContext,
15013) {
15014 let indent_guides = cx.update_editor(|editor, window, cx| {
15015 let snapshot = editor.snapshot(window, cx).display_snapshot;
15016 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15017 editor,
15018 MultiBufferRow(range.start)..MultiBufferRow(range.end),
15019 true,
15020 &snapshot,
15021 cx,
15022 );
15023
15024 indent_guides.sort_by(|a, b| {
15025 a.depth.cmp(&b.depth).then(
15026 a.start_row
15027 .cmp(&b.start_row)
15028 .then(a.end_row.cmp(&b.end_row)),
15029 )
15030 });
15031 indent_guides
15032 });
15033
15034 if let Some(expected) = active_indices {
15035 let active_indices = cx.update_editor(|editor, window, cx| {
15036 let snapshot = editor.snapshot(window, cx).display_snapshot;
15037 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
15038 });
15039
15040 assert_eq!(
15041 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
15042 expected,
15043 "Active indent guide indices do not match"
15044 );
15045 }
15046
15047 assert_eq!(indent_guides, expected, "Indent guides do not match");
15048}
15049
15050fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
15051 IndentGuide {
15052 buffer_id,
15053 start_row: MultiBufferRow(start_row),
15054 end_row: MultiBufferRow(end_row),
15055 depth,
15056 tab_size: 4,
15057 settings: IndentGuideSettings {
15058 enabled: true,
15059 line_width: 1,
15060 active_line_width: 1,
15061 ..Default::default()
15062 },
15063 }
15064}
15065
15066#[gpui::test]
15067async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15068 let (buffer_id, mut cx) = setup_indent_guides_editor(
15069 &"
15070 fn main() {
15071 let a = 1;
15072 }"
15073 .unindent(),
15074 cx,
15075 )
15076 .await;
15077
15078 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15079}
15080
15081#[gpui::test]
15082async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15083 let (buffer_id, mut cx) = setup_indent_guides_editor(
15084 &"
15085 fn main() {
15086 let a = 1;
15087 let b = 2;
15088 }"
15089 .unindent(),
15090 cx,
15091 )
15092 .await;
15093
15094 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15095}
15096
15097#[gpui::test]
15098async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15099 let (buffer_id, mut cx) = setup_indent_guides_editor(
15100 &"
15101 fn main() {
15102 let a = 1;
15103 if a == 3 {
15104 let b = 2;
15105 } else {
15106 let c = 3;
15107 }
15108 }"
15109 .unindent(),
15110 cx,
15111 )
15112 .await;
15113
15114 assert_indent_guides(
15115 0..8,
15116 vec![
15117 indent_guide(buffer_id, 1, 6, 0),
15118 indent_guide(buffer_id, 3, 3, 1),
15119 indent_guide(buffer_id, 5, 5, 1),
15120 ],
15121 None,
15122 &mut cx,
15123 );
15124}
15125
15126#[gpui::test]
15127async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15128 let (buffer_id, mut cx) = setup_indent_guides_editor(
15129 &"
15130 fn main() {
15131 let a = 1;
15132 let b = 2;
15133 let c = 3;
15134 }"
15135 .unindent(),
15136 cx,
15137 )
15138 .await;
15139
15140 assert_indent_guides(
15141 0..5,
15142 vec![
15143 indent_guide(buffer_id, 1, 3, 0),
15144 indent_guide(buffer_id, 2, 2, 1),
15145 ],
15146 None,
15147 &mut cx,
15148 );
15149}
15150
15151#[gpui::test]
15152async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15153 let (buffer_id, mut cx) = setup_indent_guides_editor(
15154 &"
15155 fn main() {
15156 let a = 1;
15157
15158 let c = 3;
15159 }"
15160 .unindent(),
15161 cx,
15162 )
15163 .await;
15164
15165 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15166}
15167
15168#[gpui::test]
15169async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15170 let (buffer_id, mut cx) = setup_indent_guides_editor(
15171 &"
15172 fn main() {
15173 let a = 1;
15174
15175 let c = 3;
15176
15177 if a == 3 {
15178 let b = 2;
15179 } else {
15180 let c = 3;
15181 }
15182 }"
15183 .unindent(),
15184 cx,
15185 )
15186 .await;
15187
15188 assert_indent_guides(
15189 0..11,
15190 vec![
15191 indent_guide(buffer_id, 1, 9, 0),
15192 indent_guide(buffer_id, 6, 6, 1),
15193 indent_guide(buffer_id, 8, 8, 1),
15194 ],
15195 None,
15196 &mut cx,
15197 );
15198}
15199
15200#[gpui::test]
15201async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15202 let (buffer_id, mut cx) = setup_indent_guides_editor(
15203 &"
15204 fn main() {
15205 let a = 1;
15206
15207 let c = 3;
15208
15209 if a == 3 {
15210 let b = 2;
15211 } else {
15212 let c = 3;
15213 }
15214 }"
15215 .unindent(),
15216 cx,
15217 )
15218 .await;
15219
15220 assert_indent_guides(
15221 1..11,
15222 vec![
15223 indent_guide(buffer_id, 1, 9, 0),
15224 indent_guide(buffer_id, 6, 6, 1),
15225 indent_guide(buffer_id, 8, 8, 1),
15226 ],
15227 None,
15228 &mut cx,
15229 );
15230}
15231
15232#[gpui::test]
15233async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15234 let (buffer_id, mut cx) = setup_indent_guides_editor(
15235 &"
15236 fn main() {
15237 let a = 1;
15238
15239 let c = 3;
15240
15241 if a == 3 {
15242 let b = 2;
15243 } else {
15244 let c = 3;
15245 }
15246 }"
15247 .unindent(),
15248 cx,
15249 )
15250 .await;
15251
15252 assert_indent_guides(
15253 1..10,
15254 vec![
15255 indent_guide(buffer_id, 1, 9, 0),
15256 indent_guide(buffer_id, 6, 6, 1),
15257 indent_guide(buffer_id, 8, 8, 1),
15258 ],
15259 None,
15260 &mut cx,
15261 );
15262}
15263
15264#[gpui::test]
15265async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15266 let (buffer_id, mut cx) = setup_indent_guides_editor(
15267 &"
15268 block1
15269 block2
15270 block3
15271 block4
15272 block2
15273 block1
15274 block1"
15275 .unindent(),
15276 cx,
15277 )
15278 .await;
15279
15280 assert_indent_guides(
15281 1..10,
15282 vec![
15283 indent_guide(buffer_id, 1, 4, 0),
15284 indent_guide(buffer_id, 2, 3, 1),
15285 indent_guide(buffer_id, 3, 3, 2),
15286 ],
15287 None,
15288 &mut cx,
15289 );
15290}
15291
15292#[gpui::test]
15293async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15294 let (buffer_id, mut cx) = setup_indent_guides_editor(
15295 &"
15296 block1
15297 block2
15298 block3
15299
15300 block1
15301 block1"
15302 .unindent(),
15303 cx,
15304 )
15305 .await;
15306
15307 assert_indent_guides(
15308 0..6,
15309 vec![
15310 indent_guide(buffer_id, 1, 2, 0),
15311 indent_guide(buffer_id, 2, 2, 1),
15312 ],
15313 None,
15314 &mut cx,
15315 );
15316}
15317
15318#[gpui::test]
15319async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15320 let (buffer_id, mut cx) = setup_indent_guides_editor(
15321 &"
15322 block1
15323
15324
15325
15326 block2
15327 "
15328 .unindent(),
15329 cx,
15330 )
15331 .await;
15332
15333 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15334}
15335
15336#[gpui::test]
15337async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15338 let (buffer_id, mut cx) = setup_indent_guides_editor(
15339 &"
15340 def a:
15341 \tb = 3
15342 \tif True:
15343 \t\tc = 4
15344 \t\td = 5
15345 \tprint(b)
15346 "
15347 .unindent(),
15348 cx,
15349 )
15350 .await;
15351
15352 assert_indent_guides(
15353 0..6,
15354 vec![
15355 indent_guide(buffer_id, 1, 6, 0),
15356 indent_guide(buffer_id, 3, 4, 1),
15357 ],
15358 None,
15359 &mut cx,
15360 );
15361}
15362
15363#[gpui::test]
15364async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15365 let (buffer_id, mut cx) = setup_indent_guides_editor(
15366 &"
15367 fn main() {
15368 let a = 1;
15369 }"
15370 .unindent(),
15371 cx,
15372 )
15373 .await;
15374
15375 cx.update_editor(|editor, window, cx| {
15376 editor.change_selections(None, window, cx, |s| {
15377 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15378 });
15379 });
15380
15381 assert_indent_guides(
15382 0..3,
15383 vec![indent_guide(buffer_id, 1, 1, 0)],
15384 Some(vec![0]),
15385 &mut cx,
15386 );
15387}
15388
15389#[gpui::test]
15390async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15391 let (buffer_id, mut cx) = setup_indent_guides_editor(
15392 &"
15393 fn main() {
15394 if 1 == 2 {
15395 let a = 1;
15396 }
15397 }"
15398 .unindent(),
15399 cx,
15400 )
15401 .await;
15402
15403 cx.update_editor(|editor, window, cx| {
15404 editor.change_selections(None, window, cx, |s| {
15405 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15406 });
15407 });
15408
15409 assert_indent_guides(
15410 0..4,
15411 vec![
15412 indent_guide(buffer_id, 1, 3, 0),
15413 indent_guide(buffer_id, 2, 2, 1),
15414 ],
15415 Some(vec![1]),
15416 &mut cx,
15417 );
15418
15419 cx.update_editor(|editor, window, cx| {
15420 editor.change_selections(None, window, cx, |s| {
15421 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15422 });
15423 });
15424
15425 assert_indent_guides(
15426 0..4,
15427 vec![
15428 indent_guide(buffer_id, 1, 3, 0),
15429 indent_guide(buffer_id, 2, 2, 1),
15430 ],
15431 Some(vec![1]),
15432 &mut cx,
15433 );
15434
15435 cx.update_editor(|editor, window, cx| {
15436 editor.change_selections(None, window, cx, |s| {
15437 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15438 });
15439 });
15440
15441 assert_indent_guides(
15442 0..4,
15443 vec![
15444 indent_guide(buffer_id, 1, 3, 0),
15445 indent_guide(buffer_id, 2, 2, 1),
15446 ],
15447 Some(vec![0]),
15448 &mut cx,
15449 );
15450}
15451
15452#[gpui::test]
15453async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15454 let (buffer_id, mut cx) = setup_indent_guides_editor(
15455 &"
15456 fn main() {
15457 let a = 1;
15458
15459 let b = 2;
15460 }"
15461 .unindent(),
15462 cx,
15463 )
15464 .await;
15465
15466 cx.update_editor(|editor, window, cx| {
15467 editor.change_selections(None, window, cx, |s| {
15468 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15469 });
15470 });
15471
15472 assert_indent_guides(
15473 0..5,
15474 vec![indent_guide(buffer_id, 1, 3, 0)],
15475 Some(vec![0]),
15476 &mut cx,
15477 );
15478}
15479
15480#[gpui::test]
15481async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15482 let (buffer_id, mut cx) = setup_indent_guides_editor(
15483 &"
15484 def m:
15485 a = 1
15486 pass"
15487 .unindent(),
15488 cx,
15489 )
15490 .await;
15491
15492 cx.update_editor(|editor, window, cx| {
15493 editor.change_selections(None, window, cx, |s| {
15494 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15495 });
15496 });
15497
15498 assert_indent_guides(
15499 0..3,
15500 vec![indent_guide(buffer_id, 1, 2, 0)],
15501 Some(vec![0]),
15502 &mut cx,
15503 );
15504}
15505
15506#[gpui::test]
15507async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15508 init_test(cx, |_| {});
15509 let mut cx = EditorTestContext::new(cx).await;
15510 let text = indoc! {
15511 "
15512 impl A {
15513 fn b() {
15514 0;
15515 3;
15516 5;
15517 6;
15518 7;
15519 }
15520 }
15521 "
15522 };
15523 let base_text = indoc! {
15524 "
15525 impl A {
15526 fn b() {
15527 0;
15528 1;
15529 2;
15530 3;
15531 4;
15532 }
15533 fn c() {
15534 5;
15535 6;
15536 7;
15537 }
15538 }
15539 "
15540 };
15541
15542 cx.update_editor(|editor, window, cx| {
15543 editor.set_text(text, window, cx);
15544
15545 editor.buffer().update(cx, |multibuffer, cx| {
15546 let buffer = multibuffer.as_singleton().unwrap();
15547 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15548
15549 multibuffer.set_all_diff_hunks_expanded(cx);
15550 multibuffer.add_diff(diff, cx);
15551
15552 buffer.read(cx).remote_id()
15553 })
15554 });
15555 cx.run_until_parked();
15556
15557 cx.assert_state_with_diff(
15558 indoc! { "
15559 impl A {
15560 fn b() {
15561 0;
15562 - 1;
15563 - 2;
15564 3;
15565 - 4;
15566 - }
15567 - fn c() {
15568 5;
15569 6;
15570 7;
15571 }
15572 }
15573 ˇ"
15574 }
15575 .to_string(),
15576 );
15577
15578 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15579 editor
15580 .snapshot(window, cx)
15581 .buffer_snapshot
15582 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15583 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15584 .collect::<Vec<_>>()
15585 });
15586 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15587 assert_eq!(
15588 actual_guides,
15589 vec![
15590 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15591 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15592 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15593 ]
15594 );
15595}
15596
15597#[gpui::test]
15598async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15599 init_test(cx, |_| {});
15600 let mut cx = EditorTestContext::new(cx).await;
15601
15602 let diff_base = r#"
15603 a
15604 b
15605 c
15606 "#
15607 .unindent();
15608
15609 cx.set_state(
15610 &r#"
15611 ˇA
15612 b
15613 C
15614 "#
15615 .unindent(),
15616 );
15617 cx.set_head_text(&diff_base);
15618 cx.update_editor(|editor, window, cx| {
15619 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15620 });
15621 executor.run_until_parked();
15622
15623 let both_hunks_expanded = r#"
15624 - a
15625 + ˇA
15626 b
15627 - c
15628 + C
15629 "#
15630 .unindent();
15631
15632 cx.assert_state_with_diff(both_hunks_expanded.clone());
15633
15634 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15635 let snapshot = editor.snapshot(window, cx);
15636 let hunks = editor
15637 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15638 .collect::<Vec<_>>();
15639 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15640 let buffer_id = hunks[0].buffer_id;
15641 hunks
15642 .into_iter()
15643 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15644 .collect::<Vec<_>>()
15645 });
15646 assert_eq!(hunk_ranges.len(), 2);
15647
15648 cx.update_editor(|editor, _, cx| {
15649 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15650 });
15651 executor.run_until_parked();
15652
15653 let second_hunk_expanded = r#"
15654 ˇA
15655 b
15656 - c
15657 + C
15658 "#
15659 .unindent();
15660
15661 cx.assert_state_with_diff(second_hunk_expanded);
15662
15663 cx.update_editor(|editor, _, cx| {
15664 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15665 });
15666 executor.run_until_parked();
15667
15668 cx.assert_state_with_diff(both_hunks_expanded.clone());
15669
15670 cx.update_editor(|editor, _, cx| {
15671 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15672 });
15673 executor.run_until_parked();
15674
15675 let first_hunk_expanded = r#"
15676 - a
15677 + ˇA
15678 b
15679 C
15680 "#
15681 .unindent();
15682
15683 cx.assert_state_with_diff(first_hunk_expanded);
15684
15685 cx.update_editor(|editor, _, cx| {
15686 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15687 });
15688 executor.run_until_parked();
15689
15690 cx.assert_state_with_diff(both_hunks_expanded);
15691
15692 cx.set_state(
15693 &r#"
15694 ˇA
15695 b
15696 "#
15697 .unindent(),
15698 );
15699 cx.run_until_parked();
15700
15701 // TODO this cursor position seems bad
15702 cx.assert_state_with_diff(
15703 r#"
15704 - ˇa
15705 + A
15706 b
15707 "#
15708 .unindent(),
15709 );
15710
15711 cx.update_editor(|editor, window, cx| {
15712 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15713 });
15714
15715 cx.assert_state_with_diff(
15716 r#"
15717 - ˇa
15718 + A
15719 b
15720 - c
15721 "#
15722 .unindent(),
15723 );
15724
15725 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15726 let snapshot = editor.snapshot(window, cx);
15727 let hunks = editor
15728 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15729 .collect::<Vec<_>>();
15730 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15731 let buffer_id = hunks[0].buffer_id;
15732 hunks
15733 .into_iter()
15734 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15735 .collect::<Vec<_>>()
15736 });
15737 assert_eq!(hunk_ranges.len(), 2);
15738
15739 cx.update_editor(|editor, _, cx| {
15740 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15741 });
15742 executor.run_until_parked();
15743
15744 cx.assert_state_with_diff(
15745 r#"
15746 - ˇa
15747 + A
15748 b
15749 "#
15750 .unindent(),
15751 );
15752}
15753
15754#[gpui::test]
15755async fn test_toggle_deletion_hunk_at_start_of_file(
15756 executor: BackgroundExecutor,
15757 cx: &mut TestAppContext,
15758) {
15759 init_test(cx, |_| {});
15760 let mut cx = EditorTestContext::new(cx).await;
15761
15762 let diff_base = r#"
15763 a
15764 b
15765 c
15766 "#
15767 .unindent();
15768
15769 cx.set_state(
15770 &r#"
15771 ˇb
15772 c
15773 "#
15774 .unindent(),
15775 );
15776 cx.set_head_text(&diff_base);
15777 cx.update_editor(|editor, window, cx| {
15778 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15779 });
15780 executor.run_until_parked();
15781
15782 let hunk_expanded = r#"
15783 - a
15784 ˇb
15785 c
15786 "#
15787 .unindent();
15788
15789 cx.assert_state_with_diff(hunk_expanded.clone());
15790
15791 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15792 let snapshot = editor.snapshot(window, cx);
15793 let hunks = editor
15794 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15795 .collect::<Vec<_>>();
15796 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15797 let buffer_id = hunks[0].buffer_id;
15798 hunks
15799 .into_iter()
15800 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15801 .collect::<Vec<_>>()
15802 });
15803 assert_eq!(hunk_ranges.len(), 1);
15804
15805 cx.update_editor(|editor, _, cx| {
15806 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15807 });
15808 executor.run_until_parked();
15809
15810 let hunk_collapsed = r#"
15811 ˇb
15812 c
15813 "#
15814 .unindent();
15815
15816 cx.assert_state_with_diff(hunk_collapsed);
15817
15818 cx.update_editor(|editor, _, cx| {
15819 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15820 });
15821 executor.run_until_parked();
15822
15823 cx.assert_state_with_diff(hunk_expanded.clone());
15824}
15825
15826#[gpui::test]
15827async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15828 init_test(cx, |_| {});
15829
15830 let fs = FakeFs::new(cx.executor());
15831 fs.insert_tree(
15832 path!("/test"),
15833 json!({
15834 ".git": {},
15835 "file-1": "ONE\n",
15836 "file-2": "TWO\n",
15837 "file-3": "THREE\n",
15838 }),
15839 )
15840 .await;
15841
15842 fs.set_head_for_repo(
15843 path!("/test/.git").as_ref(),
15844 &[
15845 ("file-1".into(), "one\n".into()),
15846 ("file-2".into(), "two\n".into()),
15847 ("file-3".into(), "three\n".into()),
15848 ],
15849 );
15850
15851 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15852 let mut buffers = vec![];
15853 for i in 1..=3 {
15854 let buffer = project
15855 .update(cx, |project, cx| {
15856 let path = format!(path!("/test/file-{}"), i);
15857 project.open_local_buffer(path, cx)
15858 })
15859 .await
15860 .unwrap();
15861 buffers.push(buffer);
15862 }
15863
15864 let multibuffer = cx.new(|cx| {
15865 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15866 multibuffer.set_all_diff_hunks_expanded(cx);
15867 for buffer in &buffers {
15868 let snapshot = buffer.read(cx).snapshot();
15869 multibuffer.set_excerpts_for_path(
15870 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15871 buffer.clone(),
15872 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15873 DEFAULT_MULTIBUFFER_CONTEXT,
15874 cx,
15875 );
15876 }
15877 multibuffer
15878 });
15879
15880 let editor = cx.add_window(|window, cx| {
15881 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
15882 });
15883 cx.run_until_parked();
15884
15885 let snapshot = editor
15886 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15887 .unwrap();
15888 let hunks = snapshot
15889 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15890 .map(|hunk| match hunk {
15891 DisplayDiffHunk::Unfolded {
15892 display_row_range, ..
15893 } => display_row_range,
15894 DisplayDiffHunk::Folded { .. } => unreachable!(),
15895 })
15896 .collect::<Vec<_>>();
15897 assert_eq!(
15898 hunks,
15899 [
15900 DisplayRow(2)..DisplayRow(4),
15901 DisplayRow(7)..DisplayRow(9),
15902 DisplayRow(12)..DisplayRow(14),
15903 ]
15904 );
15905}
15906
15907#[gpui::test]
15908async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15909 init_test(cx, |_| {});
15910
15911 let mut cx = EditorTestContext::new(cx).await;
15912 cx.set_head_text(indoc! { "
15913 one
15914 two
15915 three
15916 four
15917 five
15918 "
15919 });
15920 cx.set_index_text(indoc! { "
15921 one
15922 two
15923 three
15924 four
15925 five
15926 "
15927 });
15928 cx.set_state(indoc! {"
15929 one
15930 TWO
15931 ˇTHREE
15932 FOUR
15933 five
15934 "});
15935 cx.run_until_parked();
15936 cx.update_editor(|editor, window, cx| {
15937 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15938 });
15939 cx.run_until_parked();
15940 cx.assert_index_text(Some(indoc! {"
15941 one
15942 TWO
15943 THREE
15944 FOUR
15945 five
15946 "}));
15947 cx.set_state(indoc! { "
15948 one
15949 TWO
15950 ˇTHREE-HUNDRED
15951 FOUR
15952 five
15953 "});
15954 cx.run_until_parked();
15955 cx.update_editor(|editor, window, cx| {
15956 let snapshot = editor.snapshot(window, cx);
15957 let hunks = editor
15958 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15959 .collect::<Vec<_>>();
15960 assert_eq!(hunks.len(), 1);
15961 assert_eq!(
15962 hunks[0].status(),
15963 DiffHunkStatus {
15964 kind: DiffHunkStatusKind::Modified,
15965 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15966 }
15967 );
15968
15969 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15970 });
15971 cx.run_until_parked();
15972 cx.assert_index_text(Some(indoc! {"
15973 one
15974 TWO
15975 THREE-HUNDRED
15976 FOUR
15977 five
15978 "}));
15979}
15980
15981#[gpui::test]
15982fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15983 init_test(cx, |_| {});
15984
15985 let editor = cx.add_window(|window, cx| {
15986 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15987 build_editor(buffer, window, cx)
15988 });
15989
15990 let render_args = Arc::new(Mutex::new(None));
15991 let snapshot = editor
15992 .update(cx, |editor, window, cx| {
15993 let snapshot = editor.buffer().read(cx).snapshot(cx);
15994 let range =
15995 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15996
15997 struct RenderArgs {
15998 row: MultiBufferRow,
15999 folded: bool,
16000 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16001 }
16002
16003 let crease = Crease::inline(
16004 range,
16005 FoldPlaceholder::test(),
16006 {
16007 let toggle_callback = render_args.clone();
16008 move |row, folded, callback, _window, _cx| {
16009 *toggle_callback.lock() = Some(RenderArgs {
16010 row,
16011 folded,
16012 callback,
16013 });
16014 div()
16015 }
16016 },
16017 |_row, _folded, _window, _cx| div(),
16018 );
16019
16020 editor.insert_creases(Some(crease), cx);
16021 let snapshot = editor.snapshot(window, cx);
16022 let _div = snapshot.render_crease_toggle(
16023 MultiBufferRow(1),
16024 false,
16025 cx.entity().clone(),
16026 window,
16027 cx,
16028 );
16029 snapshot
16030 })
16031 .unwrap();
16032
16033 let render_args = render_args.lock().take().unwrap();
16034 assert_eq!(render_args.row, MultiBufferRow(1));
16035 assert!(!render_args.folded);
16036 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16037
16038 cx.update_window(*editor, |_, window, cx| {
16039 (render_args.callback)(true, window, cx)
16040 })
16041 .unwrap();
16042 let snapshot = editor
16043 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16044 .unwrap();
16045 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
16046
16047 cx.update_window(*editor, |_, window, cx| {
16048 (render_args.callback)(false, window, cx)
16049 })
16050 .unwrap();
16051 let snapshot = editor
16052 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16053 .unwrap();
16054 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16055}
16056
16057#[gpui::test]
16058async fn test_input_text(cx: &mut TestAppContext) {
16059 init_test(cx, |_| {});
16060 let mut cx = EditorTestContext::new(cx).await;
16061
16062 cx.set_state(
16063 &r#"ˇone
16064 two
16065
16066 three
16067 fourˇ
16068 five
16069
16070 siˇx"#
16071 .unindent(),
16072 );
16073
16074 cx.dispatch_action(HandleInput(String::new()));
16075 cx.assert_editor_state(
16076 &r#"ˇone
16077 two
16078
16079 three
16080 fourˇ
16081 five
16082
16083 siˇx"#
16084 .unindent(),
16085 );
16086
16087 cx.dispatch_action(HandleInput("AAAA".to_string()));
16088 cx.assert_editor_state(
16089 &r#"AAAAˇone
16090 two
16091
16092 three
16093 fourAAAAˇ
16094 five
16095
16096 siAAAAˇx"#
16097 .unindent(),
16098 );
16099}
16100
16101#[gpui::test]
16102async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16103 init_test(cx, |_| {});
16104
16105 let mut cx = EditorTestContext::new(cx).await;
16106 cx.set_state(
16107 r#"let foo = 1;
16108let foo = 2;
16109let foo = 3;
16110let fooˇ = 4;
16111let foo = 5;
16112let foo = 6;
16113let foo = 7;
16114let foo = 8;
16115let foo = 9;
16116let foo = 10;
16117let foo = 11;
16118let foo = 12;
16119let foo = 13;
16120let foo = 14;
16121let foo = 15;"#,
16122 );
16123
16124 cx.update_editor(|e, window, cx| {
16125 assert_eq!(
16126 e.next_scroll_position,
16127 NextScrollCursorCenterTopBottom::Center,
16128 "Default next scroll direction is center",
16129 );
16130
16131 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16132 assert_eq!(
16133 e.next_scroll_position,
16134 NextScrollCursorCenterTopBottom::Top,
16135 "After center, next scroll direction should be top",
16136 );
16137
16138 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16139 assert_eq!(
16140 e.next_scroll_position,
16141 NextScrollCursorCenterTopBottom::Bottom,
16142 "After top, next scroll direction should be bottom",
16143 );
16144
16145 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16146 assert_eq!(
16147 e.next_scroll_position,
16148 NextScrollCursorCenterTopBottom::Center,
16149 "After bottom, scrolling should start over",
16150 );
16151
16152 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16153 assert_eq!(
16154 e.next_scroll_position,
16155 NextScrollCursorCenterTopBottom::Top,
16156 "Scrolling continues if retriggered fast enough"
16157 );
16158 });
16159
16160 cx.executor()
16161 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16162 cx.executor().run_until_parked();
16163 cx.update_editor(|e, _, _| {
16164 assert_eq!(
16165 e.next_scroll_position,
16166 NextScrollCursorCenterTopBottom::Center,
16167 "If scrolling is not triggered fast enough, it should reset"
16168 );
16169 });
16170}
16171
16172#[gpui::test]
16173async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16174 init_test(cx, |_| {});
16175 let mut cx = EditorLspTestContext::new_rust(
16176 lsp::ServerCapabilities {
16177 definition_provider: Some(lsp::OneOf::Left(true)),
16178 references_provider: Some(lsp::OneOf::Left(true)),
16179 ..lsp::ServerCapabilities::default()
16180 },
16181 cx,
16182 )
16183 .await;
16184
16185 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16186 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
16187 move |params, _| async move {
16188 if empty_go_to_definition {
16189 Ok(None)
16190 } else {
16191 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16192 uri: params.text_document_position_params.text_document.uri,
16193 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
16194 })))
16195 }
16196 },
16197 );
16198 let references =
16199 cx.lsp
16200 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
16201 Ok(Some(vec![lsp::Location {
16202 uri: params.text_document_position.text_document.uri,
16203 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16204 }]))
16205 });
16206 (go_to_definition, references)
16207 };
16208
16209 cx.set_state(
16210 &r#"fn one() {
16211 let mut a = ˇtwo();
16212 }
16213
16214 fn two() {}"#
16215 .unindent(),
16216 );
16217 set_up_lsp_handlers(false, &mut cx);
16218 let navigated = cx
16219 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16220 .await
16221 .expect("Failed to navigate to definition");
16222 assert_eq!(
16223 navigated,
16224 Navigated::Yes,
16225 "Should have navigated to definition from the GetDefinition response"
16226 );
16227 cx.assert_editor_state(
16228 &r#"fn one() {
16229 let mut a = two();
16230 }
16231
16232 fn «twoˇ»() {}"#
16233 .unindent(),
16234 );
16235
16236 let editors = cx.update_workspace(|workspace, _, cx| {
16237 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16238 });
16239 cx.update_editor(|_, _, test_editor_cx| {
16240 assert_eq!(
16241 editors.len(),
16242 1,
16243 "Initially, only one, test, editor should be open in the workspace"
16244 );
16245 assert_eq!(
16246 test_editor_cx.entity(),
16247 editors.last().expect("Asserted len is 1").clone()
16248 );
16249 });
16250
16251 set_up_lsp_handlers(true, &mut cx);
16252 let navigated = cx
16253 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16254 .await
16255 .expect("Failed to navigate to lookup references");
16256 assert_eq!(
16257 navigated,
16258 Navigated::Yes,
16259 "Should have navigated to references as a fallback after empty GoToDefinition response"
16260 );
16261 // We should not change the selections in the existing file,
16262 // if opening another milti buffer with the references
16263 cx.assert_editor_state(
16264 &r#"fn one() {
16265 let mut a = two();
16266 }
16267
16268 fn «twoˇ»() {}"#
16269 .unindent(),
16270 );
16271 let editors = cx.update_workspace(|workspace, _, cx| {
16272 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16273 });
16274 cx.update_editor(|_, _, test_editor_cx| {
16275 assert_eq!(
16276 editors.len(),
16277 2,
16278 "After falling back to references search, we open a new editor with the results"
16279 );
16280 let references_fallback_text = editors
16281 .into_iter()
16282 .find(|new_editor| *new_editor != test_editor_cx.entity())
16283 .expect("Should have one non-test editor now")
16284 .read(test_editor_cx)
16285 .text(test_editor_cx);
16286 assert_eq!(
16287 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16288 "Should use the range from the references response and not the GoToDefinition one"
16289 );
16290 });
16291}
16292
16293#[gpui::test]
16294async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16295 init_test(cx, |_| {});
16296
16297 let language = Arc::new(Language::new(
16298 LanguageConfig::default(),
16299 Some(tree_sitter_rust::LANGUAGE.into()),
16300 ));
16301
16302 let text = r#"
16303 #[cfg(test)]
16304 mod tests() {
16305 #[test]
16306 fn runnable_1() {
16307 let a = 1;
16308 }
16309
16310 #[test]
16311 fn runnable_2() {
16312 let a = 1;
16313 let b = 2;
16314 }
16315 }
16316 "#
16317 .unindent();
16318
16319 let fs = FakeFs::new(cx.executor());
16320 fs.insert_file("/file.rs", Default::default()).await;
16321
16322 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16323 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16324 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16325 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16326 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16327
16328 let editor = cx.new_window_entity(|window, cx| {
16329 Editor::new(
16330 EditorMode::Full,
16331 multi_buffer,
16332 Some(project.clone()),
16333 window,
16334 cx,
16335 )
16336 });
16337
16338 editor.update_in(cx, |editor, window, cx| {
16339 let snapshot = editor.buffer().read(cx).snapshot(cx);
16340 editor.tasks.insert(
16341 (buffer.read(cx).remote_id(), 3),
16342 RunnableTasks {
16343 templates: vec![],
16344 offset: snapshot.anchor_before(43),
16345 column: 0,
16346 extra_variables: HashMap::default(),
16347 context_range: BufferOffset(43)..BufferOffset(85),
16348 },
16349 );
16350 editor.tasks.insert(
16351 (buffer.read(cx).remote_id(), 8),
16352 RunnableTasks {
16353 templates: vec![],
16354 offset: snapshot.anchor_before(86),
16355 column: 0,
16356 extra_variables: HashMap::default(),
16357 context_range: BufferOffset(86)..BufferOffset(191),
16358 },
16359 );
16360
16361 // Test finding task when cursor is inside function body
16362 editor.change_selections(None, window, cx, |s| {
16363 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16364 });
16365 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16366 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16367
16368 // Test finding task when cursor is on function name
16369 editor.change_selections(None, window, cx, |s| {
16370 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16371 });
16372 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16373 assert_eq!(row, 8, "Should find task when cursor is on function name");
16374 });
16375}
16376
16377#[gpui::test]
16378async fn test_folding_buffers(cx: &mut TestAppContext) {
16379 init_test(cx, |_| {});
16380
16381 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16382 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16383 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16384
16385 let fs = FakeFs::new(cx.executor());
16386 fs.insert_tree(
16387 path!("/a"),
16388 json!({
16389 "first.rs": sample_text_1,
16390 "second.rs": sample_text_2,
16391 "third.rs": sample_text_3,
16392 }),
16393 )
16394 .await;
16395 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16396 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16397 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16398 let worktree = project.update(cx, |project, cx| {
16399 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16400 assert_eq!(worktrees.len(), 1);
16401 worktrees.pop().unwrap()
16402 });
16403 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16404
16405 let buffer_1 = project
16406 .update(cx, |project, cx| {
16407 project.open_buffer((worktree_id, "first.rs"), cx)
16408 })
16409 .await
16410 .unwrap();
16411 let buffer_2 = project
16412 .update(cx, |project, cx| {
16413 project.open_buffer((worktree_id, "second.rs"), cx)
16414 })
16415 .await
16416 .unwrap();
16417 let buffer_3 = project
16418 .update(cx, |project, cx| {
16419 project.open_buffer((worktree_id, "third.rs"), cx)
16420 })
16421 .await
16422 .unwrap();
16423
16424 let multi_buffer = cx.new(|cx| {
16425 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16426 multi_buffer.push_excerpts(
16427 buffer_1.clone(),
16428 [
16429 ExcerptRange {
16430 context: Point::new(0, 0)..Point::new(3, 0),
16431 primary: None,
16432 },
16433 ExcerptRange {
16434 context: Point::new(5, 0)..Point::new(7, 0),
16435 primary: None,
16436 },
16437 ExcerptRange {
16438 context: Point::new(9, 0)..Point::new(10, 4),
16439 primary: None,
16440 },
16441 ],
16442 cx,
16443 );
16444 multi_buffer.push_excerpts(
16445 buffer_2.clone(),
16446 [
16447 ExcerptRange {
16448 context: Point::new(0, 0)..Point::new(3, 0),
16449 primary: None,
16450 },
16451 ExcerptRange {
16452 context: Point::new(5, 0)..Point::new(7, 0),
16453 primary: None,
16454 },
16455 ExcerptRange {
16456 context: Point::new(9, 0)..Point::new(10, 4),
16457 primary: None,
16458 },
16459 ],
16460 cx,
16461 );
16462 multi_buffer.push_excerpts(
16463 buffer_3.clone(),
16464 [
16465 ExcerptRange {
16466 context: Point::new(0, 0)..Point::new(3, 0),
16467 primary: None,
16468 },
16469 ExcerptRange {
16470 context: Point::new(5, 0)..Point::new(7, 0),
16471 primary: None,
16472 },
16473 ExcerptRange {
16474 context: Point::new(9, 0)..Point::new(10, 4),
16475 primary: None,
16476 },
16477 ],
16478 cx,
16479 );
16480 multi_buffer
16481 });
16482 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16483 Editor::new(
16484 EditorMode::Full,
16485 multi_buffer.clone(),
16486 Some(project.clone()),
16487 window,
16488 cx,
16489 )
16490 });
16491
16492 assert_eq!(
16493 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16494 "\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",
16495 );
16496
16497 multi_buffer_editor.update(cx, |editor, cx| {
16498 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16499 });
16500 assert_eq!(
16501 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16502 "\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",
16503 "After folding the first buffer, its text should not be displayed"
16504 );
16505
16506 multi_buffer_editor.update(cx, |editor, cx| {
16507 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16508 });
16509 assert_eq!(
16510 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16511 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16512 "After folding the second buffer, its text should not be displayed"
16513 );
16514
16515 multi_buffer_editor.update(cx, |editor, cx| {
16516 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16517 });
16518 assert_eq!(
16519 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16520 "\n\n\n\n\n",
16521 "After folding the third buffer, its text should not be displayed"
16522 );
16523
16524 // Emulate selection inside the fold logic, that should work
16525 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16526 editor
16527 .snapshot(window, cx)
16528 .next_line_boundary(Point::new(0, 4));
16529 });
16530
16531 multi_buffer_editor.update(cx, |editor, cx| {
16532 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16533 });
16534 assert_eq!(
16535 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16536 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16537 "After unfolding the second buffer, its text should be displayed"
16538 );
16539
16540 // Typing inside of buffer 1 causes that buffer to be unfolded.
16541 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16542 assert_eq!(
16543 multi_buffer
16544 .read(cx)
16545 .snapshot(cx)
16546 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16547 .collect::<String>(),
16548 "bbbb"
16549 );
16550 editor.change_selections(None, window, cx, |selections| {
16551 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16552 });
16553 editor.handle_input("B", window, cx);
16554 });
16555
16556 assert_eq!(
16557 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16558 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16559 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16560 );
16561
16562 multi_buffer_editor.update(cx, |editor, cx| {
16563 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16564 });
16565 assert_eq!(
16566 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16567 "\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",
16568 "After unfolding the all buffers, all original text should be displayed"
16569 );
16570}
16571
16572#[gpui::test]
16573async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16574 init_test(cx, |_| {});
16575
16576 let sample_text_1 = "1111\n2222\n3333".to_string();
16577 let sample_text_2 = "4444\n5555\n6666".to_string();
16578 let sample_text_3 = "7777\n8888\n9999".to_string();
16579
16580 let fs = FakeFs::new(cx.executor());
16581 fs.insert_tree(
16582 path!("/a"),
16583 json!({
16584 "first.rs": sample_text_1,
16585 "second.rs": sample_text_2,
16586 "third.rs": sample_text_3,
16587 }),
16588 )
16589 .await;
16590 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16591 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16592 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16593 let worktree = project.update(cx, |project, cx| {
16594 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16595 assert_eq!(worktrees.len(), 1);
16596 worktrees.pop().unwrap()
16597 });
16598 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16599
16600 let buffer_1 = project
16601 .update(cx, |project, cx| {
16602 project.open_buffer((worktree_id, "first.rs"), cx)
16603 })
16604 .await
16605 .unwrap();
16606 let buffer_2 = project
16607 .update(cx, |project, cx| {
16608 project.open_buffer((worktree_id, "second.rs"), cx)
16609 })
16610 .await
16611 .unwrap();
16612 let buffer_3 = project
16613 .update(cx, |project, cx| {
16614 project.open_buffer((worktree_id, "third.rs"), cx)
16615 })
16616 .await
16617 .unwrap();
16618
16619 let multi_buffer = cx.new(|cx| {
16620 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16621 multi_buffer.push_excerpts(
16622 buffer_1.clone(),
16623 [ExcerptRange {
16624 context: Point::new(0, 0)..Point::new(3, 0),
16625 primary: None,
16626 }],
16627 cx,
16628 );
16629 multi_buffer.push_excerpts(
16630 buffer_2.clone(),
16631 [ExcerptRange {
16632 context: Point::new(0, 0)..Point::new(3, 0),
16633 primary: None,
16634 }],
16635 cx,
16636 );
16637 multi_buffer.push_excerpts(
16638 buffer_3.clone(),
16639 [ExcerptRange {
16640 context: Point::new(0, 0)..Point::new(3, 0),
16641 primary: None,
16642 }],
16643 cx,
16644 );
16645 multi_buffer
16646 });
16647
16648 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16649 Editor::new(
16650 EditorMode::Full,
16651 multi_buffer,
16652 Some(project.clone()),
16653 window,
16654 cx,
16655 )
16656 });
16657
16658 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16659 assert_eq!(
16660 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16661 full_text,
16662 );
16663
16664 multi_buffer_editor.update(cx, |editor, cx| {
16665 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16666 });
16667 assert_eq!(
16668 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16669 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16670 "After folding the first buffer, its text should not be displayed"
16671 );
16672
16673 multi_buffer_editor.update(cx, |editor, cx| {
16674 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16675 });
16676
16677 assert_eq!(
16678 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16679 "\n\n\n\n\n\n7777\n8888\n9999",
16680 "After folding the second buffer, its text should not be displayed"
16681 );
16682
16683 multi_buffer_editor.update(cx, |editor, cx| {
16684 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16685 });
16686 assert_eq!(
16687 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16688 "\n\n\n\n\n",
16689 "After folding the third buffer, its text should not be displayed"
16690 );
16691
16692 multi_buffer_editor.update(cx, |editor, cx| {
16693 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16694 });
16695 assert_eq!(
16696 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16697 "\n\n\n\n4444\n5555\n6666\n\n",
16698 "After unfolding the second buffer, its text should be displayed"
16699 );
16700
16701 multi_buffer_editor.update(cx, |editor, cx| {
16702 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16703 });
16704 assert_eq!(
16705 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16706 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16707 "After unfolding the first buffer, its text should be displayed"
16708 );
16709
16710 multi_buffer_editor.update(cx, |editor, cx| {
16711 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16712 });
16713 assert_eq!(
16714 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16715 full_text,
16716 "After unfolding all buffers, all original text should be displayed"
16717 );
16718}
16719
16720#[gpui::test]
16721async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16722 init_test(cx, |_| {});
16723
16724 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16725
16726 let fs = FakeFs::new(cx.executor());
16727 fs.insert_tree(
16728 path!("/a"),
16729 json!({
16730 "main.rs": sample_text,
16731 }),
16732 )
16733 .await;
16734 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16735 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16736 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16737 let worktree = project.update(cx, |project, cx| {
16738 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16739 assert_eq!(worktrees.len(), 1);
16740 worktrees.pop().unwrap()
16741 });
16742 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16743
16744 let buffer_1 = project
16745 .update(cx, |project, cx| {
16746 project.open_buffer((worktree_id, "main.rs"), cx)
16747 })
16748 .await
16749 .unwrap();
16750
16751 let multi_buffer = cx.new(|cx| {
16752 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16753 multi_buffer.push_excerpts(
16754 buffer_1.clone(),
16755 [ExcerptRange {
16756 context: Point::new(0, 0)
16757 ..Point::new(
16758 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16759 0,
16760 ),
16761 primary: None,
16762 }],
16763 cx,
16764 );
16765 multi_buffer
16766 });
16767 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16768 Editor::new(
16769 EditorMode::Full,
16770 multi_buffer,
16771 Some(project.clone()),
16772 window,
16773 cx,
16774 )
16775 });
16776
16777 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16778 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16779 enum TestHighlight {}
16780 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16781 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16782 editor.highlight_text::<TestHighlight>(
16783 vec![highlight_range.clone()],
16784 HighlightStyle::color(Hsla::green()),
16785 cx,
16786 );
16787 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16788 });
16789
16790 let full_text = format!("\n\n{sample_text}");
16791 assert_eq!(
16792 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16793 full_text,
16794 );
16795}
16796
16797#[gpui::test]
16798async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16799 init_test(cx, |_| {});
16800 cx.update(|cx| {
16801 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16802 "keymaps/default-linux.json",
16803 cx,
16804 )
16805 .unwrap();
16806 cx.bind_keys(default_key_bindings);
16807 });
16808
16809 let (editor, cx) = cx.add_window_view(|window, cx| {
16810 let multi_buffer = MultiBuffer::build_multi(
16811 [
16812 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16813 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16814 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16815 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16816 ],
16817 cx,
16818 );
16819 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16820
16821 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16822 // fold all but the second buffer, so that we test navigating between two
16823 // adjacent folded buffers, as well as folded buffers at the start and
16824 // end the multibuffer
16825 editor.fold_buffer(buffer_ids[0], cx);
16826 editor.fold_buffer(buffer_ids[2], cx);
16827 editor.fold_buffer(buffer_ids[3], cx);
16828
16829 editor
16830 });
16831 cx.simulate_resize(size(px(1000.), px(1000.)));
16832
16833 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16834 cx.assert_excerpts_with_selections(indoc! {"
16835 [EXCERPT]
16836 ˇ[FOLDED]
16837 [EXCERPT]
16838 a1
16839 b1
16840 [EXCERPT]
16841 [FOLDED]
16842 [EXCERPT]
16843 [FOLDED]
16844 "
16845 });
16846 cx.simulate_keystroke("down");
16847 cx.assert_excerpts_with_selections(indoc! {"
16848 [EXCERPT]
16849 [FOLDED]
16850 [EXCERPT]
16851 ˇa1
16852 b1
16853 [EXCERPT]
16854 [FOLDED]
16855 [EXCERPT]
16856 [FOLDED]
16857 "
16858 });
16859 cx.simulate_keystroke("down");
16860 cx.assert_excerpts_with_selections(indoc! {"
16861 [EXCERPT]
16862 [FOLDED]
16863 [EXCERPT]
16864 a1
16865 ˇb1
16866 [EXCERPT]
16867 [FOLDED]
16868 [EXCERPT]
16869 [FOLDED]
16870 "
16871 });
16872 cx.simulate_keystroke("down");
16873 cx.assert_excerpts_with_selections(indoc! {"
16874 [EXCERPT]
16875 [FOLDED]
16876 [EXCERPT]
16877 a1
16878 b1
16879 ˇ[EXCERPT]
16880 [FOLDED]
16881 [EXCERPT]
16882 [FOLDED]
16883 "
16884 });
16885 cx.simulate_keystroke("down");
16886 cx.assert_excerpts_with_selections(indoc! {"
16887 [EXCERPT]
16888 [FOLDED]
16889 [EXCERPT]
16890 a1
16891 b1
16892 [EXCERPT]
16893 ˇ[FOLDED]
16894 [EXCERPT]
16895 [FOLDED]
16896 "
16897 });
16898 for _ in 0..5 {
16899 cx.simulate_keystroke("down");
16900 cx.assert_excerpts_with_selections(indoc! {"
16901 [EXCERPT]
16902 [FOLDED]
16903 [EXCERPT]
16904 a1
16905 b1
16906 [EXCERPT]
16907 [FOLDED]
16908 [EXCERPT]
16909 ˇ[FOLDED]
16910 "
16911 });
16912 }
16913
16914 cx.simulate_keystroke("up");
16915 cx.assert_excerpts_with_selections(indoc! {"
16916 [EXCERPT]
16917 [FOLDED]
16918 [EXCERPT]
16919 a1
16920 b1
16921 [EXCERPT]
16922 ˇ[FOLDED]
16923 [EXCERPT]
16924 [FOLDED]
16925 "
16926 });
16927 cx.simulate_keystroke("up");
16928 cx.assert_excerpts_with_selections(indoc! {"
16929 [EXCERPT]
16930 [FOLDED]
16931 [EXCERPT]
16932 a1
16933 b1
16934 ˇ[EXCERPT]
16935 [FOLDED]
16936 [EXCERPT]
16937 [FOLDED]
16938 "
16939 });
16940 cx.simulate_keystroke("up");
16941 cx.assert_excerpts_with_selections(indoc! {"
16942 [EXCERPT]
16943 [FOLDED]
16944 [EXCERPT]
16945 a1
16946 ˇb1
16947 [EXCERPT]
16948 [FOLDED]
16949 [EXCERPT]
16950 [FOLDED]
16951 "
16952 });
16953 cx.simulate_keystroke("up");
16954 cx.assert_excerpts_with_selections(indoc! {"
16955 [EXCERPT]
16956 [FOLDED]
16957 [EXCERPT]
16958 ˇa1
16959 b1
16960 [EXCERPT]
16961 [FOLDED]
16962 [EXCERPT]
16963 [FOLDED]
16964 "
16965 });
16966 for _ in 0..5 {
16967 cx.simulate_keystroke("up");
16968 cx.assert_excerpts_with_selections(indoc! {"
16969 [EXCERPT]
16970 ˇ[FOLDED]
16971 [EXCERPT]
16972 a1
16973 b1
16974 [EXCERPT]
16975 [FOLDED]
16976 [EXCERPT]
16977 [FOLDED]
16978 "
16979 });
16980 }
16981}
16982
16983#[gpui::test]
16984async fn test_inline_completion_text(cx: &mut TestAppContext) {
16985 init_test(cx, |_| {});
16986
16987 // Simple insertion
16988 assert_highlighted_edits(
16989 "Hello, world!",
16990 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16991 true,
16992 cx,
16993 |highlighted_edits, cx| {
16994 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16995 assert_eq!(highlighted_edits.highlights.len(), 1);
16996 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16997 assert_eq!(
16998 highlighted_edits.highlights[0].1.background_color,
16999 Some(cx.theme().status().created_background)
17000 );
17001 },
17002 )
17003 .await;
17004
17005 // Replacement
17006 assert_highlighted_edits(
17007 "This is a test.",
17008 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
17009 false,
17010 cx,
17011 |highlighted_edits, cx| {
17012 assert_eq!(highlighted_edits.text, "That is a test.");
17013 assert_eq!(highlighted_edits.highlights.len(), 1);
17014 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
17015 assert_eq!(
17016 highlighted_edits.highlights[0].1.background_color,
17017 Some(cx.theme().status().created_background)
17018 );
17019 },
17020 )
17021 .await;
17022
17023 // Multiple edits
17024 assert_highlighted_edits(
17025 "Hello, world!",
17026 vec![
17027 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
17028 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
17029 ],
17030 false,
17031 cx,
17032 |highlighted_edits, cx| {
17033 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
17034 assert_eq!(highlighted_edits.highlights.len(), 2);
17035 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
17036 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
17037 assert_eq!(
17038 highlighted_edits.highlights[0].1.background_color,
17039 Some(cx.theme().status().created_background)
17040 );
17041 assert_eq!(
17042 highlighted_edits.highlights[1].1.background_color,
17043 Some(cx.theme().status().created_background)
17044 );
17045 },
17046 )
17047 .await;
17048
17049 // Multiple lines with edits
17050 assert_highlighted_edits(
17051 "First line\nSecond line\nThird line\nFourth line",
17052 vec![
17053 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
17054 (
17055 Point::new(2, 0)..Point::new(2, 10),
17056 "New third line".to_string(),
17057 ),
17058 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
17059 ],
17060 false,
17061 cx,
17062 |highlighted_edits, cx| {
17063 assert_eq!(
17064 highlighted_edits.text,
17065 "Second modified\nNew third line\nFourth updated line"
17066 );
17067 assert_eq!(highlighted_edits.highlights.len(), 3);
17068 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17069 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17070 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17071 for highlight in &highlighted_edits.highlights {
17072 assert_eq!(
17073 highlight.1.background_color,
17074 Some(cx.theme().status().created_background)
17075 );
17076 }
17077 },
17078 )
17079 .await;
17080}
17081
17082#[gpui::test]
17083async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17084 init_test(cx, |_| {});
17085
17086 // Deletion
17087 assert_highlighted_edits(
17088 "Hello, world!",
17089 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17090 true,
17091 cx,
17092 |highlighted_edits, cx| {
17093 assert_eq!(highlighted_edits.text, "Hello, world!");
17094 assert_eq!(highlighted_edits.highlights.len(), 1);
17095 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17096 assert_eq!(
17097 highlighted_edits.highlights[0].1.background_color,
17098 Some(cx.theme().status().deleted_background)
17099 );
17100 },
17101 )
17102 .await;
17103
17104 // Insertion
17105 assert_highlighted_edits(
17106 "Hello, world!",
17107 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17108 true,
17109 cx,
17110 |highlighted_edits, cx| {
17111 assert_eq!(highlighted_edits.highlights.len(), 1);
17112 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17113 assert_eq!(
17114 highlighted_edits.highlights[0].1.background_color,
17115 Some(cx.theme().status().created_background)
17116 );
17117 },
17118 )
17119 .await;
17120}
17121
17122async fn assert_highlighted_edits(
17123 text: &str,
17124 edits: Vec<(Range<Point>, String)>,
17125 include_deletions: bool,
17126 cx: &mut TestAppContext,
17127 assertion_fn: impl Fn(HighlightedText, &App),
17128) {
17129 let window = cx.add_window(|window, cx| {
17130 let buffer = MultiBuffer::build_simple(text, cx);
17131 Editor::new(EditorMode::Full, buffer, None, window, cx)
17132 });
17133 let cx = &mut VisualTestContext::from_window(*window, cx);
17134
17135 let (buffer, snapshot) = window
17136 .update(cx, |editor, _window, cx| {
17137 (
17138 editor.buffer().clone(),
17139 editor.buffer().read(cx).snapshot(cx),
17140 )
17141 })
17142 .unwrap();
17143
17144 let edits = edits
17145 .into_iter()
17146 .map(|(range, edit)| {
17147 (
17148 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17149 edit,
17150 )
17151 })
17152 .collect::<Vec<_>>();
17153
17154 let text_anchor_edits = edits
17155 .clone()
17156 .into_iter()
17157 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17158 .collect::<Vec<_>>();
17159
17160 let edit_preview = window
17161 .update(cx, |_, _window, cx| {
17162 buffer
17163 .read(cx)
17164 .as_singleton()
17165 .unwrap()
17166 .read(cx)
17167 .preview_edits(text_anchor_edits.into(), cx)
17168 })
17169 .unwrap()
17170 .await;
17171
17172 cx.update(|_window, cx| {
17173 let highlighted_edits = inline_completion_edit_text(
17174 &snapshot.as_singleton().unwrap().2,
17175 &edits,
17176 &edit_preview,
17177 include_deletions,
17178 cx,
17179 );
17180 assertion_fn(highlighted_edits, cx)
17181 });
17182}
17183
17184#[track_caller]
17185fn assert_breakpoint(
17186 breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
17187 path: &Arc<Path>,
17188 expected: Vec<(u32, BreakpointKind)>,
17189) {
17190 if expected.len() == 0usize {
17191 assert!(!breakpoints.contains_key(path));
17192 } else {
17193 let mut breakpoint = breakpoints
17194 .get(path)
17195 .unwrap()
17196 .into_iter()
17197 .map(|breakpoint| (breakpoint.position, breakpoint.kind.clone()))
17198 .collect::<Vec<_>>();
17199
17200 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17201
17202 assert_eq!(expected, breakpoint);
17203 }
17204}
17205
17206fn add_log_breakpoint_at_cursor(
17207 editor: &mut Editor,
17208 log_message: &str,
17209 window: &mut Window,
17210 cx: &mut Context<Editor>,
17211) {
17212 let (anchor, bp) = editor
17213 .breakpoint_at_cursor_head(window, cx)
17214 .unwrap_or_else(|| {
17215 let cursor_position: Point = editor.selections.newest(cx).head();
17216
17217 let breakpoint_position = editor
17218 .snapshot(window, cx)
17219 .display_snapshot
17220 .buffer_snapshot
17221 .anchor_before(Point::new(cursor_position.row, 0));
17222
17223 let kind = BreakpointKind::Log(Arc::from(log_message));
17224
17225 (breakpoint_position, Breakpoint { kind })
17226 });
17227
17228 editor.edit_breakpoint_at_anchor(
17229 anchor,
17230 bp.kind,
17231 BreakpointEditAction::EditLogMessage(log_message.into()),
17232 cx,
17233 );
17234}
17235
17236#[gpui::test]
17237async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
17238 init_test(cx, |_| {});
17239
17240 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17241 let fs = FakeFs::new(cx.executor());
17242 fs.insert_tree(
17243 path!("/a"),
17244 json!({
17245 "main.rs": sample_text,
17246 }),
17247 )
17248 .await;
17249 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17250 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17251 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17252
17253 let fs = FakeFs::new(cx.executor());
17254 fs.insert_tree(
17255 path!("/a"),
17256 json!({
17257 "main.rs": sample_text,
17258 }),
17259 )
17260 .await;
17261 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17262 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17263 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17264 let worktree_id = workspace
17265 .update(cx, |workspace, _window, cx| {
17266 workspace.project().update(cx, |project, cx| {
17267 project.worktrees(cx).next().unwrap().read(cx).id()
17268 })
17269 })
17270 .unwrap();
17271
17272 let buffer = project
17273 .update(cx, |project, cx| {
17274 project.open_buffer((worktree_id, "main.rs"), cx)
17275 })
17276 .await
17277 .unwrap();
17278
17279 let (editor, cx) = cx.add_window_view(|window, cx| {
17280 Editor::new(
17281 EditorMode::Full,
17282 MultiBuffer::build_from_buffer(buffer, cx),
17283 Some(project.clone()),
17284 window,
17285 cx,
17286 )
17287 });
17288
17289 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17290 let abs_path = project.read_with(cx, |project, cx| {
17291 project
17292 .absolute_path(&project_path, cx)
17293 .map(|path_buf| Arc::from(path_buf.to_owned()))
17294 .unwrap()
17295 });
17296
17297 // assert we can add breakpoint on the first line
17298 editor.update_in(cx, |editor, window, cx| {
17299 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17300 editor.move_to_end(&MoveToEnd, window, cx);
17301 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17302 });
17303
17304 let breakpoints = editor.update(cx, |editor, cx| {
17305 editor
17306 .breakpoint_store()
17307 .as_ref()
17308 .unwrap()
17309 .read(cx)
17310 .all_breakpoints(cx)
17311 .clone()
17312 });
17313
17314 assert_eq!(1, breakpoints.len());
17315 assert_breakpoint(
17316 &breakpoints,
17317 &abs_path,
17318 vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17319 );
17320
17321 editor.update_in(cx, |editor, window, cx| {
17322 editor.move_to_beginning(&MoveToBeginning, window, cx);
17323 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17324 });
17325
17326 let breakpoints = editor.update(cx, |editor, cx| {
17327 editor
17328 .breakpoint_store()
17329 .as_ref()
17330 .unwrap()
17331 .read(cx)
17332 .all_breakpoints(cx)
17333 .clone()
17334 });
17335
17336 assert_eq!(1, breakpoints.len());
17337 assert_breakpoint(&breakpoints, &abs_path, vec![(3, BreakpointKind::Standard)]);
17338
17339 editor.update_in(cx, |editor, window, cx| {
17340 editor.move_to_end(&MoveToEnd, window, cx);
17341 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17342 });
17343
17344 let breakpoints = editor.update(cx, |editor, cx| {
17345 editor
17346 .breakpoint_store()
17347 .as_ref()
17348 .unwrap()
17349 .read(cx)
17350 .all_breakpoints(cx)
17351 .clone()
17352 });
17353
17354 assert_eq!(0, breakpoints.len());
17355 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17356}
17357
17358#[gpui::test]
17359async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
17360 init_test(cx, |_| {});
17361
17362 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17363
17364 let fs = FakeFs::new(cx.executor());
17365 fs.insert_tree(
17366 path!("/a"),
17367 json!({
17368 "main.rs": sample_text,
17369 }),
17370 )
17371 .await;
17372 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17373 let (workspace, cx) =
17374 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
17375
17376 let worktree_id = workspace.update(cx, |workspace, cx| {
17377 workspace.project().update(cx, |project, cx| {
17378 project.worktrees(cx).next().unwrap().read(cx).id()
17379 })
17380 });
17381
17382 let buffer = project
17383 .update(cx, |project, cx| {
17384 project.open_buffer((worktree_id, "main.rs"), cx)
17385 })
17386 .await
17387 .unwrap();
17388
17389 let (editor, cx) = cx.add_window_view(|window, cx| {
17390 Editor::new(
17391 EditorMode::Full,
17392 MultiBuffer::build_from_buffer(buffer, cx),
17393 Some(project.clone()),
17394 window,
17395 cx,
17396 )
17397 });
17398
17399 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17400 let abs_path = project.read_with(cx, |project, cx| {
17401 project
17402 .absolute_path(&project_path, cx)
17403 .map(|path_buf| Arc::from(path_buf.to_owned()))
17404 .unwrap()
17405 });
17406
17407 editor.update_in(cx, |editor, window, cx| {
17408 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17409 });
17410
17411 let breakpoints = editor.update(cx, |editor, cx| {
17412 editor
17413 .breakpoint_store()
17414 .as_ref()
17415 .unwrap()
17416 .read(cx)
17417 .all_breakpoints(cx)
17418 .clone()
17419 });
17420
17421 assert_breakpoint(
17422 &breakpoints,
17423 &abs_path,
17424 vec![(0, BreakpointKind::Log("hello world".into()))],
17425 );
17426
17427 // Removing a log message from a log breakpoint should remove it
17428 editor.update_in(cx, |editor, window, cx| {
17429 add_log_breakpoint_at_cursor(editor, "", window, cx);
17430 });
17431
17432 let breakpoints = editor.update(cx, |editor, cx| {
17433 editor
17434 .breakpoint_store()
17435 .as_ref()
17436 .unwrap()
17437 .read(cx)
17438 .all_breakpoints(cx)
17439 .clone()
17440 });
17441
17442 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17443
17444 editor.update_in(cx, |editor, window, cx| {
17445 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17446 editor.move_to_end(&MoveToEnd, window, cx);
17447 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17448 // Not adding a log message to a standard breakpoint shouldn't remove it
17449 add_log_breakpoint_at_cursor(editor, "", window, cx);
17450 });
17451
17452 let breakpoints = editor.update(cx, |editor, cx| {
17453 editor
17454 .breakpoint_store()
17455 .as_ref()
17456 .unwrap()
17457 .read(cx)
17458 .all_breakpoints(cx)
17459 .clone()
17460 });
17461
17462 assert_breakpoint(
17463 &breakpoints,
17464 &abs_path,
17465 vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17466 );
17467
17468 editor.update_in(cx, |editor, window, cx| {
17469 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17470 });
17471
17472 let breakpoints = editor.update(cx, |editor, cx| {
17473 editor
17474 .breakpoint_store()
17475 .as_ref()
17476 .unwrap()
17477 .read(cx)
17478 .all_breakpoints(cx)
17479 .clone()
17480 });
17481
17482 assert_breakpoint(
17483 &breakpoints,
17484 &abs_path,
17485 vec![
17486 (0, BreakpointKind::Standard),
17487 (3, BreakpointKind::Log("hello world".into())),
17488 ],
17489 );
17490
17491 editor.update_in(cx, |editor, window, cx| {
17492 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
17493 });
17494
17495 let breakpoints = editor.update(cx, |editor, cx| {
17496 editor
17497 .breakpoint_store()
17498 .as_ref()
17499 .unwrap()
17500 .read(cx)
17501 .all_breakpoints(cx)
17502 .clone()
17503 });
17504
17505 assert_breakpoint(
17506 &breakpoints,
17507 &abs_path,
17508 vec![
17509 (0, BreakpointKind::Standard),
17510 (3, BreakpointKind::Log("hello Earth !!".into())),
17511 ],
17512 );
17513}
17514
17515#[gpui::test]
17516async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17517 init_test(cx, |_| {});
17518 let capabilities = lsp::ServerCapabilities {
17519 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17520 prepare_provider: Some(true),
17521 work_done_progress_options: Default::default(),
17522 })),
17523 ..Default::default()
17524 };
17525 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17526
17527 cx.set_state(indoc! {"
17528 struct Fˇoo {}
17529 "});
17530
17531 cx.update_editor(|editor, _, cx| {
17532 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17533 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17534 editor.highlight_background::<DocumentHighlightRead>(
17535 &[highlight_range],
17536 |c| c.editor_document_highlight_read_background,
17537 cx,
17538 );
17539 });
17540
17541 let mut prepare_rename_handler =
17542 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17543 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17544 start: lsp::Position {
17545 line: 0,
17546 character: 7,
17547 },
17548 end: lsp::Position {
17549 line: 0,
17550 character: 10,
17551 },
17552 })))
17553 });
17554 let prepare_rename_task = cx
17555 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17556 .expect("Prepare rename was not started");
17557 prepare_rename_handler.next().await.unwrap();
17558 prepare_rename_task.await.expect("Prepare rename failed");
17559
17560 let mut rename_handler =
17561 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17562 let edit = lsp::TextEdit {
17563 range: lsp::Range {
17564 start: lsp::Position {
17565 line: 0,
17566 character: 7,
17567 },
17568 end: lsp::Position {
17569 line: 0,
17570 character: 10,
17571 },
17572 },
17573 new_text: "FooRenamed".to_string(),
17574 };
17575 Ok(Some(lsp::WorkspaceEdit::new(
17576 // Specify the same edit twice
17577 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17578 )))
17579 });
17580 let rename_task = cx
17581 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17582 .expect("Confirm rename was not started");
17583 rename_handler.next().await.unwrap();
17584 rename_task.await.expect("Confirm rename failed");
17585 cx.run_until_parked();
17586
17587 // Despite two edits, only one is actually applied as those are identical
17588 cx.assert_editor_state(indoc! {"
17589 struct FooRenamedˇ {}
17590 "});
17591}
17592
17593#[gpui::test]
17594async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17595 init_test(cx, |_| {});
17596 // These capabilities indicate that the server does not support prepare rename.
17597 let capabilities = lsp::ServerCapabilities {
17598 rename_provider: Some(lsp::OneOf::Left(true)),
17599 ..Default::default()
17600 };
17601 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17602
17603 cx.set_state(indoc! {"
17604 struct Fˇoo {}
17605 "});
17606
17607 cx.update_editor(|editor, _window, cx| {
17608 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17609 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17610 editor.highlight_background::<DocumentHighlightRead>(
17611 &[highlight_range],
17612 |c| c.editor_document_highlight_read_background,
17613 cx,
17614 );
17615 });
17616
17617 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17618 .expect("Prepare rename was not started")
17619 .await
17620 .expect("Prepare rename failed");
17621
17622 let mut rename_handler =
17623 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17624 let edit = lsp::TextEdit {
17625 range: lsp::Range {
17626 start: lsp::Position {
17627 line: 0,
17628 character: 7,
17629 },
17630 end: lsp::Position {
17631 line: 0,
17632 character: 10,
17633 },
17634 },
17635 new_text: "FooRenamed".to_string(),
17636 };
17637 Ok(Some(lsp::WorkspaceEdit::new(
17638 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17639 )))
17640 });
17641 let rename_task = cx
17642 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17643 .expect("Confirm rename was not started");
17644 rename_handler.next().await.unwrap();
17645 rename_task.await.expect("Confirm rename failed");
17646 cx.run_until_parked();
17647
17648 // Correct range is renamed, as `surrounding_word` is used to find it.
17649 cx.assert_editor_state(indoc! {"
17650 struct FooRenamedˇ {}
17651 "});
17652}
17653
17654#[gpui::test]
17655async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17656 init_test(cx, |_| {});
17657 let mut cx = EditorTestContext::new(cx).await;
17658
17659 let language = Arc::new(
17660 Language::new(
17661 LanguageConfig::default(),
17662 Some(tree_sitter_html::LANGUAGE.into()),
17663 )
17664 .with_brackets_query(
17665 r#"
17666 ("<" @open "/>" @close)
17667 ("</" @open ">" @close)
17668 ("<" @open ">" @close)
17669 ("\"" @open "\"" @close)
17670 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17671 "#,
17672 )
17673 .unwrap(),
17674 );
17675 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17676
17677 cx.set_state(indoc! {"
17678 <span>ˇ</span>
17679 "});
17680 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17681 cx.assert_editor_state(indoc! {"
17682 <span>
17683 ˇ
17684 </span>
17685 "});
17686
17687 cx.set_state(indoc! {"
17688 <span><span></span>ˇ</span>
17689 "});
17690 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17691 cx.assert_editor_state(indoc! {"
17692 <span><span></span>
17693 ˇ</span>
17694 "});
17695
17696 cx.set_state(indoc! {"
17697 <span>ˇ
17698 </span>
17699 "});
17700 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17701 cx.assert_editor_state(indoc! {"
17702 <span>
17703 ˇ
17704 </span>
17705 "});
17706}
17707
17708#[gpui::test(iterations = 10)]
17709async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
17710 init_test(cx, |_| {});
17711
17712 let fs = FakeFs::new(cx.executor());
17713 fs.insert_tree(
17714 path!("/dir"),
17715 json!({
17716 "a.ts": "a",
17717 }),
17718 )
17719 .await;
17720
17721 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
17722 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17723 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17724
17725 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17726 language_registry.add(Arc::new(Language::new(
17727 LanguageConfig {
17728 name: "TypeScript".into(),
17729 matcher: LanguageMatcher {
17730 path_suffixes: vec!["ts".to_string()],
17731 ..Default::default()
17732 },
17733 ..Default::default()
17734 },
17735 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17736 )));
17737 let mut fake_language_servers = language_registry.register_fake_lsp(
17738 "TypeScript",
17739 FakeLspAdapter {
17740 capabilities: lsp::ServerCapabilities {
17741 code_lens_provider: Some(lsp::CodeLensOptions {
17742 resolve_provider: Some(true),
17743 }),
17744 execute_command_provider: Some(lsp::ExecuteCommandOptions {
17745 commands: vec!["_the/command".to_string()],
17746 ..lsp::ExecuteCommandOptions::default()
17747 }),
17748 ..lsp::ServerCapabilities::default()
17749 },
17750 ..FakeLspAdapter::default()
17751 },
17752 );
17753
17754 let (buffer, _handle) = project
17755 .update(cx, |p, cx| {
17756 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
17757 })
17758 .await
17759 .unwrap();
17760 cx.executor().run_until_parked();
17761
17762 let fake_server = fake_language_servers.next().await.unwrap();
17763
17764 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
17765 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
17766 drop(buffer_snapshot);
17767 let actions = cx
17768 .update_window(*workspace, |_, window, cx| {
17769 project.code_actions(&buffer, anchor..anchor, window, cx)
17770 })
17771 .unwrap();
17772
17773 fake_server
17774 .handle_request::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
17775 Ok(Some(vec![
17776 lsp::CodeLens {
17777 range: lsp::Range::default(),
17778 command: Some(lsp::Command {
17779 title: "Code lens command".to_owned(),
17780 command: "_the/command".to_owned(),
17781 arguments: None,
17782 }),
17783 data: None,
17784 },
17785 lsp::CodeLens {
17786 range: lsp::Range::default(),
17787 command: Some(lsp::Command {
17788 title: "Command not in capabilities".to_owned(),
17789 command: "not in capabilities".to_owned(),
17790 arguments: None,
17791 }),
17792 data: None,
17793 },
17794 lsp::CodeLens {
17795 range: lsp::Range {
17796 start: lsp::Position {
17797 line: 1,
17798 character: 1,
17799 },
17800 end: lsp::Position {
17801 line: 1,
17802 character: 1,
17803 },
17804 },
17805 command: Some(lsp::Command {
17806 title: "Command not in range".to_owned(),
17807 command: "_the/command".to_owned(),
17808 arguments: None,
17809 }),
17810 data: None,
17811 },
17812 ]))
17813 })
17814 .next()
17815 .await;
17816
17817 let actions = actions.await.unwrap();
17818 assert_eq!(
17819 actions.len(),
17820 1,
17821 "Should have only one valid action for the 0..0 range"
17822 );
17823 let action = actions[0].clone();
17824 let apply = project.update(cx, |project, cx| {
17825 project.apply_code_action(buffer.clone(), action, true, cx)
17826 });
17827
17828 // Resolving the code action does not populate its edits. In absence of
17829 // edits, we must execute the given command.
17830 fake_server.handle_request::<lsp::request::CodeLensResolve, _, _>(|mut lens, _| async move {
17831 let lens_command = lens.command.as_mut().expect("should have a command");
17832 assert_eq!(lens_command.title, "Code lens command");
17833 lens_command.arguments = Some(vec![json!("the-argument")]);
17834 Ok(lens)
17835 });
17836
17837 // While executing the command, the language server sends the editor
17838 // a `workspaceEdit` request.
17839 fake_server
17840 .handle_request::<lsp::request::ExecuteCommand, _, _>({
17841 let fake = fake_server.clone();
17842 move |params, _| {
17843 assert_eq!(params.command, "_the/command");
17844 let fake = fake.clone();
17845 async move {
17846 fake.server
17847 .request::<lsp::request::ApplyWorkspaceEdit>(
17848 lsp::ApplyWorkspaceEditParams {
17849 label: None,
17850 edit: lsp::WorkspaceEdit {
17851 changes: Some(
17852 [(
17853 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
17854 vec![lsp::TextEdit {
17855 range: lsp::Range::new(
17856 lsp::Position::new(0, 0),
17857 lsp::Position::new(0, 0),
17858 ),
17859 new_text: "X".into(),
17860 }],
17861 )]
17862 .into_iter()
17863 .collect(),
17864 ),
17865 ..Default::default()
17866 },
17867 },
17868 )
17869 .await
17870 .unwrap();
17871 Ok(Some(json!(null)))
17872 }
17873 }
17874 })
17875 .next()
17876 .await;
17877
17878 // Applying the code lens command returns a project transaction containing the edits
17879 // sent by the language server in its `workspaceEdit` request.
17880 let transaction = apply.await.unwrap();
17881 assert!(transaction.0.contains_key(&buffer));
17882 buffer.update(cx, |buffer, cx| {
17883 assert_eq!(buffer.text(), "Xa");
17884 buffer.undo(cx);
17885 assert_eq!(buffer.text(), "a");
17886 });
17887}
17888
17889mod autoclose_tags {
17890 use super::*;
17891 use language::language_settings::JsxTagAutoCloseSettings;
17892 use languages::language;
17893
17894 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17895 init_test(cx, |settings| {
17896 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17897 });
17898
17899 let mut cx = EditorTestContext::new(cx).await;
17900 cx.update_buffer(|buffer, cx| {
17901 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17902
17903 buffer.set_language(Some(language), cx)
17904 });
17905
17906 cx
17907 }
17908
17909 macro_rules! check {
17910 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17911 #[gpui::test]
17912 async fn $name(cx: &mut TestAppContext) {
17913 let mut cx = test_setup(cx).await;
17914 cx.set_state($initial);
17915 cx.run_until_parked();
17916
17917 cx.update_editor(|editor, window, cx| {
17918 editor.handle_input($input, window, cx);
17919 });
17920 cx.run_until_parked();
17921 cx.assert_editor_state($expected);
17922 }
17923 };
17924 }
17925
17926 check!(
17927 test_basic,
17928 "<divˇ" + ">" => "<div>ˇ</div>"
17929 );
17930
17931 check!(
17932 test_basic_nested,
17933 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17934 );
17935
17936 check!(
17937 test_basic_ignore_already_closed,
17938 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17939 );
17940
17941 check!(
17942 test_doesnt_autoclose_closing_tag,
17943 "</divˇ" + ">" => "</div>ˇ"
17944 );
17945
17946 check!(
17947 test_jsx_attr,
17948 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17949 );
17950
17951 check!(
17952 test_ignores_closing_tags_in_expr_block,
17953 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17954 );
17955
17956 check!(
17957 test_doesnt_autoclose_on_gt_in_expr,
17958 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17959 );
17960
17961 check!(
17962 test_ignores_closing_tags_with_different_tag_names,
17963 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17964 );
17965
17966 check!(
17967 test_autocloses_in_jsx_expression,
17968 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17969 );
17970
17971 check!(
17972 test_doesnt_autoclose_already_closed_in_jsx_expression,
17973 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17974 );
17975
17976 check!(
17977 test_autocloses_fragment,
17978 "<ˇ" + ">" => "<>ˇ</>"
17979 );
17980
17981 check!(
17982 test_does_not_include_type_argument_in_autoclose_tag_name,
17983 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17984 );
17985
17986 check!(
17987 test_does_not_autoclose_doctype,
17988 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17989 );
17990
17991 check!(
17992 test_does_not_autoclose_comment,
17993 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17994 );
17995
17996 check!(
17997 test_multi_cursor_autoclose_same_tag,
17998 r#"
17999 <divˇ
18000 <divˇ
18001 "#
18002 + ">" =>
18003 r#"
18004 <div>ˇ</div>
18005 <div>ˇ</div>
18006 "#
18007 );
18008
18009 check!(
18010 test_multi_cursor_autoclose_different_tags,
18011 r#"
18012 <divˇ
18013 <spanˇ
18014 "#
18015 + ">" =>
18016 r#"
18017 <div>ˇ</div>
18018 <span>ˇ</span>
18019 "#
18020 );
18021
18022 check!(
18023 test_multi_cursor_autoclose_some_dont_autoclose_others,
18024 r#"
18025 <divˇ
18026 <div /ˇ
18027 <spanˇ</span>
18028 <!DOCTYPE htmlˇ
18029 </headˇ
18030 <Component<T>ˇ
18031 ˇ
18032 "#
18033 + ">" =>
18034 r#"
18035 <div>ˇ</div>
18036 <div />ˇ
18037 <span>ˇ</span>
18038 <!DOCTYPE html>ˇ
18039 </head>ˇ
18040 <Component<T>>ˇ</Component>
18041 >ˇ
18042 "#
18043 );
18044
18045 check!(
18046 test_doesnt_mess_up_trailing_text,
18047 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
18048 );
18049
18050 #[gpui::test]
18051 async fn test_multibuffer(cx: &mut TestAppContext) {
18052 init_test(cx, |settings| {
18053 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
18054 });
18055
18056 let buffer_a = cx.new(|cx| {
18057 let mut buf = language::Buffer::local("<div", cx);
18058 buf.set_language(
18059 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18060 cx,
18061 );
18062 buf
18063 });
18064 let buffer_b = cx.new(|cx| {
18065 let mut buf = language::Buffer::local("<pre", cx);
18066 buf.set_language(
18067 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18068 cx,
18069 );
18070 buf
18071 });
18072 let buffer_c = cx.new(|cx| {
18073 let buf = language::Buffer::local("<span", cx);
18074 buf
18075 });
18076 let buffer = cx.new(|cx| {
18077 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
18078 buf.push_excerpts(
18079 buffer_a,
18080 [ExcerptRange {
18081 context: text::Anchor::MIN..text::Anchor::MAX,
18082 primary: None,
18083 }],
18084 cx,
18085 );
18086 buf.push_excerpts(
18087 buffer_b,
18088 [ExcerptRange {
18089 context: text::Anchor::MIN..text::Anchor::MAX,
18090 primary: None,
18091 }],
18092 cx,
18093 );
18094 buf.push_excerpts(
18095 buffer_c,
18096 [ExcerptRange {
18097 context: text::Anchor::MIN..text::Anchor::MAX,
18098 primary: None,
18099 }],
18100 cx,
18101 );
18102 buf
18103 });
18104 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18105
18106 let mut cx = EditorTestContext::for_editor(editor, cx).await;
18107
18108 cx.update_editor(|editor, window, cx| {
18109 editor.change_selections(None, window, cx, |selections| {
18110 selections.select(vec![
18111 Selection::from_offset(4),
18112 Selection::from_offset(9),
18113 Selection::from_offset(15),
18114 ])
18115 })
18116 });
18117 cx.run_until_parked();
18118
18119 cx.update_editor(|editor, window, cx| {
18120 editor.handle_input(">", window, cx);
18121 });
18122 cx.run_until_parked();
18123
18124 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
18125 }
18126}
18127
18128fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
18129 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
18130 point..point
18131}
18132
18133fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
18134 let (text, ranges) = marked_text_ranges(marked_text, true);
18135 assert_eq!(editor.text(cx), text);
18136 assert_eq!(
18137 editor.selections.ranges(cx),
18138 ranges,
18139 "Assert selections are {}",
18140 marked_text
18141 );
18142}
18143
18144pub fn handle_signature_help_request(
18145 cx: &mut EditorLspTestContext,
18146 mocked_response: lsp::SignatureHelp,
18147) -> impl Future<Output = ()> {
18148 let mut request =
18149 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
18150 let mocked_response = mocked_response.clone();
18151 async move { Ok(Some(mocked_response)) }
18152 });
18153
18154 async move {
18155 request.next().await;
18156 }
18157}
18158
18159/// Handle completion request passing a marked string specifying where the completion
18160/// should be triggered from using '|' character, what range should be replaced, and what completions
18161/// should be returned using '<' and '>' to delimit the range
18162pub fn handle_completion_request(
18163 cx: &mut EditorLspTestContext,
18164 marked_string: &str,
18165 completions: Vec<&'static str>,
18166 counter: Arc<AtomicUsize>,
18167) -> impl Future<Output = ()> {
18168 let complete_from_marker: TextRangeMarker = '|'.into();
18169 let replace_range_marker: TextRangeMarker = ('<', '>').into();
18170 let (_, mut marked_ranges) = marked_text_ranges_by(
18171 marked_string,
18172 vec![complete_from_marker.clone(), replace_range_marker.clone()],
18173 );
18174
18175 let complete_from_position =
18176 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
18177 let replace_range =
18178 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
18179
18180 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
18181 let completions = completions.clone();
18182 counter.fetch_add(1, atomic::Ordering::Release);
18183 async move {
18184 assert_eq!(params.text_document_position.text_document.uri, url.clone());
18185 assert_eq!(
18186 params.text_document_position.position,
18187 complete_from_position
18188 );
18189 Ok(Some(lsp::CompletionResponse::Array(
18190 completions
18191 .iter()
18192 .map(|completion_text| lsp::CompletionItem {
18193 label: completion_text.to_string(),
18194 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18195 range: replace_range,
18196 new_text: completion_text.to_string(),
18197 })),
18198 ..Default::default()
18199 })
18200 .collect(),
18201 )))
18202 }
18203 });
18204
18205 async move {
18206 request.next().await;
18207 }
18208}
18209
18210fn handle_resolve_completion_request(
18211 cx: &mut EditorLspTestContext,
18212 edits: Option<Vec<(&'static str, &'static str)>>,
18213) -> impl Future<Output = ()> {
18214 let edits = edits.map(|edits| {
18215 edits
18216 .iter()
18217 .map(|(marked_string, new_text)| {
18218 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
18219 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
18220 lsp::TextEdit::new(replace_range, new_text.to_string())
18221 })
18222 .collect::<Vec<_>>()
18223 });
18224
18225 let mut request =
18226 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18227 let edits = edits.clone();
18228 async move {
18229 Ok(lsp::CompletionItem {
18230 additional_text_edits: edits,
18231 ..Default::default()
18232 })
18233 }
18234 });
18235
18236 async move {
18237 request.next().await;
18238 }
18239}
18240
18241pub(crate) fn update_test_language_settings(
18242 cx: &mut TestAppContext,
18243 f: impl Fn(&mut AllLanguageSettingsContent),
18244) {
18245 cx.update(|cx| {
18246 SettingsStore::update_global(cx, |store, cx| {
18247 store.update_user_settings::<AllLanguageSettings>(cx, f);
18248 });
18249 });
18250}
18251
18252pub(crate) fn update_test_project_settings(
18253 cx: &mut TestAppContext,
18254 f: impl Fn(&mut ProjectSettings),
18255) {
18256 cx.update(|cx| {
18257 SettingsStore::update_global(cx, |store, cx| {
18258 store.update_user_settings::<ProjectSettings>(cx, f);
18259 });
18260 });
18261}
18262
18263pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
18264 cx.update(|cx| {
18265 assets::Assets.load_test_fonts(cx);
18266 let store = SettingsStore::test(cx);
18267 cx.set_global(store);
18268 theme::init(theme::LoadThemes::JustBase, cx);
18269 release_channel::init(SemanticVersion::default(), cx);
18270 client::init_settings(cx);
18271 language::init(cx);
18272 Project::init_settings(cx);
18273 workspace::init_settings(cx);
18274 crate::init(cx);
18275 });
18276
18277 update_test_language_settings(cx, f);
18278}
18279
18280#[track_caller]
18281fn assert_hunk_revert(
18282 not_reverted_text_with_selections: &str,
18283 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
18284 expected_reverted_text_with_selections: &str,
18285 base_text: &str,
18286 cx: &mut EditorLspTestContext,
18287) {
18288 cx.set_state(not_reverted_text_with_selections);
18289 cx.set_head_text(base_text);
18290 cx.executor().run_until_parked();
18291
18292 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
18293 let snapshot = editor.snapshot(window, cx);
18294 let reverted_hunk_statuses = snapshot
18295 .buffer_snapshot
18296 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
18297 .map(|hunk| hunk.status().kind)
18298 .collect::<Vec<_>>();
18299
18300 editor.git_restore(&Default::default(), window, cx);
18301 reverted_hunk_statuses
18302 });
18303 cx.executor().run_until_parked();
18304 cx.assert_editor_state(expected_reverted_text_with_selections);
18305 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
18306}