1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
20 LanguageSettingsContent, PrettierSettings,
21 },
22 BracketPairConfig,
23 Capability::ReadWrite,
24 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
25 Override, Point,
26};
27use language_settings::{Formatter, FormatterList, IndentGuideSettings};
28use multi_buffer::{IndentGuide, PathKey};
29use parking_lot::Mutex;
30use pretty_assertions::{assert_eq, assert_ne};
31use project::{
32 debugger::breakpoint_store::{BreakpointKind, SerializedBreakpoint},
33 project_settings::{LspSettings, ProjectSettings},
34 FakeFs,
35};
36use serde_json::{self, json};
37use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
38use std::{
39 iter,
40 sync::atomic::{self, AtomicUsize},
41};
42use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
43use text::ToPoint as _;
44use unindent::Unindent;
45use util::{
46 assert_set_eq, path,
47 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
48 uri,
49};
50use workspace::{
51 item::{FollowEvent, FollowableItem, Item, ItemHandle},
52 NavigationEntry, ViewId,
53};
54
55#[gpui::test]
56fn test_edit_events(cx: &mut TestAppContext) {
57 init_test(cx, |_| {});
58
59 let buffer = cx.new(|cx| {
60 let mut buffer = language::Buffer::local("123456", cx);
61 buffer.set_group_interval(Duration::from_secs(1));
62 buffer
63 });
64
65 let events = Rc::new(RefCell::new(Vec::new()));
66 let editor1 = cx.add_window({
67 let events = events.clone();
68 |window, cx| {
69 let entity = cx.entity().clone();
70 cx.subscribe_in(
71 &entity,
72 window,
73 move |_, _, event: &EditorEvent, _, _| match event {
74 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
75 EditorEvent::BufferEdited => {
76 events.borrow_mut().push(("editor1", "buffer edited"))
77 }
78 _ => {}
79 },
80 )
81 .detach();
82 Editor::for_buffer(buffer.clone(), None, window, cx)
83 }
84 });
85
86 let editor2 = cx.add_window({
87 let events = events.clone();
88 |window, cx| {
89 cx.subscribe_in(
90 &cx.entity().clone(),
91 window,
92 move |_, _, event: &EditorEvent, _, _| match event {
93 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
94 EditorEvent::BufferEdited => {
95 events.borrow_mut().push(("editor2", "buffer edited"))
96 }
97 _ => {}
98 },
99 )
100 .detach();
101 Editor::for_buffer(buffer.clone(), None, window, cx)
102 }
103 });
104
105 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
106
107 // Mutating editor 1 will emit an `Edited` event only for that editor.
108 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
109 assert_eq!(
110 mem::take(&mut *events.borrow_mut()),
111 [
112 ("editor1", "edited"),
113 ("editor1", "buffer edited"),
114 ("editor2", "buffer edited"),
115 ]
116 );
117
118 // Mutating editor 2 will emit an `Edited` event only for that editor.
119 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
120 assert_eq!(
121 mem::take(&mut *events.borrow_mut()),
122 [
123 ("editor2", "edited"),
124 ("editor1", "buffer edited"),
125 ("editor2", "buffer edited"),
126 ]
127 );
128
129 // Undoing on editor 1 will emit an `Edited` event only for that editor.
130 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
131 assert_eq!(
132 mem::take(&mut *events.borrow_mut()),
133 [
134 ("editor1", "edited"),
135 ("editor1", "buffer edited"),
136 ("editor2", "buffer edited"),
137 ]
138 );
139
140 // Redoing on editor 1 will emit an `Edited` event only for that editor.
141 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
142 assert_eq!(
143 mem::take(&mut *events.borrow_mut()),
144 [
145 ("editor1", "edited"),
146 ("editor1", "buffer edited"),
147 ("editor2", "buffer edited"),
148 ]
149 );
150
151 // Undoing on editor 2 will emit an `Edited` event only for that editor.
152 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
153 assert_eq!(
154 mem::take(&mut *events.borrow_mut()),
155 [
156 ("editor2", "edited"),
157 ("editor1", "buffer edited"),
158 ("editor2", "buffer edited"),
159 ]
160 );
161
162 // Redoing on editor 2 will emit an `Edited` event only for that editor.
163 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
164 assert_eq!(
165 mem::take(&mut *events.borrow_mut()),
166 [
167 ("editor2", "edited"),
168 ("editor1", "buffer edited"),
169 ("editor2", "buffer edited"),
170 ]
171 );
172
173 // No event is emitted when the mutation is a no-op.
174 _ = editor2.update(cx, |editor, window, cx| {
175 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
176
177 editor.backspace(&Backspace, window, cx);
178 });
179 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
180}
181
182#[gpui::test]
183fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
184 init_test(cx, |_| {});
185
186 let mut now = Instant::now();
187 let group_interval = Duration::from_millis(1);
188 let buffer = cx.new(|cx| {
189 let mut buf = language::Buffer::local("123456", cx);
190 buf.set_group_interval(group_interval);
191 buf
192 });
193 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
194 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
195
196 _ = editor.update(cx, |editor, window, cx| {
197 editor.start_transaction_at(now, window, cx);
198 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
199
200 editor.insert("cd", window, cx);
201 editor.end_transaction_at(now, cx);
202 assert_eq!(editor.text(cx), "12cd56");
203 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
204
205 editor.start_transaction_at(now, window, cx);
206 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
207 editor.insert("e", window, cx);
208 editor.end_transaction_at(now, cx);
209 assert_eq!(editor.text(cx), "12cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
211
212 now += group_interval + Duration::from_millis(1);
213 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
214
215 // Simulate an edit in another editor
216 buffer.update(cx, |buffer, cx| {
217 buffer.start_transaction_at(now, cx);
218 buffer.edit([(0..1, "a")], None, cx);
219 buffer.edit([(1..1, "b")], None, cx);
220 buffer.end_transaction_at(now, cx);
221 });
222
223 assert_eq!(editor.text(cx), "ab2cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
225
226 // Last transaction happened past the group interval in a different editor.
227 // Undo it individually and don't restore selections.
228 editor.undo(&Undo, window, cx);
229 assert_eq!(editor.text(cx), "12cde6");
230 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
231
232 // First two transactions happened within the group interval in this editor.
233 // Undo them together and restore selections.
234 editor.undo(&Undo, window, cx);
235 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
236 assert_eq!(editor.text(cx), "123456");
237 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
238
239 // Redo the first two transactions together.
240 editor.redo(&Redo, window, cx);
241 assert_eq!(editor.text(cx), "12cde6");
242 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
243
244 // Redo the last transaction on its own.
245 editor.redo(&Redo, window, cx);
246 assert_eq!(editor.text(cx), "ab2cde6");
247 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
248
249 // Test empty transactions.
250 editor.start_transaction_at(now, window, cx);
251 editor.end_transaction_at(now, cx);
252 editor.undo(&Undo, window, cx);
253 assert_eq!(editor.text(cx), "12cde6");
254 });
255}
256
257#[gpui::test]
258fn test_ime_composition(cx: &mut TestAppContext) {
259 init_test(cx, |_| {});
260
261 let buffer = cx.new(|cx| {
262 let mut buffer = language::Buffer::local("abcde", cx);
263 // Ensure automatic grouping doesn't occur.
264 buffer.set_group_interval(Duration::ZERO);
265 buffer
266 });
267
268 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
269 cx.add_window(|window, cx| {
270 let mut editor = build_editor(buffer.clone(), window, cx);
271
272 // Start a new IME composition.
273 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
274 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
275 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
276 assert_eq!(editor.text(cx), "äbcde");
277 assert_eq!(
278 editor.marked_text_ranges(cx),
279 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
280 );
281
282 // Finalize IME composition.
283 editor.replace_text_in_range(None, "ā", window, cx);
284 assert_eq!(editor.text(cx), "ābcde");
285 assert_eq!(editor.marked_text_ranges(cx), None);
286
287 // IME composition edits are grouped and are undone/redone at once.
288 editor.undo(&Default::default(), window, cx);
289 assert_eq!(editor.text(cx), "abcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291 editor.redo(&Default::default(), window, cx);
292 assert_eq!(editor.text(cx), "ābcde");
293 assert_eq!(editor.marked_text_ranges(cx), None);
294
295 // Start a new IME composition.
296 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
297 assert_eq!(
298 editor.marked_text_ranges(cx),
299 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
300 );
301
302 // Undoing during an IME composition cancels it.
303 editor.undo(&Default::default(), window, cx);
304 assert_eq!(editor.text(cx), "ābcde");
305 assert_eq!(editor.marked_text_ranges(cx), None);
306
307 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
308 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
309 assert_eq!(editor.text(cx), "ābcdè");
310 assert_eq!(
311 editor.marked_text_ranges(cx),
312 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
313 );
314
315 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
316 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
317 assert_eq!(editor.text(cx), "ābcdę");
318 assert_eq!(editor.marked_text_ranges(cx), None);
319
320 // Start a new IME composition with multiple cursors.
321 editor.change_selections(None, window, cx, |s| {
322 s.select_ranges([
323 OffsetUtf16(1)..OffsetUtf16(1),
324 OffsetUtf16(3)..OffsetUtf16(3),
325 OffsetUtf16(5)..OffsetUtf16(5),
326 ])
327 });
328 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
329 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
330 assert_eq!(
331 editor.marked_text_ranges(cx),
332 Some(vec![
333 OffsetUtf16(0)..OffsetUtf16(3),
334 OffsetUtf16(4)..OffsetUtf16(7),
335 OffsetUtf16(8)..OffsetUtf16(11)
336 ])
337 );
338
339 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
340 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
341 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
342 assert_eq!(
343 editor.marked_text_ranges(cx),
344 Some(vec![
345 OffsetUtf16(1)..OffsetUtf16(2),
346 OffsetUtf16(5)..OffsetUtf16(6),
347 OffsetUtf16(9)..OffsetUtf16(10)
348 ])
349 );
350
351 // Finalize IME composition with multiple cursors.
352 editor.replace_text_in_range(Some(9..10), "2", window, cx);
353 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
354 assert_eq!(editor.marked_text_ranges(cx), None);
355
356 editor
357 });
358}
359
360#[gpui::test]
361fn test_selection_with_mouse(cx: &mut TestAppContext) {
362 init_test(cx, |_| {});
363
364 let editor = cx.add_window(|window, cx| {
365 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
366 build_editor(buffer, window, cx)
367 });
368
369 _ = editor.update(cx, |editor, window, cx| {
370 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
371 });
372 assert_eq!(
373 editor
374 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
375 .unwrap(),
376 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
377 );
378
379 _ = editor.update(cx, |editor, window, cx| {
380 editor.update_selection(
381 DisplayPoint::new(DisplayRow(3), 3),
382 0,
383 gpui::Point::<f32>::default(),
384 window,
385 cx,
386 );
387 });
388
389 assert_eq!(
390 editor
391 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
392 .unwrap(),
393 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
394 );
395
396 _ = editor.update(cx, |editor, window, cx| {
397 editor.update_selection(
398 DisplayPoint::new(DisplayRow(1), 1),
399 0,
400 gpui::Point::<f32>::default(),
401 window,
402 cx,
403 );
404 });
405
406 assert_eq!(
407 editor
408 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
409 .unwrap(),
410 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
411 );
412
413 _ = editor.update(cx, |editor, window, cx| {
414 editor.end_selection(window, cx);
415 editor.update_selection(
416 DisplayPoint::new(DisplayRow(3), 3),
417 0,
418 gpui::Point::<f32>::default(),
419 window,
420 cx,
421 );
422 });
423
424 assert_eq!(
425 editor
426 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
427 .unwrap(),
428 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
429 );
430
431 _ = editor.update(cx, |editor, window, cx| {
432 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
433 editor.update_selection(
434 DisplayPoint::new(DisplayRow(0), 0),
435 0,
436 gpui::Point::<f32>::default(),
437 window,
438 cx,
439 );
440 });
441
442 assert_eq!(
443 editor
444 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
445 .unwrap(),
446 [
447 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
448 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
449 ]
450 );
451
452 _ = editor.update(cx, |editor, window, cx| {
453 editor.end_selection(window, cx);
454 });
455
456 assert_eq!(
457 editor
458 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
459 .unwrap(),
460 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
461 );
462}
463
464#[gpui::test]
465fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
466 init_test(cx, |_| {});
467
468 let editor = cx.add_window(|window, cx| {
469 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
470 build_editor(buffer, window, cx)
471 });
472
473 _ = editor.update(cx, |editor, window, cx| {
474 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
475 });
476
477 _ = editor.update(cx, |editor, window, cx| {
478 editor.end_selection(window, cx);
479 });
480
481 _ = editor.update(cx, |editor, window, cx| {
482 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
483 });
484
485 _ = editor.update(cx, |editor, window, cx| {
486 editor.end_selection(window, cx);
487 });
488
489 assert_eq!(
490 editor
491 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
492 .unwrap(),
493 [
494 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
495 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
496 ]
497 );
498
499 _ = editor.update(cx, |editor, window, cx| {
500 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
501 });
502
503 _ = editor.update(cx, |editor, window, cx| {
504 editor.end_selection(window, cx);
505 });
506
507 assert_eq!(
508 editor
509 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
510 .unwrap(),
511 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
512 );
513}
514
515#[gpui::test]
516fn test_canceling_pending_selection(cx: &mut TestAppContext) {
517 init_test(cx, |_| {});
518
519 let editor = cx.add_window(|window, cx| {
520 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
521 build_editor(buffer, window, cx)
522 });
523
524 _ = editor.update(cx, |editor, window, cx| {
525 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
526 assert_eq!(
527 editor.selections.display_ranges(cx),
528 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
529 );
530 });
531
532 _ = editor.update(cx, |editor, window, cx| {
533 editor.update_selection(
534 DisplayPoint::new(DisplayRow(3), 3),
535 0,
536 gpui::Point::<f32>::default(),
537 window,
538 cx,
539 );
540 assert_eq!(
541 editor.selections.display_ranges(cx),
542 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
543 );
544 });
545
546 _ = editor.update(cx, |editor, window, cx| {
547 editor.cancel(&Cancel, window, cx);
548 editor.update_selection(
549 DisplayPoint::new(DisplayRow(1), 1),
550 0,
551 gpui::Point::<f32>::default(),
552 window,
553 cx,
554 );
555 assert_eq!(
556 editor.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
558 );
559 });
560}
561
562#[gpui::test]
563fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
564 init_test(cx, |_| {});
565
566 let editor = cx.add_window(|window, cx| {
567 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
568 build_editor(buffer, window, cx)
569 });
570
571 _ = editor.update(cx, |editor, window, cx| {
572 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
573 assert_eq!(
574 editor.selections.display_ranges(cx),
575 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
576 );
577
578 editor.move_down(&Default::default(), window, cx);
579 assert_eq!(
580 editor.selections.display_ranges(cx),
581 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
582 );
583
584 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
585 assert_eq!(
586 editor.selections.display_ranges(cx),
587 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
588 );
589
590 editor.move_up(&Default::default(), window, cx);
591 assert_eq!(
592 editor.selections.display_ranges(cx),
593 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
594 );
595 });
596}
597
598#[gpui::test]
599fn test_clone(cx: &mut TestAppContext) {
600 init_test(cx, |_| {});
601
602 let (text, selection_ranges) = marked_text_ranges(
603 indoc! {"
604 one
605 two
606 threeˇ
607 four
608 fiveˇ
609 "},
610 true,
611 );
612
613 let editor = cx.add_window(|window, cx| {
614 let buffer = MultiBuffer::build_simple(&text, cx);
615 build_editor(buffer, window, cx)
616 });
617
618 _ = editor.update(cx, |editor, window, cx| {
619 editor.change_selections(None, window, cx, |s| {
620 s.select_ranges(selection_ranges.clone())
621 });
622 editor.fold_creases(
623 vec![
624 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
625 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
626 ],
627 true,
628 window,
629 cx,
630 );
631 });
632
633 let cloned_editor = editor
634 .update(cx, |editor, _, cx| {
635 cx.open_window(Default::default(), |window, cx| {
636 cx.new(|cx| editor.clone(window, cx))
637 })
638 })
639 .unwrap()
640 .unwrap();
641
642 let snapshot = editor
643 .update(cx, |e, window, cx| e.snapshot(window, cx))
644 .unwrap();
645 let cloned_snapshot = cloned_editor
646 .update(cx, |e, window, cx| e.snapshot(window, cx))
647 .unwrap();
648
649 assert_eq!(
650 cloned_editor
651 .update(cx, |e, _, cx| e.display_text(cx))
652 .unwrap(),
653 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
654 );
655 assert_eq!(
656 cloned_snapshot
657 .folds_in_range(0..text.len())
658 .collect::<Vec<_>>(),
659 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
660 );
661 assert_set_eq!(
662 cloned_editor
663 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
664 .unwrap(),
665 editor
666 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
667 .unwrap()
668 );
669 assert_set_eq!(
670 cloned_editor
671 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
672 .unwrap(),
673 editor
674 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
675 .unwrap()
676 );
677}
678
679#[gpui::test]
680async fn test_navigation_history(cx: &mut TestAppContext) {
681 init_test(cx, |_| {});
682
683 use workspace::item::Item;
684
685 let fs = FakeFs::new(cx.executor());
686 let project = Project::test(fs, [], cx).await;
687 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
688 let pane = workspace
689 .update(cx, |workspace, _, _| workspace.active_pane().clone())
690 .unwrap();
691
692 _ = workspace.update(cx, |_v, window, cx| {
693 cx.new(|cx| {
694 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
695 let mut editor = build_editor(buffer.clone(), window, cx);
696 let handle = cx.entity();
697 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
698
699 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
700 editor.nav_history.as_mut().unwrap().pop_backward(cx)
701 }
702
703 // Move the cursor a small distance.
704 // Nothing is added to the navigation history.
705 editor.change_selections(None, window, cx, |s| {
706 s.select_display_ranges([
707 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
708 ])
709 });
710 editor.change_selections(None, window, cx, |s| {
711 s.select_display_ranges([
712 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
713 ])
714 });
715 assert!(pop_history(&mut editor, cx).is_none());
716
717 // Move the cursor a large distance.
718 // The history can jump back to the previous position.
719 editor.change_selections(None, window, cx, |s| {
720 s.select_display_ranges([
721 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
722 ])
723 });
724 let nav_entry = pop_history(&mut editor, cx).unwrap();
725 editor.navigate(nav_entry.data.unwrap(), window, cx);
726 assert_eq!(nav_entry.item.id(), cx.entity_id());
727 assert_eq!(
728 editor.selections.display_ranges(cx),
729 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
730 );
731 assert!(pop_history(&mut editor, cx).is_none());
732
733 // Move the cursor a small distance via the mouse.
734 // Nothing is added to the navigation history.
735 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
736 editor.end_selection(window, cx);
737 assert_eq!(
738 editor.selections.display_ranges(cx),
739 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
740 );
741 assert!(pop_history(&mut editor, cx).is_none());
742
743 // Move the cursor a large distance via the mouse.
744 // The history can jump back to the previous position.
745 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
746 editor.end_selection(window, cx);
747 assert_eq!(
748 editor.selections.display_ranges(cx),
749 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
750 );
751 let nav_entry = pop_history(&mut editor, cx).unwrap();
752 editor.navigate(nav_entry.data.unwrap(), window, cx);
753 assert_eq!(nav_entry.item.id(), cx.entity_id());
754 assert_eq!(
755 editor.selections.display_ranges(cx),
756 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
757 );
758 assert!(pop_history(&mut editor, cx).is_none());
759
760 // Set scroll position to check later
761 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
762 let original_scroll_position = editor.scroll_manager.anchor();
763
764 // Jump to the end of the document and adjust scroll
765 editor.move_to_end(&MoveToEnd, window, cx);
766 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
767 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
768
769 let nav_entry = pop_history(&mut editor, cx).unwrap();
770 editor.navigate(nav_entry.data.unwrap(), window, cx);
771 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
772
773 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
774 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
775 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
776 let invalid_point = Point::new(9999, 0);
777 editor.navigate(
778 Box::new(NavigationData {
779 cursor_anchor: invalid_anchor,
780 cursor_position: invalid_point,
781 scroll_anchor: ScrollAnchor {
782 anchor: invalid_anchor,
783 offset: Default::default(),
784 },
785 scroll_top_row: invalid_point.row,
786 }),
787 window,
788 cx,
789 );
790 assert_eq!(
791 editor.selections.display_ranges(cx),
792 &[editor.max_point(cx)..editor.max_point(cx)]
793 );
794 assert_eq!(
795 editor.scroll_position(cx),
796 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
797 );
798
799 editor
800 })
801 });
802}
803
804#[gpui::test]
805fn test_cancel(cx: &mut TestAppContext) {
806 init_test(cx, |_| {});
807
808 let editor = cx.add_window(|window, cx| {
809 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
810 build_editor(buffer, window, cx)
811 });
812
813 _ = editor.update(cx, |editor, window, cx| {
814 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
815 editor.update_selection(
816 DisplayPoint::new(DisplayRow(1), 1),
817 0,
818 gpui::Point::<f32>::default(),
819 window,
820 cx,
821 );
822 editor.end_selection(window, cx);
823
824 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
825 editor.update_selection(
826 DisplayPoint::new(DisplayRow(0), 3),
827 0,
828 gpui::Point::<f32>::default(),
829 window,
830 cx,
831 );
832 editor.end_selection(window, cx);
833 assert_eq!(
834 editor.selections.display_ranges(cx),
835 [
836 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
837 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
838 ]
839 );
840 });
841
842 _ = editor.update(cx, |editor, window, cx| {
843 editor.cancel(&Cancel, window, cx);
844 assert_eq!(
845 editor.selections.display_ranges(cx),
846 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
847 );
848 });
849
850 _ = editor.update(cx, |editor, window, cx| {
851 editor.cancel(&Cancel, window, cx);
852 assert_eq!(
853 editor.selections.display_ranges(cx),
854 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
855 );
856 });
857}
858
859#[gpui::test]
860fn test_fold_action(cx: &mut TestAppContext) {
861 init_test(cx, |_| {});
862
863 let editor = cx.add_window(|window, cx| {
864 let buffer = MultiBuffer::build_simple(
865 &"
866 impl Foo {
867 // Hello!
868
869 fn a() {
870 1
871 }
872
873 fn b() {
874 2
875 }
876
877 fn c() {
878 3
879 }
880 }
881 "
882 .unindent(),
883 cx,
884 );
885 build_editor(buffer.clone(), window, cx)
886 });
887
888 _ = editor.update(cx, |editor, window, cx| {
889 editor.change_selections(None, window, cx, |s| {
890 s.select_display_ranges([
891 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
892 ]);
893 });
894 editor.fold(&Fold, window, cx);
895 assert_eq!(
896 editor.display_text(cx),
897 "
898 impl Foo {
899 // Hello!
900
901 fn a() {
902 1
903 }
904
905 fn b() {⋯
906 }
907
908 fn c() {⋯
909 }
910 }
911 "
912 .unindent(),
913 );
914
915 editor.fold(&Fold, window, cx);
916 assert_eq!(
917 editor.display_text(cx),
918 "
919 impl Foo {⋯
920 }
921 "
922 .unindent(),
923 );
924
925 editor.unfold_lines(&UnfoldLines, window, cx);
926 assert_eq!(
927 editor.display_text(cx),
928 "
929 impl Foo {
930 // Hello!
931
932 fn a() {
933 1
934 }
935
936 fn b() {⋯
937 }
938
939 fn c() {⋯
940 }
941 }
942 "
943 .unindent(),
944 );
945
946 editor.unfold_lines(&UnfoldLines, window, cx);
947 assert_eq!(
948 editor.display_text(cx),
949 editor.buffer.read(cx).read(cx).text()
950 );
951 });
952}
953
954#[gpui::test]
955fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
956 init_test(cx, |_| {});
957
958 let editor = cx.add_window(|window, cx| {
959 let buffer = MultiBuffer::build_simple(
960 &"
961 class Foo:
962 # Hello!
963
964 def a():
965 print(1)
966
967 def b():
968 print(2)
969
970 def c():
971 print(3)
972 "
973 .unindent(),
974 cx,
975 );
976 build_editor(buffer.clone(), window, cx)
977 });
978
979 _ = editor.update(cx, |editor, window, cx| {
980 editor.change_selections(None, window, cx, |s| {
981 s.select_display_ranges([
982 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
983 ]);
984 });
985 editor.fold(&Fold, window, cx);
986 assert_eq!(
987 editor.display_text(cx),
988 "
989 class Foo:
990 # Hello!
991
992 def a():
993 print(1)
994
995 def b():⋯
996
997 def c():⋯
998 "
999 .unindent(),
1000 );
1001
1002 editor.fold(&Fold, window, cx);
1003 assert_eq!(
1004 editor.display_text(cx),
1005 "
1006 class Foo:⋯
1007 "
1008 .unindent(),
1009 );
1010
1011 editor.unfold_lines(&UnfoldLines, window, cx);
1012 assert_eq!(
1013 editor.display_text(cx),
1014 "
1015 class Foo:
1016 # Hello!
1017
1018 def a():
1019 print(1)
1020
1021 def b():⋯
1022
1023 def c():⋯
1024 "
1025 .unindent(),
1026 );
1027
1028 editor.unfold_lines(&UnfoldLines, window, cx);
1029 assert_eq!(
1030 editor.display_text(cx),
1031 editor.buffer.read(cx).read(cx).text()
1032 );
1033 });
1034}
1035
1036#[gpui::test]
1037fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1038 init_test(cx, |_| {});
1039
1040 let editor = cx.add_window(|window, cx| {
1041 let buffer = MultiBuffer::build_simple(
1042 &"
1043 class Foo:
1044 # Hello!
1045
1046 def a():
1047 print(1)
1048
1049 def b():
1050 print(2)
1051
1052
1053 def c():
1054 print(3)
1055
1056
1057 "
1058 .unindent(),
1059 cx,
1060 );
1061 build_editor(buffer.clone(), window, cx)
1062 });
1063
1064 _ = editor.update(cx, |editor, window, cx| {
1065 editor.change_selections(None, window, cx, |s| {
1066 s.select_display_ranges([
1067 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1068 ]);
1069 });
1070 editor.fold(&Fold, window, cx);
1071 assert_eq!(
1072 editor.display_text(cx),
1073 "
1074 class Foo:
1075 # Hello!
1076
1077 def a():
1078 print(1)
1079
1080 def b():⋯
1081
1082
1083 def c():⋯
1084
1085
1086 "
1087 .unindent(),
1088 );
1089
1090 editor.fold(&Fold, window, cx);
1091 assert_eq!(
1092 editor.display_text(cx),
1093 "
1094 class Foo:⋯
1095
1096
1097 "
1098 .unindent(),
1099 );
1100
1101 editor.unfold_lines(&UnfoldLines, window, cx);
1102 assert_eq!(
1103 editor.display_text(cx),
1104 "
1105 class Foo:
1106 # Hello!
1107
1108 def a():
1109 print(1)
1110
1111 def b():⋯
1112
1113
1114 def c():⋯
1115
1116
1117 "
1118 .unindent(),
1119 );
1120
1121 editor.unfold_lines(&UnfoldLines, window, cx);
1122 assert_eq!(
1123 editor.display_text(cx),
1124 editor.buffer.read(cx).read(cx).text()
1125 );
1126 });
1127}
1128
1129#[gpui::test]
1130fn test_fold_at_level(cx: &mut TestAppContext) {
1131 init_test(cx, |_| {});
1132
1133 let editor = cx.add_window(|window, cx| {
1134 let buffer = MultiBuffer::build_simple(
1135 &"
1136 class Foo:
1137 # Hello!
1138
1139 def a():
1140 print(1)
1141
1142 def b():
1143 print(2)
1144
1145
1146 class Bar:
1147 # World!
1148
1149 def a():
1150 print(1)
1151
1152 def b():
1153 print(2)
1154
1155
1156 "
1157 .unindent(),
1158 cx,
1159 );
1160 build_editor(buffer.clone(), window, cx)
1161 });
1162
1163 _ = editor.update(cx, |editor, window, cx| {
1164 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1165 assert_eq!(
1166 editor.display_text(cx),
1167 "
1168 class Foo:
1169 # Hello!
1170
1171 def a():⋯
1172
1173 def b():⋯
1174
1175
1176 class Bar:
1177 # World!
1178
1179 def a():⋯
1180
1181 def b():⋯
1182
1183
1184 "
1185 .unindent(),
1186 );
1187
1188 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1189 assert_eq!(
1190 editor.display_text(cx),
1191 "
1192 class Foo:⋯
1193
1194
1195 class Bar:⋯
1196
1197
1198 "
1199 .unindent(),
1200 );
1201
1202 editor.unfold_all(&UnfoldAll, window, cx);
1203 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1204 assert_eq!(
1205 editor.display_text(cx),
1206 "
1207 class Foo:
1208 # Hello!
1209
1210 def a():
1211 print(1)
1212
1213 def b():
1214 print(2)
1215
1216
1217 class Bar:
1218 # World!
1219
1220 def a():
1221 print(1)
1222
1223 def b():
1224 print(2)
1225
1226
1227 "
1228 .unindent(),
1229 );
1230
1231 assert_eq!(
1232 editor.display_text(cx),
1233 editor.buffer.read(cx).read(cx).text()
1234 );
1235 });
1236}
1237
1238#[gpui::test]
1239fn test_move_cursor(cx: &mut TestAppContext) {
1240 init_test(cx, |_| {});
1241
1242 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1243 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1244
1245 buffer.update(cx, |buffer, cx| {
1246 buffer.edit(
1247 vec![
1248 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1249 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1250 ],
1251 None,
1252 cx,
1253 );
1254 });
1255 _ = editor.update(cx, |editor, window, cx| {
1256 assert_eq!(
1257 editor.selections.display_ranges(cx),
1258 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1259 );
1260
1261 editor.move_down(&MoveDown, window, cx);
1262 assert_eq!(
1263 editor.selections.display_ranges(cx),
1264 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1265 );
1266
1267 editor.move_right(&MoveRight, window, cx);
1268 assert_eq!(
1269 editor.selections.display_ranges(cx),
1270 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1271 );
1272
1273 editor.move_left(&MoveLeft, window, cx);
1274 assert_eq!(
1275 editor.selections.display_ranges(cx),
1276 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1277 );
1278
1279 editor.move_up(&MoveUp, window, cx);
1280 assert_eq!(
1281 editor.selections.display_ranges(cx),
1282 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1283 );
1284
1285 editor.move_to_end(&MoveToEnd, window, cx);
1286 assert_eq!(
1287 editor.selections.display_ranges(cx),
1288 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1289 );
1290
1291 editor.move_to_beginning(&MoveToBeginning, window, cx);
1292 assert_eq!(
1293 editor.selections.display_ranges(cx),
1294 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1295 );
1296
1297 editor.change_selections(None, window, cx, |s| {
1298 s.select_display_ranges([
1299 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1300 ]);
1301 });
1302 editor.select_to_beginning(&SelectToBeginning, window, cx);
1303 assert_eq!(
1304 editor.selections.display_ranges(cx),
1305 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1306 );
1307
1308 editor.select_to_end(&SelectToEnd, window, cx);
1309 assert_eq!(
1310 editor.selections.display_ranges(cx),
1311 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1312 );
1313 });
1314}
1315
1316// TODO: Re-enable this test
1317#[cfg(target_os = "macos")]
1318#[gpui::test]
1319fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1320 init_test(cx, |_| {});
1321
1322 let editor = cx.add_window(|window, cx| {
1323 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1324 build_editor(buffer.clone(), window, cx)
1325 });
1326
1327 assert_eq!('🟥'.len_utf8(), 4);
1328 assert_eq!('α'.len_utf8(), 2);
1329
1330 _ = editor.update(cx, |editor, window, cx| {
1331 editor.fold_creases(
1332 vec![
1333 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1334 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1335 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1336 ],
1337 true,
1338 window,
1339 cx,
1340 );
1341 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1342
1343 editor.move_right(&MoveRight, window, cx);
1344 assert_eq!(
1345 editor.selections.display_ranges(cx),
1346 &[empty_range(0, "🟥".len())]
1347 );
1348 editor.move_right(&MoveRight, window, cx);
1349 assert_eq!(
1350 editor.selections.display_ranges(cx),
1351 &[empty_range(0, "🟥🟧".len())]
1352 );
1353 editor.move_right(&MoveRight, window, cx);
1354 assert_eq!(
1355 editor.selections.display_ranges(cx),
1356 &[empty_range(0, "🟥🟧⋯".len())]
1357 );
1358
1359 editor.move_down(&MoveDown, window, cx);
1360 assert_eq!(
1361 editor.selections.display_ranges(cx),
1362 &[empty_range(1, "ab⋯e".len())]
1363 );
1364 editor.move_left(&MoveLeft, window, cx);
1365 assert_eq!(
1366 editor.selections.display_ranges(cx),
1367 &[empty_range(1, "ab⋯".len())]
1368 );
1369 editor.move_left(&MoveLeft, window, cx);
1370 assert_eq!(
1371 editor.selections.display_ranges(cx),
1372 &[empty_range(1, "ab".len())]
1373 );
1374 editor.move_left(&MoveLeft, window, cx);
1375 assert_eq!(
1376 editor.selections.display_ranges(cx),
1377 &[empty_range(1, "a".len())]
1378 );
1379
1380 editor.move_down(&MoveDown, window, cx);
1381 assert_eq!(
1382 editor.selections.display_ranges(cx),
1383 &[empty_range(2, "α".len())]
1384 );
1385 editor.move_right(&MoveRight, window, cx);
1386 assert_eq!(
1387 editor.selections.display_ranges(cx),
1388 &[empty_range(2, "αβ".len())]
1389 );
1390 editor.move_right(&MoveRight, window, cx);
1391 assert_eq!(
1392 editor.selections.display_ranges(cx),
1393 &[empty_range(2, "αβ⋯".len())]
1394 );
1395 editor.move_right(&MoveRight, window, cx);
1396 assert_eq!(
1397 editor.selections.display_ranges(cx),
1398 &[empty_range(2, "αβ⋯ε".len())]
1399 );
1400
1401 editor.move_up(&MoveUp, window, cx);
1402 assert_eq!(
1403 editor.selections.display_ranges(cx),
1404 &[empty_range(1, "ab⋯e".len())]
1405 );
1406 editor.move_down(&MoveDown, window, cx);
1407 assert_eq!(
1408 editor.selections.display_ranges(cx),
1409 &[empty_range(2, "αβ⋯ε".len())]
1410 );
1411 editor.move_up(&MoveUp, window, cx);
1412 assert_eq!(
1413 editor.selections.display_ranges(cx),
1414 &[empty_range(1, "ab⋯e".len())]
1415 );
1416
1417 editor.move_up(&MoveUp, window, cx);
1418 assert_eq!(
1419 editor.selections.display_ranges(cx),
1420 &[empty_range(0, "🟥🟧".len())]
1421 );
1422 editor.move_left(&MoveLeft, window, cx);
1423 assert_eq!(
1424 editor.selections.display_ranges(cx),
1425 &[empty_range(0, "🟥".len())]
1426 );
1427 editor.move_left(&MoveLeft, window, cx);
1428 assert_eq!(
1429 editor.selections.display_ranges(cx),
1430 &[empty_range(0, "".len())]
1431 );
1432 });
1433}
1434
1435#[gpui::test]
1436fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1437 init_test(cx, |_| {});
1438
1439 let editor = cx.add_window(|window, cx| {
1440 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1441 build_editor(buffer.clone(), window, cx)
1442 });
1443 _ = editor.update(cx, |editor, window, cx| {
1444 editor.change_selections(None, window, cx, |s| {
1445 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1446 });
1447
1448 // moving above start of document should move selection to start of document,
1449 // but the next move down should still be at the original goal_x
1450 editor.move_up(&MoveUp, window, cx);
1451 assert_eq!(
1452 editor.selections.display_ranges(cx),
1453 &[empty_range(0, "".len())]
1454 );
1455
1456 editor.move_down(&MoveDown, window, cx);
1457 assert_eq!(
1458 editor.selections.display_ranges(cx),
1459 &[empty_range(1, "abcd".len())]
1460 );
1461
1462 editor.move_down(&MoveDown, window, cx);
1463 assert_eq!(
1464 editor.selections.display_ranges(cx),
1465 &[empty_range(2, "αβγ".len())]
1466 );
1467
1468 editor.move_down(&MoveDown, window, cx);
1469 assert_eq!(
1470 editor.selections.display_ranges(cx),
1471 &[empty_range(3, "abcd".len())]
1472 );
1473
1474 editor.move_down(&MoveDown, window, cx);
1475 assert_eq!(
1476 editor.selections.display_ranges(cx),
1477 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1478 );
1479
1480 // moving past end of document should not change goal_x
1481 editor.move_down(&MoveDown, window, cx);
1482 assert_eq!(
1483 editor.selections.display_ranges(cx),
1484 &[empty_range(5, "".len())]
1485 );
1486
1487 editor.move_down(&MoveDown, window, cx);
1488 assert_eq!(
1489 editor.selections.display_ranges(cx),
1490 &[empty_range(5, "".len())]
1491 );
1492
1493 editor.move_up(&MoveUp, window, cx);
1494 assert_eq!(
1495 editor.selections.display_ranges(cx),
1496 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1497 );
1498
1499 editor.move_up(&MoveUp, window, cx);
1500 assert_eq!(
1501 editor.selections.display_ranges(cx),
1502 &[empty_range(3, "abcd".len())]
1503 );
1504
1505 editor.move_up(&MoveUp, window, cx);
1506 assert_eq!(
1507 editor.selections.display_ranges(cx),
1508 &[empty_range(2, "αβγ".len())]
1509 );
1510 });
1511}
1512
1513#[gpui::test]
1514fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1515 init_test(cx, |_| {});
1516 let move_to_beg = MoveToBeginningOfLine {
1517 stop_at_soft_wraps: true,
1518 stop_at_indent: true,
1519 };
1520
1521 let delete_to_beg = DeleteToBeginningOfLine {
1522 stop_at_indent: false,
1523 };
1524
1525 let move_to_end = MoveToEndOfLine {
1526 stop_at_soft_wraps: true,
1527 };
1528
1529 let editor = cx.add_window(|window, cx| {
1530 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1531 build_editor(buffer, window, cx)
1532 });
1533 _ = editor.update(cx, |editor, window, cx| {
1534 editor.change_selections(None, window, cx, |s| {
1535 s.select_display_ranges([
1536 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1537 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1538 ]);
1539 });
1540 });
1541
1542 _ = editor.update(cx, |editor, window, cx| {
1543 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1544 assert_eq!(
1545 editor.selections.display_ranges(cx),
1546 &[
1547 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1548 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1549 ]
1550 );
1551 });
1552
1553 _ = editor.update(cx, |editor, window, cx| {
1554 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1555 assert_eq!(
1556 editor.selections.display_ranges(cx),
1557 &[
1558 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1559 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1560 ]
1561 );
1562 });
1563
1564 _ = editor.update(cx, |editor, window, cx| {
1565 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1566 assert_eq!(
1567 editor.selections.display_ranges(cx),
1568 &[
1569 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1570 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1571 ]
1572 );
1573 });
1574
1575 _ = editor.update(cx, |editor, window, cx| {
1576 editor.move_to_end_of_line(&move_to_end, window, cx);
1577 assert_eq!(
1578 editor.selections.display_ranges(cx),
1579 &[
1580 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1581 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1582 ]
1583 );
1584 });
1585
1586 // Moving to the end of line again is a no-op.
1587 _ = editor.update(cx, |editor, window, cx| {
1588 editor.move_to_end_of_line(&move_to_end, window, cx);
1589 assert_eq!(
1590 editor.selections.display_ranges(cx),
1591 &[
1592 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1593 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1594 ]
1595 );
1596 });
1597
1598 _ = editor.update(cx, |editor, window, cx| {
1599 editor.move_left(&MoveLeft, window, cx);
1600 editor.select_to_beginning_of_line(
1601 &SelectToBeginningOfLine {
1602 stop_at_soft_wraps: true,
1603 stop_at_indent: true,
1604 },
1605 window,
1606 cx,
1607 );
1608 assert_eq!(
1609 editor.selections.display_ranges(cx),
1610 &[
1611 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1612 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1613 ]
1614 );
1615 });
1616
1617 _ = editor.update(cx, |editor, window, cx| {
1618 editor.select_to_beginning_of_line(
1619 &SelectToBeginningOfLine {
1620 stop_at_soft_wraps: true,
1621 stop_at_indent: true,
1622 },
1623 window,
1624 cx,
1625 );
1626 assert_eq!(
1627 editor.selections.display_ranges(cx),
1628 &[
1629 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1630 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1631 ]
1632 );
1633 });
1634
1635 _ = editor.update(cx, |editor, window, cx| {
1636 editor.select_to_beginning_of_line(
1637 &SelectToBeginningOfLine {
1638 stop_at_soft_wraps: true,
1639 stop_at_indent: true,
1640 },
1641 window,
1642 cx,
1643 );
1644 assert_eq!(
1645 editor.selections.display_ranges(cx),
1646 &[
1647 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1648 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1649 ]
1650 );
1651 });
1652
1653 _ = editor.update(cx, |editor, window, cx| {
1654 editor.select_to_end_of_line(
1655 &SelectToEndOfLine {
1656 stop_at_soft_wraps: true,
1657 },
1658 window,
1659 cx,
1660 );
1661 assert_eq!(
1662 editor.selections.display_ranges(cx),
1663 &[
1664 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1665 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1666 ]
1667 );
1668 });
1669
1670 _ = editor.update(cx, |editor, window, cx| {
1671 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1672 assert_eq!(editor.display_text(cx), "ab\n de");
1673 assert_eq!(
1674 editor.selections.display_ranges(cx),
1675 &[
1676 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1677 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1678 ]
1679 );
1680 });
1681
1682 _ = editor.update(cx, |editor, window, cx| {
1683 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1684 assert_eq!(editor.display_text(cx), "\n");
1685 assert_eq!(
1686 editor.selections.display_ranges(cx),
1687 &[
1688 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1689 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1690 ]
1691 );
1692 });
1693}
1694
1695#[gpui::test]
1696fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1697 init_test(cx, |_| {});
1698 let move_to_beg = MoveToBeginningOfLine {
1699 stop_at_soft_wraps: false,
1700 stop_at_indent: false,
1701 };
1702
1703 let move_to_end = MoveToEndOfLine {
1704 stop_at_soft_wraps: false,
1705 };
1706
1707 let editor = cx.add_window(|window, cx| {
1708 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1709 build_editor(buffer, window, cx)
1710 });
1711
1712 _ = editor.update(cx, |editor, window, cx| {
1713 editor.set_wrap_width(Some(140.0.into()), cx);
1714
1715 // We expect the following lines after wrapping
1716 // ```
1717 // thequickbrownfox
1718 // jumpedoverthelazydo
1719 // gs
1720 // ```
1721 // The final `gs` was soft-wrapped onto a new line.
1722 assert_eq!(
1723 "thequickbrownfox\njumpedoverthelaz\nydogs",
1724 editor.display_text(cx),
1725 );
1726
1727 // First, let's assert behavior on the first line, that was not soft-wrapped.
1728 // Start the cursor at the `k` on the first line
1729 editor.change_selections(None, window, cx, |s| {
1730 s.select_display_ranges([
1731 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1732 ]);
1733 });
1734
1735 // Moving to the beginning of the line should put us at the beginning of the line.
1736 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1737 assert_eq!(
1738 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1739 editor.selections.display_ranges(cx)
1740 );
1741
1742 // Moving to the end of the line should put us at the end of the line.
1743 editor.move_to_end_of_line(&move_to_end, window, cx);
1744 assert_eq!(
1745 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1746 editor.selections.display_ranges(cx)
1747 );
1748
1749 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1750 // Start the cursor at the last line (`y` that was wrapped to a new line)
1751 editor.change_selections(None, window, cx, |s| {
1752 s.select_display_ranges([
1753 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1754 ]);
1755 });
1756
1757 // Moving to the beginning of the line should put us at the start of the second line of
1758 // display text, i.e., the `j`.
1759 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1760 assert_eq!(
1761 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1762 editor.selections.display_ranges(cx)
1763 );
1764
1765 // Moving to the beginning of the line again should be a no-op.
1766 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1767 assert_eq!(
1768 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1769 editor.selections.display_ranges(cx)
1770 );
1771
1772 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1773 // next display line.
1774 editor.move_to_end_of_line(&move_to_end, window, cx);
1775 assert_eq!(
1776 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1777 editor.selections.display_ranges(cx)
1778 );
1779
1780 // Moving to the end of the line again should be a no-op.
1781 editor.move_to_end_of_line(&move_to_end, window, cx);
1782 assert_eq!(
1783 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1784 editor.selections.display_ranges(cx)
1785 );
1786 });
1787}
1788
1789#[gpui::test]
1790fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1791 init_test(cx, |_| {});
1792
1793 let move_to_beg = MoveToBeginningOfLine {
1794 stop_at_soft_wraps: true,
1795 stop_at_indent: true,
1796 };
1797
1798 let select_to_beg = SelectToBeginningOfLine {
1799 stop_at_soft_wraps: true,
1800 stop_at_indent: true,
1801 };
1802
1803 let delete_to_beg = DeleteToBeginningOfLine {
1804 stop_at_indent: true,
1805 };
1806
1807 let move_to_end = MoveToEndOfLine {
1808 stop_at_soft_wraps: false,
1809 };
1810
1811 let editor = cx.add_window(|window, cx| {
1812 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1813 build_editor(buffer, window, cx)
1814 });
1815
1816 _ = editor.update(cx, |editor, window, cx| {
1817 editor.change_selections(None, window, cx, |s| {
1818 s.select_display_ranges([
1819 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1820 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1821 ]);
1822 });
1823
1824 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1825 // and the second cursor at the first non-whitespace character in the line.
1826 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1827 assert_eq!(
1828 editor.selections.display_ranges(cx),
1829 &[
1830 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1831 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1832 ]
1833 );
1834
1835 // Moving to the beginning of the line again should be a no-op for the first cursor,
1836 // and should move the second cursor to the beginning of the line.
1837 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1838 assert_eq!(
1839 editor.selections.display_ranges(cx),
1840 &[
1841 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1842 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1843 ]
1844 );
1845
1846 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1847 // and should move the second cursor back to the first non-whitespace character in the line.
1848 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1849 assert_eq!(
1850 editor.selections.display_ranges(cx),
1851 &[
1852 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1853 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1854 ]
1855 );
1856
1857 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1858 // and to the first non-whitespace character in the line for the second cursor.
1859 editor.move_to_end_of_line(&move_to_end, window, cx);
1860 editor.move_left(&MoveLeft, window, cx);
1861 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1862 assert_eq!(
1863 editor.selections.display_ranges(cx),
1864 &[
1865 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1866 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1867 ]
1868 );
1869
1870 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1871 // and should select to the beginning of the line for the second cursor.
1872 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1873 assert_eq!(
1874 editor.selections.display_ranges(cx),
1875 &[
1876 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1877 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1878 ]
1879 );
1880
1881 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1882 // and should delete to the first non-whitespace character in the line for the second cursor.
1883 editor.move_to_end_of_line(&move_to_end, window, cx);
1884 editor.move_left(&MoveLeft, window, cx);
1885 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1886 assert_eq!(editor.text(cx), "c\n f");
1887 });
1888}
1889
1890#[gpui::test]
1891fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1892 init_test(cx, |_| {});
1893
1894 let editor = cx.add_window(|window, cx| {
1895 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1896 build_editor(buffer, window, cx)
1897 });
1898 _ = editor.update(cx, |editor, window, cx| {
1899 editor.change_selections(None, window, cx, |s| {
1900 s.select_display_ranges([
1901 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1902 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1903 ])
1904 });
1905
1906 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1907 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1908
1909 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1910 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1911
1912 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1913 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1914
1915 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1916 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1917
1918 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1919 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1920
1921 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1922 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1923
1924 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1925 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1926
1927 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1928 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1929
1930 editor.move_right(&MoveRight, window, cx);
1931 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1932 assert_selection_ranges(
1933 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1934 editor,
1935 cx,
1936 );
1937
1938 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1939 assert_selection_ranges(
1940 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1941 editor,
1942 cx,
1943 );
1944
1945 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1946 assert_selection_ranges(
1947 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1948 editor,
1949 cx,
1950 );
1951 });
1952}
1953
1954#[gpui::test]
1955fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1956 init_test(cx, |_| {});
1957
1958 let editor = cx.add_window(|window, cx| {
1959 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1960 build_editor(buffer, window, cx)
1961 });
1962
1963 _ = editor.update(cx, |editor, window, cx| {
1964 editor.set_wrap_width(Some(140.0.into()), cx);
1965 assert_eq!(
1966 editor.display_text(cx),
1967 "use one::{\n two::three::\n four::five\n};"
1968 );
1969
1970 editor.change_selections(None, window, cx, |s| {
1971 s.select_display_ranges([
1972 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1973 ]);
1974 });
1975
1976 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1977 assert_eq!(
1978 editor.selections.display_ranges(cx),
1979 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1980 );
1981
1982 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1983 assert_eq!(
1984 editor.selections.display_ranges(cx),
1985 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1986 );
1987
1988 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1989 assert_eq!(
1990 editor.selections.display_ranges(cx),
1991 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1992 );
1993
1994 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1995 assert_eq!(
1996 editor.selections.display_ranges(cx),
1997 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1998 );
1999
2000 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2001 assert_eq!(
2002 editor.selections.display_ranges(cx),
2003 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2004 );
2005
2006 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2007 assert_eq!(
2008 editor.selections.display_ranges(cx),
2009 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2010 );
2011 });
2012}
2013
2014#[gpui::test]
2015async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2016 init_test(cx, |_| {});
2017 let mut cx = EditorTestContext::new(cx).await;
2018
2019 let line_height = cx.editor(|editor, window, _| {
2020 editor
2021 .style()
2022 .unwrap()
2023 .text
2024 .line_height_in_pixels(window.rem_size())
2025 });
2026 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2027
2028 cx.set_state(
2029 &r#"ˇone
2030 two
2031
2032 three
2033 fourˇ
2034 five
2035
2036 six"#
2037 .unindent(),
2038 );
2039
2040 cx.update_editor(|editor, window, cx| {
2041 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2042 });
2043 cx.assert_editor_state(
2044 &r#"one
2045 two
2046 ˇ
2047 three
2048 four
2049 five
2050 ˇ
2051 six"#
2052 .unindent(),
2053 );
2054
2055 cx.update_editor(|editor, window, cx| {
2056 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2057 });
2058 cx.assert_editor_state(
2059 &r#"one
2060 two
2061
2062 three
2063 four
2064 five
2065 ˇ
2066 sixˇ"#
2067 .unindent(),
2068 );
2069
2070 cx.update_editor(|editor, window, cx| {
2071 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2072 });
2073 cx.assert_editor_state(
2074 &r#"one
2075 two
2076
2077 three
2078 four
2079 five
2080
2081 sixˇ"#
2082 .unindent(),
2083 );
2084
2085 cx.update_editor(|editor, window, cx| {
2086 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2087 });
2088 cx.assert_editor_state(
2089 &r#"one
2090 two
2091
2092 three
2093 four
2094 five
2095 ˇ
2096 six"#
2097 .unindent(),
2098 );
2099
2100 cx.update_editor(|editor, window, cx| {
2101 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2102 });
2103 cx.assert_editor_state(
2104 &r#"one
2105 two
2106 ˇ
2107 three
2108 four
2109 five
2110
2111 six"#
2112 .unindent(),
2113 );
2114
2115 cx.update_editor(|editor, window, cx| {
2116 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2117 });
2118 cx.assert_editor_state(
2119 &r#"ˇone
2120 two
2121
2122 three
2123 four
2124 five
2125
2126 six"#
2127 .unindent(),
2128 );
2129}
2130
2131#[gpui::test]
2132async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2133 init_test(cx, |_| {});
2134 let mut cx = EditorTestContext::new(cx).await;
2135 let line_height = cx.editor(|editor, window, _| {
2136 editor
2137 .style()
2138 .unwrap()
2139 .text
2140 .line_height_in_pixels(window.rem_size())
2141 });
2142 let window = cx.window;
2143 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2144
2145 cx.set_state(
2146 r#"ˇone
2147 two
2148 three
2149 four
2150 five
2151 six
2152 seven
2153 eight
2154 nine
2155 ten
2156 "#,
2157 );
2158
2159 cx.update_editor(|editor, window, cx| {
2160 assert_eq!(
2161 editor.snapshot(window, cx).scroll_position(),
2162 gpui::Point::new(0., 0.)
2163 );
2164 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2165 assert_eq!(
2166 editor.snapshot(window, cx).scroll_position(),
2167 gpui::Point::new(0., 3.)
2168 );
2169 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2170 assert_eq!(
2171 editor.snapshot(window, cx).scroll_position(),
2172 gpui::Point::new(0., 6.)
2173 );
2174 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2175 assert_eq!(
2176 editor.snapshot(window, cx).scroll_position(),
2177 gpui::Point::new(0., 3.)
2178 );
2179
2180 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2181 assert_eq!(
2182 editor.snapshot(window, cx).scroll_position(),
2183 gpui::Point::new(0., 1.)
2184 );
2185 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2186 assert_eq!(
2187 editor.snapshot(window, cx).scroll_position(),
2188 gpui::Point::new(0., 3.)
2189 );
2190 });
2191}
2192
2193#[gpui::test]
2194async fn test_autoscroll(cx: &mut TestAppContext) {
2195 init_test(cx, |_| {});
2196 let mut cx = EditorTestContext::new(cx).await;
2197
2198 let line_height = cx.update_editor(|editor, window, cx| {
2199 editor.set_vertical_scroll_margin(2, cx);
2200 editor
2201 .style()
2202 .unwrap()
2203 .text
2204 .line_height_in_pixels(window.rem_size())
2205 });
2206 let window = cx.window;
2207 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2208
2209 cx.set_state(
2210 r#"ˇone
2211 two
2212 three
2213 four
2214 five
2215 six
2216 seven
2217 eight
2218 nine
2219 ten
2220 "#,
2221 );
2222 cx.update_editor(|editor, window, cx| {
2223 assert_eq!(
2224 editor.snapshot(window, cx).scroll_position(),
2225 gpui::Point::new(0., 0.0)
2226 );
2227 });
2228
2229 // Add a cursor below the visible area. Since both cursors cannot fit
2230 // on screen, the editor autoscrolls to reveal the newest cursor, and
2231 // allows the vertical scroll margin below that cursor.
2232 cx.update_editor(|editor, window, cx| {
2233 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2234 selections.select_ranges([
2235 Point::new(0, 0)..Point::new(0, 0),
2236 Point::new(6, 0)..Point::new(6, 0),
2237 ]);
2238 })
2239 });
2240 cx.update_editor(|editor, window, cx| {
2241 assert_eq!(
2242 editor.snapshot(window, cx).scroll_position(),
2243 gpui::Point::new(0., 3.0)
2244 );
2245 });
2246
2247 // Move down. The editor cursor scrolls down to track the newest cursor.
2248 cx.update_editor(|editor, window, cx| {
2249 editor.move_down(&Default::default(), window, cx);
2250 });
2251 cx.update_editor(|editor, window, cx| {
2252 assert_eq!(
2253 editor.snapshot(window, cx).scroll_position(),
2254 gpui::Point::new(0., 4.0)
2255 );
2256 });
2257
2258 // Add a cursor above the visible area. Since both cursors fit on screen,
2259 // the editor scrolls to show both.
2260 cx.update_editor(|editor, window, cx| {
2261 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2262 selections.select_ranges([
2263 Point::new(1, 0)..Point::new(1, 0),
2264 Point::new(6, 0)..Point::new(6, 0),
2265 ]);
2266 })
2267 });
2268 cx.update_editor(|editor, window, cx| {
2269 assert_eq!(
2270 editor.snapshot(window, cx).scroll_position(),
2271 gpui::Point::new(0., 1.0)
2272 );
2273 });
2274}
2275
2276#[gpui::test]
2277async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2278 init_test(cx, |_| {});
2279 let mut cx = EditorTestContext::new(cx).await;
2280
2281 let line_height = cx.editor(|editor, window, _cx| {
2282 editor
2283 .style()
2284 .unwrap()
2285 .text
2286 .line_height_in_pixels(window.rem_size())
2287 });
2288 let window = cx.window;
2289 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2290 cx.set_state(
2291 &r#"
2292 ˇone
2293 two
2294 threeˇ
2295 four
2296 five
2297 six
2298 seven
2299 eight
2300 nine
2301 ten
2302 "#
2303 .unindent(),
2304 );
2305
2306 cx.update_editor(|editor, window, cx| {
2307 editor.move_page_down(&MovePageDown::default(), window, cx)
2308 });
2309 cx.assert_editor_state(
2310 &r#"
2311 one
2312 two
2313 three
2314 ˇfour
2315 five
2316 sixˇ
2317 seven
2318 eight
2319 nine
2320 ten
2321 "#
2322 .unindent(),
2323 );
2324
2325 cx.update_editor(|editor, window, cx| {
2326 editor.move_page_down(&MovePageDown::default(), window, cx)
2327 });
2328 cx.assert_editor_state(
2329 &r#"
2330 one
2331 two
2332 three
2333 four
2334 five
2335 six
2336 ˇseven
2337 eight
2338 nineˇ
2339 ten
2340 "#
2341 .unindent(),
2342 );
2343
2344 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2345 cx.assert_editor_state(
2346 &r#"
2347 one
2348 two
2349 three
2350 ˇfour
2351 five
2352 sixˇ
2353 seven
2354 eight
2355 nine
2356 ten
2357 "#
2358 .unindent(),
2359 );
2360
2361 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2362 cx.assert_editor_state(
2363 &r#"
2364 ˇone
2365 two
2366 threeˇ
2367 four
2368 five
2369 six
2370 seven
2371 eight
2372 nine
2373 ten
2374 "#
2375 .unindent(),
2376 );
2377
2378 // Test select collapsing
2379 cx.update_editor(|editor, window, cx| {
2380 editor.move_page_down(&MovePageDown::default(), window, cx);
2381 editor.move_page_down(&MovePageDown::default(), window, cx);
2382 editor.move_page_down(&MovePageDown::default(), window, cx);
2383 });
2384 cx.assert_editor_state(
2385 &r#"
2386 one
2387 two
2388 three
2389 four
2390 five
2391 six
2392 seven
2393 eight
2394 nine
2395 ˇten
2396 ˇ"#
2397 .unindent(),
2398 );
2399}
2400
2401#[gpui::test]
2402async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2403 init_test(cx, |_| {});
2404 let mut cx = EditorTestContext::new(cx).await;
2405 cx.set_state("one «two threeˇ» four");
2406 cx.update_editor(|editor, window, cx| {
2407 editor.delete_to_beginning_of_line(
2408 &DeleteToBeginningOfLine {
2409 stop_at_indent: false,
2410 },
2411 window,
2412 cx,
2413 );
2414 assert_eq!(editor.text(cx), " four");
2415 });
2416}
2417
2418#[gpui::test]
2419fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2420 init_test(cx, |_| {});
2421
2422 let editor = cx.add_window(|window, cx| {
2423 let buffer = MultiBuffer::build_simple("one two three four", cx);
2424 build_editor(buffer.clone(), window, cx)
2425 });
2426
2427 _ = editor.update(cx, |editor, window, cx| {
2428 editor.change_selections(None, window, cx, |s| {
2429 s.select_display_ranges([
2430 // an empty selection - the preceding word fragment is deleted
2431 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2432 // characters selected - they are deleted
2433 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2434 ])
2435 });
2436 editor.delete_to_previous_word_start(
2437 &DeleteToPreviousWordStart {
2438 ignore_newlines: false,
2439 },
2440 window,
2441 cx,
2442 );
2443 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2444 });
2445
2446 _ = editor.update(cx, |editor, window, cx| {
2447 editor.change_selections(None, window, cx, |s| {
2448 s.select_display_ranges([
2449 // an empty selection - the following word fragment is deleted
2450 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2451 // characters selected - they are deleted
2452 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2453 ])
2454 });
2455 editor.delete_to_next_word_end(
2456 &DeleteToNextWordEnd {
2457 ignore_newlines: false,
2458 },
2459 window,
2460 cx,
2461 );
2462 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2463 });
2464}
2465
2466#[gpui::test]
2467fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2468 init_test(cx, |_| {});
2469
2470 let editor = cx.add_window(|window, cx| {
2471 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2472 build_editor(buffer.clone(), window, cx)
2473 });
2474 let del_to_prev_word_start = DeleteToPreviousWordStart {
2475 ignore_newlines: false,
2476 };
2477 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2478 ignore_newlines: true,
2479 };
2480
2481 _ = editor.update(cx, |editor, window, cx| {
2482 editor.change_selections(None, window, cx, |s| {
2483 s.select_display_ranges([
2484 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2485 ])
2486 });
2487 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2488 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
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");
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\n");
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");
2495 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2496 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
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(), "");
2499 });
2500}
2501
2502#[gpui::test]
2503fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2504 init_test(cx, |_| {});
2505
2506 let editor = cx.add_window(|window, cx| {
2507 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2508 build_editor(buffer.clone(), window, cx)
2509 });
2510 let del_to_next_word_end = DeleteToNextWordEnd {
2511 ignore_newlines: false,
2512 };
2513 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2514 ignore_newlines: true,
2515 };
2516
2517 _ = editor.update(cx, |editor, window, cx| {
2518 editor.change_selections(None, window, cx, |s| {
2519 s.select_display_ranges([
2520 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2521 ])
2522 });
2523 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2524 assert_eq!(
2525 editor.buffer.read(cx).read(cx).text(),
2526 "one\n two\nthree\n four"
2527 );
2528 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2529 assert_eq!(
2530 editor.buffer.read(cx).read(cx).text(),
2531 "\n two\nthree\n four"
2532 );
2533 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2534 assert_eq!(
2535 editor.buffer.read(cx).read(cx).text(),
2536 "two\nthree\n four"
2537 );
2538 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2539 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2540 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2541 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\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(), "");
2544 });
2545}
2546
2547#[gpui::test]
2548fn test_newline(cx: &mut TestAppContext) {
2549 init_test(cx, |_| {});
2550
2551 let editor = cx.add_window(|window, cx| {
2552 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2553 build_editor(buffer.clone(), window, cx)
2554 });
2555
2556 _ = editor.update(cx, |editor, window, cx| {
2557 editor.change_selections(None, window, cx, |s| {
2558 s.select_display_ranges([
2559 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2560 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2561 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2562 ])
2563 });
2564
2565 editor.newline(&Newline, window, cx);
2566 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2567 });
2568}
2569
2570#[gpui::test]
2571fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2572 init_test(cx, |_| {});
2573
2574 let editor = cx.add_window(|window, cx| {
2575 let buffer = MultiBuffer::build_simple(
2576 "
2577 a
2578 b(
2579 X
2580 )
2581 c(
2582 X
2583 )
2584 "
2585 .unindent()
2586 .as_str(),
2587 cx,
2588 );
2589 let mut editor = build_editor(buffer.clone(), window, cx);
2590 editor.change_selections(None, window, cx, |s| {
2591 s.select_ranges([
2592 Point::new(2, 4)..Point::new(2, 5),
2593 Point::new(5, 4)..Point::new(5, 5),
2594 ])
2595 });
2596 editor
2597 });
2598
2599 _ = editor.update(cx, |editor, window, cx| {
2600 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2601 editor.buffer.update(cx, |buffer, cx| {
2602 buffer.edit(
2603 [
2604 (Point::new(1, 2)..Point::new(3, 0), ""),
2605 (Point::new(4, 2)..Point::new(6, 0), ""),
2606 ],
2607 None,
2608 cx,
2609 );
2610 assert_eq!(
2611 buffer.read(cx).text(),
2612 "
2613 a
2614 b()
2615 c()
2616 "
2617 .unindent()
2618 );
2619 });
2620 assert_eq!(
2621 editor.selections.ranges(cx),
2622 &[
2623 Point::new(1, 2)..Point::new(1, 2),
2624 Point::new(2, 2)..Point::new(2, 2),
2625 ],
2626 );
2627
2628 editor.newline(&Newline, window, cx);
2629 assert_eq!(
2630 editor.text(cx),
2631 "
2632 a
2633 b(
2634 )
2635 c(
2636 )
2637 "
2638 .unindent()
2639 );
2640
2641 // The selections are moved after the inserted newlines
2642 assert_eq!(
2643 editor.selections.ranges(cx),
2644 &[
2645 Point::new(2, 0)..Point::new(2, 0),
2646 Point::new(4, 0)..Point::new(4, 0),
2647 ],
2648 );
2649 });
2650}
2651
2652#[gpui::test]
2653async fn test_newline_above(cx: &mut TestAppContext) {
2654 init_test(cx, |settings| {
2655 settings.defaults.tab_size = NonZeroU32::new(4)
2656 });
2657
2658 let language = Arc::new(
2659 Language::new(
2660 LanguageConfig::default(),
2661 Some(tree_sitter_rust::LANGUAGE.into()),
2662 )
2663 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2664 .unwrap(),
2665 );
2666
2667 let mut cx = EditorTestContext::new(cx).await;
2668 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2669 cx.set_state(indoc! {"
2670 const a: ˇA = (
2671 (ˇ
2672 «const_functionˇ»(ˇ),
2673 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2674 )ˇ
2675 ˇ);ˇ
2676 "});
2677
2678 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2679 cx.assert_editor_state(indoc! {"
2680 ˇ
2681 const a: A = (
2682 ˇ
2683 (
2684 ˇ
2685 ˇ
2686 const_function(),
2687 ˇ
2688 ˇ
2689 ˇ
2690 ˇ
2691 something_else,
2692 ˇ
2693 )
2694 ˇ
2695 ˇ
2696 );
2697 "});
2698}
2699
2700#[gpui::test]
2701async fn test_newline_below(cx: &mut TestAppContext) {
2702 init_test(cx, |settings| {
2703 settings.defaults.tab_size = NonZeroU32::new(4)
2704 });
2705
2706 let language = Arc::new(
2707 Language::new(
2708 LanguageConfig::default(),
2709 Some(tree_sitter_rust::LANGUAGE.into()),
2710 )
2711 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2712 .unwrap(),
2713 );
2714
2715 let mut cx = EditorTestContext::new(cx).await;
2716 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2717 cx.set_state(indoc! {"
2718 const a: ˇA = (
2719 (ˇ
2720 «const_functionˇ»(ˇ),
2721 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2722 )ˇ
2723 ˇ);ˇ
2724 "});
2725
2726 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2727 cx.assert_editor_state(indoc! {"
2728 const a: A = (
2729 ˇ
2730 (
2731 ˇ
2732 const_function(),
2733 ˇ
2734 ˇ
2735 something_else,
2736 ˇ
2737 ˇ
2738 ˇ
2739 ˇ
2740 )
2741 ˇ
2742 );
2743 ˇ
2744 ˇ
2745 "});
2746}
2747
2748#[gpui::test]
2749async fn test_newline_comments(cx: &mut TestAppContext) {
2750 init_test(cx, |settings| {
2751 settings.defaults.tab_size = NonZeroU32::new(4)
2752 });
2753
2754 let language = Arc::new(Language::new(
2755 LanguageConfig {
2756 line_comments: vec!["//".into()],
2757 ..LanguageConfig::default()
2758 },
2759 None,
2760 ));
2761 {
2762 let mut cx = EditorTestContext::new(cx).await;
2763 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2764 cx.set_state(indoc! {"
2765 // Fooˇ
2766 "});
2767
2768 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2769 cx.assert_editor_state(indoc! {"
2770 // Foo
2771 //ˇ
2772 "});
2773 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2774 cx.set_state(indoc! {"
2775 ˇ// Foo
2776 "});
2777 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2778 cx.assert_editor_state(indoc! {"
2779
2780 ˇ// Foo
2781 "});
2782 }
2783 // Ensure that comment continuations can be disabled.
2784 update_test_language_settings(cx, |settings| {
2785 settings.defaults.extend_comment_on_newline = Some(false);
2786 });
2787 let mut cx = EditorTestContext::new(cx).await;
2788 cx.set_state(indoc! {"
2789 // Fooˇ
2790 "});
2791 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2792 cx.assert_editor_state(indoc! {"
2793 // Foo
2794 ˇ
2795 "});
2796}
2797
2798#[gpui::test]
2799fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2800 init_test(cx, |_| {});
2801
2802 let editor = cx.add_window(|window, cx| {
2803 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2804 let mut editor = build_editor(buffer.clone(), window, cx);
2805 editor.change_selections(None, window, cx, |s| {
2806 s.select_ranges([3..4, 11..12, 19..20])
2807 });
2808 editor
2809 });
2810
2811 _ = editor.update(cx, |editor, window, cx| {
2812 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2813 editor.buffer.update(cx, |buffer, cx| {
2814 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2815 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2816 });
2817 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2818
2819 editor.insert("Z", window, cx);
2820 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2821
2822 // The selections are moved after the inserted characters
2823 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2824 });
2825}
2826
2827#[gpui::test]
2828async fn test_tab(cx: &mut TestAppContext) {
2829 init_test(cx, |settings| {
2830 settings.defaults.tab_size = NonZeroU32::new(3)
2831 });
2832
2833 let mut cx = EditorTestContext::new(cx).await;
2834 cx.set_state(indoc! {"
2835 ˇabˇc
2836 ˇ🏀ˇ🏀ˇefg
2837 dˇ
2838 "});
2839 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2840 cx.assert_editor_state(indoc! {"
2841 ˇab ˇc
2842 ˇ🏀 ˇ🏀 ˇefg
2843 d ˇ
2844 "});
2845
2846 cx.set_state(indoc! {"
2847 a
2848 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2849 "});
2850 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2851 cx.assert_editor_state(indoc! {"
2852 a
2853 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2854 "});
2855}
2856
2857#[gpui::test]
2858async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2859 init_test(cx, |_| {});
2860
2861 let mut cx = EditorTestContext::new(cx).await;
2862 let language = Arc::new(
2863 Language::new(
2864 LanguageConfig::default(),
2865 Some(tree_sitter_rust::LANGUAGE.into()),
2866 )
2867 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2868 .unwrap(),
2869 );
2870 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2871
2872 // cursors that are already at the suggested indent level insert
2873 // a soft tab. cursors that are to the left of the suggested indent
2874 // auto-indent their line.
2875 cx.set_state(indoc! {"
2876 ˇ
2877 const a: B = (
2878 c(
2879 d(
2880 ˇ
2881 )
2882 ˇ
2883 ˇ )
2884 );
2885 "});
2886 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2887 cx.assert_editor_state(indoc! {"
2888 ˇ
2889 const a: B = (
2890 c(
2891 d(
2892 ˇ
2893 )
2894 ˇ
2895 ˇ)
2896 );
2897 "});
2898
2899 // handle auto-indent when there are multiple cursors on the same line
2900 cx.set_state(indoc! {"
2901 const a: B = (
2902 c(
2903 ˇ ˇ
2904 ˇ )
2905 );
2906 "});
2907 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2908 cx.assert_editor_state(indoc! {"
2909 const a: B = (
2910 c(
2911 ˇ
2912 ˇ)
2913 );
2914 "});
2915}
2916
2917#[gpui::test]
2918async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2919 init_test(cx, |settings| {
2920 settings.defaults.tab_size = NonZeroU32::new(4)
2921 });
2922
2923 let language = Arc::new(
2924 Language::new(
2925 LanguageConfig::default(),
2926 Some(tree_sitter_rust::LANGUAGE.into()),
2927 )
2928 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2929 .unwrap(),
2930 );
2931
2932 let mut cx = EditorTestContext::new(cx).await;
2933 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2934 cx.set_state(indoc! {"
2935 fn a() {
2936 if b {
2937 \t ˇc
2938 }
2939 }
2940 "});
2941
2942 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2943 cx.assert_editor_state(indoc! {"
2944 fn a() {
2945 if b {
2946 ˇc
2947 }
2948 }
2949 "});
2950}
2951
2952#[gpui::test]
2953async fn test_indent_outdent(cx: &mut TestAppContext) {
2954 init_test(cx, |settings| {
2955 settings.defaults.tab_size = NonZeroU32::new(4);
2956 });
2957
2958 let mut cx = EditorTestContext::new(cx).await;
2959
2960 cx.set_state(indoc! {"
2961 «oneˇ» «twoˇ»
2962 three
2963 four
2964 "});
2965 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2966 cx.assert_editor_state(indoc! {"
2967 «oneˇ» «twoˇ»
2968 three
2969 four
2970 "});
2971
2972 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2973 cx.assert_editor_state(indoc! {"
2974 «oneˇ» «twoˇ»
2975 three
2976 four
2977 "});
2978
2979 // select across line ending
2980 cx.set_state(indoc! {"
2981 one two
2982 t«hree
2983 ˇ» four
2984 "});
2985 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2986 cx.assert_editor_state(indoc! {"
2987 one two
2988 t«hree
2989 ˇ» four
2990 "});
2991
2992 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2993 cx.assert_editor_state(indoc! {"
2994 one two
2995 t«hree
2996 ˇ» four
2997 "});
2998
2999 // Ensure that indenting/outdenting works when the cursor is at column 0.
3000 cx.set_state(indoc! {"
3001 one two
3002 ˇthree
3003 four
3004 "});
3005 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3006 cx.assert_editor_state(indoc! {"
3007 one two
3008 ˇthree
3009 four
3010 "});
3011
3012 cx.set_state(indoc! {"
3013 one two
3014 ˇ three
3015 four
3016 "});
3017 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3018 cx.assert_editor_state(indoc! {"
3019 one two
3020 ˇthree
3021 four
3022 "});
3023}
3024
3025#[gpui::test]
3026async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3027 init_test(cx, |settings| {
3028 settings.defaults.hard_tabs = Some(true);
3029 });
3030
3031 let mut cx = EditorTestContext::new(cx).await;
3032
3033 // select two ranges on one line
3034 cx.set_state(indoc! {"
3035 «oneˇ» «twoˇ»
3036 three
3037 four
3038 "});
3039 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3040 cx.assert_editor_state(indoc! {"
3041 \t«oneˇ» «twoˇ»
3042 three
3043 four
3044 "});
3045 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3046 cx.assert_editor_state(indoc! {"
3047 \t\t«oneˇ» «twoˇ»
3048 three
3049 four
3050 "});
3051 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3052 cx.assert_editor_state(indoc! {"
3053 \t«oneˇ» «twoˇ»
3054 three
3055 four
3056 "});
3057 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3058 cx.assert_editor_state(indoc! {"
3059 «oneˇ» «twoˇ»
3060 three
3061 four
3062 "});
3063
3064 // select across a line ending
3065 cx.set_state(indoc! {"
3066 one two
3067 t«hree
3068 ˇ»four
3069 "});
3070 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3071 cx.assert_editor_state(indoc! {"
3072 one two
3073 \tt«hree
3074 ˇ»four
3075 "});
3076 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3077 cx.assert_editor_state(indoc! {"
3078 one two
3079 \t\tt«hree
3080 ˇ»four
3081 "});
3082 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3083 cx.assert_editor_state(indoc! {"
3084 one two
3085 \tt«hree
3086 ˇ»four
3087 "});
3088 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3089 cx.assert_editor_state(indoc! {"
3090 one two
3091 t«hree
3092 ˇ»four
3093 "});
3094
3095 // Ensure that indenting/outdenting works when the cursor is at column 0.
3096 cx.set_state(indoc! {"
3097 one two
3098 ˇthree
3099 four
3100 "});
3101 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3102 cx.assert_editor_state(indoc! {"
3103 one two
3104 ˇthree
3105 four
3106 "});
3107 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3108 cx.assert_editor_state(indoc! {"
3109 one two
3110 \tˇthree
3111 four
3112 "});
3113 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3114 cx.assert_editor_state(indoc! {"
3115 one two
3116 ˇthree
3117 four
3118 "});
3119}
3120
3121#[gpui::test]
3122fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3123 init_test(cx, |settings| {
3124 settings.languages.extend([
3125 (
3126 "TOML".into(),
3127 LanguageSettingsContent {
3128 tab_size: NonZeroU32::new(2),
3129 ..Default::default()
3130 },
3131 ),
3132 (
3133 "Rust".into(),
3134 LanguageSettingsContent {
3135 tab_size: NonZeroU32::new(4),
3136 ..Default::default()
3137 },
3138 ),
3139 ]);
3140 });
3141
3142 let toml_language = Arc::new(Language::new(
3143 LanguageConfig {
3144 name: "TOML".into(),
3145 ..Default::default()
3146 },
3147 None,
3148 ));
3149 let rust_language = Arc::new(Language::new(
3150 LanguageConfig {
3151 name: "Rust".into(),
3152 ..Default::default()
3153 },
3154 None,
3155 ));
3156
3157 let toml_buffer =
3158 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3159 let rust_buffer =
3160 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3161 let multibuffer = cx.new(|cx| {
3162 let mut multibuffer = MultiBuffer::new(ReadWrite);
3163 multibuffer.push_excerpts(
3164 toml_buffer.clone(),
3165 [ExcerptRange {
3166 context: Point::new(0, 0)..Point::new(2, 0),
3167 primary: None,
3168 }],
3169 cx,
3170 );
3171 multibuffer.push_excerpts(
3172 rust_buffer.clone(),
3173 [ExcerptRange {
3174 context: Point::new(0, 0)..Point::new(1, 0),
3175 primary: None,
3176 }],
3177 cx,
3178 );
3179 multibuffer
3180 });
3181
3182 cx.add_window(|window, cx| {
3183 let mut editor = build_editor(multibuffer, window, cx);
3184
3185 assert_eq!(
3186 editor.text(cx),
3187 indoc! {"
3188 a = 1
3189 b = 2
3190
3191 const c: usize = 3;
3192 "}
3193 );
3194
3195 select_ranges(
3196 &mut editor,
3197 indoc! {"
3198 «aˇ» = 1
3199 b = 2
3200
3201 «const c:ˇ» usize = 3;
3202 "},
3203 window,
3204 cx,
3205 );
3206
3207 editor.tab(&Tab, window, cx);
3208 assert_text_with_selections(
3209 &mut editor,
3210 indoc! {"
3211 «aˇ» = 1
3212 b = 2
3213
3214 «const c:ˇ» usize = 3;
3215 "},
3216 cx,
3217 );
3218 editor.backtab(&Backtab, window, cx);
3219 assert_text_with_selections(
3220 &mut editor,
3221 indoc! {"
3222 «aˇ» = 1
3223 b = 2
3224
3225 «const c:ˇ» usize = 3;
3226 "},
3227 cx,
3228 );
3229
3230 editor
3231 });
3232}
3233
3234#[gpui::test]
3235async fn test_backspace(cx: &mut TestAppContext) {
3236 init_test(cx, |_| {});
3237
3238 let mut cx = EditorTestContext::new(cx).await;
3239
3240 // Basic backspace
3241 cx.set_state(indoc! {"
3242 onˇe two three
3243 fou«rˇ» five six
3244 seven «ˇeight nine
3245 »ten
3246 "});
3247 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3248 cx.assert_editor_state(indoc! {"
3249 oˇe two three
3250 fouˇ five six
3251 seven ˇten
3252 "});
3253
3254 // Test backspace inside and around indents
3255 cx.set_state(indoc! {"
3256 zero
3257 ˇone
3258 ˇtwo
3259 ˇ ˇ ˇ three
3260 ˇ ˇ four
3261 "});
3262 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3263 cx.assert_editor_state(indoc! {"
3264 zero
3265 ˇone
3266 ˇtwo
3267 ˇ threeˇ four
3268 "});
3269
3270 // Test backspace with line_mode set to true
3271 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3272 cx.set_state(indoc! {"
3273 The ˇquick ˇbrown
3274 fox jumps over
3275 the lazy dog
3276 ˇThe qu«ick bˇ»rown"});
3277 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3278 cx.assert_editor_state(indoc! {"
3279 ˇfox jumps over
3280 the lazy dogˇ"});
3281}
3282
3283#[gpui::test]
3284async fn test_delete(cx: &mut TestAppContext) {
3285 init_test(cx, |_| {});
3286
3287 let mut cx = EditorTestContext::new(cx).await;
3288 cx.set_state(indoc! {"
3289 onˇe two three
3290 fou«rˇ» five six
3291 seven «ˇeight nine
3292 »ten
3293 "});
3294 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3295 cx.assert_editor_state(indoc! {"
3296 onˇ two three
3297 fouˇ five six
3298 seven ˇten
3299 "});
3300
3301 // Test backspace with line_mode set to true
3302 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3303 cx.set_state(indoc! {"
3304 The ˇquick ˇbrown
3305 fox «ˇjum»ps over
3306 the lazy dog
3307 ˇThe qu«ick bˇ»rown"});
3308 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3309 cx.assert_editor_state("ˇthe lazy dogˇ");
3310}
3311
3312#[gpui::test]
3313fn test_delete_line(cx: &mut TestAppContext) {
3314 init_test(cx, |_| {});
3315
3316 let editor = cx.add_window(|window, cx| {
3317 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3318 build_editor(buffer, window, cx)
3319 });
3320 _ = editor.update(cx, |editor, window, cx| {
3321 editor.change_selections(None, window, cx, |s| {
3322 s.select_display_ranges([
3323 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3324 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3325 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3326 ])
3327 });
3328 editor.delete_line(&DeleteLine, window, cx);
3329 assert_eq!(editor.display_text(cx), "ghi");
3330 assert_eq!(
3331 editor.selections.display_ranges(cx),
3332 vec![
3333 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3334 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3335 ]
3336 );
3337 });
3338
3339 let editor = cx.add_window(|window, cx| {
3340 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3341 build_editor(buffer, window, cx)
3342 });
3343 _ = editor.update(cx, |editor, window, cx| {
3344 editor.change_selections(None, window, cx, |s| {
3345 s.select_display_ranges([
3346 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3347 ])
3348 });
3349 editor.delete_line(&DeleteLine, window, cx);
3350 assert_eq!(editor.display_text(cx), "ghi\n");
3351 assert_eq!(
3352 editor.selections.display_ranges(cx),
3353 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3354 );
3355 });
3356}
3357
3358#[gpui::test]
3359fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3360 init_test(cx, |_| {});
3361
3362 cx.add_window(|window, cx| {
3363 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3364 let mut editor = build_editor(buffer.clone(), window, cx);
3365 let buffer = buffer.read(cx).as_singleton().unwrap();
3366
3367 assert_eq!(
3368 editor.selections.ranges::<Point>(cx),
3369 &[Point::new(0, 0)..Point::new(0, 0)]
3370 );
3371
3372 // When on single line, replace newline at end by space
3373 editor.join_lines(&JoinLines, window, cx);
3374 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3375 assert_eq!(
3376 editor.selections.ranges::<Point>(cx),
3377 &[Point::new(0, 3)..Point::new(0, 3)]
3378 );
3379
3380 // When multiple lines are selected, remove newlines that are spanned by the selection
3381 editor.change_selections(None, window, cx, |s| {
3382 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3383 });
3384 editor.join_lines(&JoinLines, window, cx);
3385 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3386 assert_eq!(
3387 editor.selections.ranges::<Point>(cx),
3388 &[Point::new(0, 11)..Point::new(0, 11)]
3389 );
3390
3391 // Undo should be transactional
3392 editor.undo(&Undo, window, cx);
3393 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3394 assert_eq!(
3395 editor.selections.ranges::<Point>(cx),
3396 &[Point::new(0, 5)..Point::new(2, 2)]
3397 );
3398
3399 // When joining an empty line don't insert a space
3400 editor.change_selections(None, window, cx, |s| {
3401 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3402 });
3403 editor.join_lines(&JoinLines, window, cx);
3404 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3405 assert_eq!(
3406 editor.selections.ranges::<Point>(cx),
3407 [Point::new(2, 3)..Point::new(2, 3)]
3408 );
3409
3410 // We can remove trailing newlines
3411 editor.join_lines(&JoinLines, window, cx);
3412 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3413 assert_eq!(
3414 editor.selections.ranges::<Point>(cx),
3415 [Point::new(2, 3)..Point::new(2, 3)]
3416 );
3417
3418 // We don't blow up on the last line
3419 editor.join_lines(&JoinLines, window, cx);
3420 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3421 assert_eq!(
3422 editor.selections.ranges::<Point>(cx),
3423 [Point::new(2, 3)..Point::new(2, 3)]
3424 );
3425
3426 // reset to test indentation
3427 editor.buffer.update(cx, |buffer, cx| {
3428 buffer.edit(
3429 [
3430 (Point::new(1, 0)..Point::new(1, 2), " "),
3431 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3432 ],
3433 None,
3434 cx,
3435 )
3436 });
3437
3438 // We remove any leading spaces
3439 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3440 editor.change_selections(None, window, cx, |s| {
3441 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3442 });
3443 editor.join_lines(&JoinLines, window, cx);
3444 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3445
3446 // We don't insert a space for a line containing only spaces
3447 editor.join_lines(&JoinLines, window, cx);
3448 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3449
3450 // We ignore any leading tabs
3451 editor.join_lines(&JoinLines, window, cx);
3452 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3453
3454 editor
3455 });
3456}
3457
3458#[gpui::test]
3459fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3460 init_test(cx, |_| {});
3461
3462 cx.add_window(|window, cx| {
3463 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3464 let mut editor = build_editor(buffer.clone(), window, cx);
3465 let buffer = buffer.read(cx).as_singleton().unwrap();
3466
3467 editor.change_selections(None, window, cx, |s| {
3468 s.select_ranges([
3469 Point::new(0, 2)..Point::new(1, 1),
3470 Point::new(1, 2)..Point::new(1, 2),
3471 Point::new(3, 1)..Point::new(3, 2),
3472 ])
3473 });
3474
3475 editor.join_lines(&JoinLines, window, cx);
3476 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3477
3478 assert_eq!(
3479 editor.selections.ranges::<Point>(cx),
3480 [
3481 Point::new(0, 7)..Point::new(0, 7),
3482 Point::new(1, 3)..Point::new(1, 3)
3483 ]
3484 );
3485 editor
3486 });
3487}
3488
3489#[gpui::test]
3490async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3491 init_test(cx, |_| {});
3492
3493 let mut cx = EditorTestContext::new(cx).await;
3494
3495 let diff_base = r#"
3496 Line 0
3497 Line 1
3498 Line 2
3499 Line 3
3500 "#
3501 .unindent();
3502
3503 cx.set_state(
3504 &r#"
3505 ˇLine 0
3506 Line 1
3507 Line 2
3508 Line 3
3509 "#
3510 .unindent(),
3511 );
3512
3513 cx.set_head_text(&diff_base);
3514 executor.run_until_parked();
3515
3516 // Join lines
3517 cx.update_editor(|editor, window, cx| {
3518 editor.join_lines(&JoinLines, window, cx);
3519 });
3520 executor.run_until_parked();
3521
3522 cx.assert_editor_state(
3523 &r#"
3524 Line 0ˇ Line 1
3525 Line 2
3526 Line 3
3527 "#
3528 .unindent(),
3529 );
3530 // Join again
3531 cx.update_editor(|editor, window, cx| {
3532 editor.join_lines(&JoinLines, window, cx);
3533 });
3534 executor.run_until_parked();
3535
3536 cx.assert_editor_state(
3537 &r#"
3538 Line 0 Line 1ˇ Line 2
3539 Line 3
3540 "#
3541 .unindent(),
3542 );
3543}
3544
3545#[gpui::test]
3546async fn test_custom_newlines_cause_no_false_positive_diffs(
3547 executor: BackgroundExecutor,
3548 cx: &mut TestAppContext,
3549) {
3550 init_test(cx, |_| {});
3551 let mut cx = EditorTestContext::new(cx).await;
3552 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3553 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3554 executor.run_until_parked();
3555
3556 cx.update_editor(|editor, window, cx| {
3557 let snapshot = editor.snapshot(window, cx);
3558 assert_eq!(
3559 snapshot
3560 .buffer_snapshot
3561 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3562 .collect::<Vec<_>>(),
3563 Vec::new(),
3564 "Should not have any diffs for files with custom newlines"
3565 );
3566 });
3567}
3568
3569#[gpui::test]
3570async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3571 init_test(cx, |_| {});
3572
3573 let mut cx = EditorTestContext::new(cx).await;
3574
3575 // Test sort_lines_case_insensitive()
3576 cx.set_state(indoc! {"
3577 «z
3578 y
3579 x
3580 Z
3581 Y
3582 Xˇ»
3583 "});
3584 cx.update_editor(|e, window, cx| {
3585 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3586 });
3587 cx.assert_editor_state(indoc! {"
3588 «x
3589 X
3590 y
3591 Y
3592 z
3593 Zˇ»
3594 "});
3595
3596 // Test reverse_lines()
3597 cx.set_state(indoc! {"
3598 «5
3599 4
3600 3
3601 2
3602 1ˇ»
3603 "});
3604 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3605 cx.assert_editor_state(indoc! {"
3606 «1
3607 2
3608 3
3609 4
3610 5ˇ»
3611 "});
3612
3613 // Skip testing shuffle_line()
3614
3615 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3616 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3617
3618 // Don't manipulate when cursor is on single line, but expand the selection
3619 cx.set_state(indoc! {"
3620 ddˇdd
3621 ccc
3622 bb
3623 a
3624 "});
3625 cx.update_editor(|e, window, cx| {
3626 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3627 });
3628 cx.assert_editor_state(indoc! {"
3629 «ddddˇ»
3630 ccc
3631 bb
3632 a
3633 "});
3634
3635 // Basic manipulate case
3636 // Start selection moves to column 0
3637 // End of selection shrinks to fit shorter line
3638 cx.set_state(indoc! {"
3639 dd«d
3640 ccc
3641 bb
3642 aaaaaˇ»
3643 "});
3644 cx.update_editor(|e, window, cx| {
3645 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3646 });
3647 cx.assert_editor_state(indoc! {"
3648 «aaaaa
3649 bb
3650 ccc
3651 dddˇ»
3652 "});
3653
3654 // Manipulate case with newlines
3655 cx.set_state(indoc! {"
3656 dd«d
3657 ccc
3658
3659 bb
3660 aaaaa
3661
3662 ˇ»
3663 "});
3664 cx.update_editor(|e, window, cx| {
3665 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3666 });
3667 cx.assert_editor_state(indoc! {"
3668 «
3669
3670 aaaaa
3671 bb
3672 ccc
3673 dddˇ»
3674
3675 "});
3676
3677 // Adding new line
3678 cx.set_state(indoc! {"
3679 aa«a
3680 bbˇ»b
3681 "});
3682 cx.update_editor(|e, window, cx| {
3683 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3684 });
3685 cx.assert_editor_state(indoc! {"
3686 «aaa
3687 bbb
3688 added_lineˇ»
3689 "});
3690
3691 // Removing line
3692 cx.set_state(indoc! {"
3693 aa«a
3694 bbbˇ»
3695 "});
3696 cx.update_editor(|e, window, cx| {
3697 e.manipulate_lines(window, cx, |lines| {
3698 lines.pop();
3699 })
3700 });
3701 cx.assert_editor_state(indoc! {"
3702 «aaaˇ»
3703 "});
3704
3705 // Removing all lines
3706 cx.set_state(indoc! {"
3707 aa«a
3708 bbbˇ»
3709 "});
3710 cx.update_editor(|e, window, cx| {
3711 e.manipulate_lines(window, cx, |lines| {
3712 lines.drain(..);
3713 })
3714 });
3715 cx.assert_editor_state(indoc! {"
3716 ˇ
3717 "});
3718}
3719
3720#[gpui::test]
3721async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3722 init_test(cx, |_| {});
3723
3724 let mut cx = EditorTestContext::new(cx).await;
3725
3726 // Consider continuous selection as single selection
3727 cx.set_state(indoc! {"
3728 Aaa«aa
3729 cˇ»c«c
3730 bb
3731 aaaˇ»aa
3732 "});
3733 cx.update_editor(|e, window, cx| {
3734 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3735 });
3736 cx.assert_editor_state(indoc! {"
3737 «Aaaaa
3738 ccc
3739 bb
3740 aaaaaˇ»
3741 "});
3742
3743 cx.set_state(indoc! {"
3744 Aaa«aa
3745 cˇ»c«c
3746 bb
3747 aaaˇ»aa
3748 "});
3749 cx.update_editor(|e, window, cx| {
3750 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3751 });
3752 cx.assert_editor_state(indoc! {"
3753 «Aaaaa
3754 ccc
3755 bbˇ»
3756 "});
3757
3758 // Consider non continuous selection as distinct dedup operations
3759 cx.set_state(indoc! {"
3760 «aaaaa
3761 bb
3762 aaaaa
3763 aaaaaˇ»
3764
3765 aaa«aaˇ»
3766 "});
3767 cx.update_editor(|e, window, cx| {
3768 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3769 });
3770 cx.assert_editor_state(indoc! {"
3771 «aaaaa
3772 bbˇ»
3773
3774 «aaaaaˇ»
3775 "});
3776}
3777
3778#[gpui::test]
3779async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3780 init_test(cx, |_| {});
3781
3782 let mut cx = EditorTestContext::new(cx).await;
3783
3784 cx.set_state(indoc! {"
3785 «Aaa
3786 aAa
3787 Aaaˇ»
3788 "});
3789 cx.update_editor(|e, window, cx| {
3790 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3791 });
3792 cx.assert_editor_state(indoc! {"
3793 «Aaa
3794 aAaˇ»
3795 "});
3796
3797 cx.set_state(indoc! {"
3798 «Aaa
3799 aAa
3800 aaAˇ»
3801 "});
3802 cx.update_editor(|e, window, cx| {
3803 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3804 });
3805 cx.assert_editor_state(indoc! {"
3806 «Aaaˇ»
3807 "});
3808}
3809
3810#[gpui::test]
3811async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3812 init_test(cx, |_| {});
3813
3814 let mut cx = EditorTestContext::new(cx).await;
3815
3816 // Manipulate with multiple selections on a single line
3817 cx.set_state(indoc! {"
3818 dd«dd
3819 cˇ»c«c
3820 bb
3821 aaaˇ»aa
3822 "});
3823 cx.update_editor(|e, window, cx| {
3824 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3825 });
3826 cx.assert_editor_state(indoc! {"
3827 «aaaaa
3828 bb
3829 ccc
3830 ddddˇ»
3831 "});
3832
3833 // Manipulate with multiple disjoin selections
3834 cx.set_state(indoc! {"
3835 5«
3836 4
3837 3
3838 2
3839 1ˇ»
3840
3841 dd«dd
3842 ccc
3843 bb
3844 aaaˇ»aa
3845 "});
3846 cx.update_editor(|e, window, cx| {
3847 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3848 });
3849 cx.assert_editor_state(indoc! {"
3850 «1
3851 2
3852 3
3853 4
3854 5ˇ»
3855
3856 «aaaaa
3857 bb
3858 ccc
3859 ddddˇ»
3860 "});
3861
3862 // Adding lines on each selection
3863 cx.set_state(indoc! {"
3864 2«
3865 1ˇ»
3866
3867 bb«bb
3868 aaaˇ»aa
3869 "});
3870 cx.update_editor(|e, window, cx| {
3871 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3872 });
3873 cx.assert_editor_state(indoc! {"
3874 «2
3875 1
3876 added lineˇ»
3877
3878 «bbbb
3879 aaaaa
3880 added lineˇ»
3881 "});
3882
3883 // Removing lines on each selection
3884 cx.set_state(indoc! {"
3885 2«
3886 1ˇ»
3887
3888 bb«bb
3889 aaaˇ»aa
3890 "});
3891 cx.update_editor(|e, window, cx| {
3892 e.manipulate_lines(window, cx, |lines| {
3893 lines.pop();
3894 })
3895 });
3896 cx.assert_editor_state(indoc! {"
3897 «2ˇ»
3898
3899 «bbbbˇ»
3900 "});
3901}
3902
3903#[gpui::test]
3904async fn test_manipulate_text(cx: &mut TestAppContext) {
3905 init_test(cx, |_| {});
3906
3907 let mut cx = EditorTestContext::new(cx).await;
3908
3909 // Test convert_to_upper_case()
3910 cx.set_state(indoc! {"
3911 «hello worldˇ»
3912 "});
3913 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3914 cx.assert_editor_state(indoc! {"
3915 «HELLO WORLDˇ»
3916 "});
3917
3918 // Test convert_to_lower_case()
3919 cx.set_state(indoc! {"
3920 «HELLO WORLDˇ»
3921 "});
3922 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3923 cx.assert_editor_state(indoc! {"
3924 «hello worldˇ»
3925 "});
3926
3927 // Test multiple line, single selection case
3928 cx.set_state(indoc! {"
3929 «The quick brown
3930 fox jumps over
3931 the lazy dogˇ»
3932 "});
3933 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3934 cx.assert_editor_state(indoc! {"
3935 «The Quick Brown
3936 Fox Jumps Over
3937 The Lazy Dogˇ»
3938 "});
3939
3940 // Test multiple line, single selection case
3941 cx.set_state(indoc! {"
3942 «The quick brown
3943 fox jumps over
3944 the lazy dogˇ»
3945 "});
3946 cx.update_editor(|e, window, cx| {
3947 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3948 });
3949 cx.assert_editor_state(indoc! {"
3950 «TheQuickBrown
3951 FoxJumpsOver
3952 TheLazyDogˇ»
3953 "});
3954
3955 // From here on out, test more complex cases of manipulate_text()
3956
3957 // Test no selection case - should affect words cursors are in
3958 // Cursor at beginning, middle, and end of word
3959 cx.set_state(indoc! {"
3960 ˇhello big beauˇtiful worldˇ
3961 "});
3962 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3963 cx.assert_editor_state(indoc! {"
3964 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3965 "});
3966
3967 // Test multiple selections on a single line and across multiple lines
3968 cx.set_state(indoc! {"
3969 «Theˇ» quick «brown
3970 foxˇ» jumps «overˇ»
3971 the «lazyˇ» dog
3972 "});
3973 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3974 cx.assert_editor_state(indoc! {"
3975 «THEˇ» quick «BROWN
3976 FOXˇ» jumps «OVERˇ»
3977 the «LAZYˇ» dog
3978 "});
3979
3980 // Test case where text length grows
3981 cx.set_state(indoc! {"
3982 «tschüߡ»
3983 "});
3984 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3985 cx.assert_editor_state(indoc! {"
3986 «TSCHÜSSˇ»
3987 "});
3988
3989 // Test to make sure we don't crash when text shrinks
3990 cx.set_state(indoc! {"
3991 aaa_bbbˇ
3992 "});
3993 cx.update_editor(|e, window, cx| {
3994 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3995 });
3996 cx.assert_editor_state(indoc! {"
3997 «aaaBbbˇ»
3998 "});
3999
4000 // Test to make sure we all aware of the fact that each word can grow and shrink
4001 // Final selections should be aware of this fact
4002 cx.set_state(indoc! {"
4003 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4004 "});
4005 cx.update_editor(|e, window, cx| {
4006 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4007 });
4008 cx.assert_editor_state(indoc! {"
4009 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4010 "});
4011
4012 cx.set_state(indoc! {"
4013 «hElLo, WoRld!ˇ»
4014 "});
4015 cx.update_editor(|e, window, cx| {
4016 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4017 });
4018 cx.assert_editor_state(indoc! {"
4019 «HeLlO, wOrLD!ˇ»
4020 "});
4021}
4022
4023#[gpui::test]
4024fn test_duplicate_line(cx: &mut TestAppContext) {
4025 init_test(cx, |_| {});
4026
4027 let editor = cx.add_window(|window, cx| {
4028 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4029 build_editor(buffer, window, cx)
4030 });
4031 _ = editor.update(cx, |editor, window, cx| {
4032 editor.change_selections(None, window, cx, |s| {
4033 s.select_display_ranges([
4034 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4035 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4036 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4037 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4038 ])
4039 });
4040 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4041 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4042 assert_eq!(
4043 editor.selections.display_ranges(cx),
4044 vec![
4045 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4046 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4047 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4048 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4049 ]
4050 );
4051 });
4052
4053 let editor = cx.add_window(|window, cx| {
4054 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4055 build_editor(buffer, window, cx)
4056 });
4057 _ = editor.update(cx, |editor, window, cx| {
4058 editor.change_selections(None, window, cx, |s| {
4059 s.select_display_ranges([
4060 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4061 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4062 ])
4063 });
4064 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4065 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4066 assert_eq!(
4067 editor.selections.display_ranges(cx),
4068 vec![
4069 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4070 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4071 ]
4072 );
4073 });
4074
4075 // With `move_upwards` the selections stay in place, except for
4076 // the lines inserted above them
4077 let editor = cx.add_window(|window, cx| {
4078 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4079 build_editor(buffer, window, cx)
4080 });
4081 _ = editor.update(cx, |editor, window, cx| {
4082 editor.change_selections(None, window, cx, |s| {
4083 s.select_display_ranges([
4084 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4085 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4086 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4087 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4088 ])
4089 });
4090 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4091 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4092 assert_eq!(
4093 editor.selections.display_ranges(cx),
4094 vec![
4095 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4096 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4097 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4098 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4099 ]
4100 );
4101 });
4102
4103 let editor = cx.add_window(|window, cx| {
4104 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4105 build_editor(buffer, window, cx)
4106 });
4107 _ = editor.update(cx, |editor, window, cx| {
4108 editor.change_selections(None, window, cx, |s| {
4109 s.select_display_ranges([
4110 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4111 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4112 ])
4113 });
4114 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4115 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4116 assert_eq!(
4117 editor.selections.display_ranges(cx),
4118 vec![
4119 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4120 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4121 ]
4122 );
4123 });
4124
4125 let editor = cx.add_window(|window, cx| {
4126 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4127 build_editor(buffer, window, cx)
4128 });
4129 _ = editor.update(cx, |editor, window, cx| {
4130 editor.change_selections(None, window, cx, |s| {
4131 s.select_display_ranges([
4132 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4133 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4134 ])
4135 });
4136 editor.duplicate_selection(&DuplicateSelection, window, cx);
4137 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4138 assert_eq!(
4139 editor.selections.display_ranges(cx),
4140 vec![
4141 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4142 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4143 ]
4144 );
4145 });
4146}
4147
4148#[gpui::test]
4149fn test_move_line_up_down(cx: &mut TestAppContext) {
4150 init_test(cx, |_| {});
4151
4152 let editor = cx.add_window(|window, cx| {
4153 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4154 build_editor(buffer, window, cx)
4155 });
4156 _ = editor.update(cx, |editor, window, cx| {
4157 editor.fold_creases(
4158 vec![
4159 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4160 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4161 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4162 ],
4163 true,
4164 window,
4165 cx,
4166 );
4167 editor.change_selections(None, window, cx, |s| {
4168 s.select_display_ranges([
4169 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4170 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4171 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4172 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4173 ])
4174 });
4175 assert_eq!(
4176 editor.display_text(cx),
4177 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4178 );
4179
4180 editor.move_line_up(&MoveLineUp, window, cx);
4181 assert_eq!(
4182 editor.display_text(cx),
4183 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4184 );
4185 assert_eq!(
4186 editor.selections.display_ranges(cx),
4187 vec![
4188 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4189 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4190 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4191 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4192 ]
4193 );
4194 });
4195
4196 _ = editor.update(cx, |editor, window, cx| {
4197 editor.move_line_down(&MoveLineDown, window, cx);
4198 assert_eq!(
4199 editor.display_text(cx),
4200 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4201 );
4202 assert_eq!(
4203 editor.selections.display_ranges(cx),
4204 vec![
4205 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4206 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4207 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4208 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4209 ]
4210 );
4211 });
4212
4213 _ = editor.update(cx, |editor, window, cx| {
4214 editor.move_line_down(&MoveLineDown, window, cx);
4215 assert_eq!(
4216 editor.display_text(cx),
4217 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4218 );
4219 assert_eq!(
4220 editor.selections.display_ranges(cx),
4221 vec![
4222 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4223 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4224 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4225 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4226 ]
4227 );
4228 });
4229
4230 _ = editor.update(cx, |editor, window, cx| {
4231 editor.move_line_up(&MoveLineUp, window, cx);
4232 assert_eq!(
4233 editor.display_text(cx),
4234 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4235 );
4236 assert_eq!(
4237 editor.selections.display_ranges(cx),
4238 vec![
4239 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4240 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4241 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4242 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4243 ]
4244 );
4245 });
4246}
4247
4248#[gpui::test]
4249fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4250 init_test(cx, |_| {});
4251
4252 let editor = cx.add_window(|window, cx| {
4253 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4254 build_editor(buffer, window, cx)
4255 });
4256 _ = editor.update(cx, |editor, window, cx| {
4257 let snapshot = editor.buffer.read(cx).snapshot(cx);
4258 editor.insert_blocks(
4259 [BlockProperties {
4260 style: BlockStyle::Fixed,
4261 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4262 height: 1,
4263 render: Arc::new(|_| div().into_any()),
4264 priority: 0,
4265 }],
4266 Some(Autoscroll::fit()),
4267 cx,
4268 );
4269 editor.change_selections(None, window, cx, |s| {
4270 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4271 });
4272 editor.move_line_down(&MoveLineDown, window, cx);
4273 });
4274}
4275
4276#[gpui::test]
4277async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4278 init_test(cx, |_| {});
4279
4280 let mut cx = EditorTestContext::new(cx).await;
4281 cx.set_state(
4282 &"
4283 ˇzero
4284 one
4285 two
4286 three
4287 four
4288 five
4289 "
4290 .unindent(),
4291 );
4292
4293 // Create a four-line block that replaces three lines of text.
4294 cx.update_editor(|editor, window, cx| {
4295 let snapshot = editor.snapshot(window, cx);
4296 let snapshot = &snapshot.buffer_snapshot;
4297 let placement = BlockPlacement::Replace(
4298 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4299 );
4300 editor.insert_blocks(
4301 [BlockProperties {
4302 placement,
4303 height: 4,
4304 style: BlockStyle::Sticky,
4305 render: Arc::new(|_| gpui::div().into_any_element()),
4306 priority: 0,
4307 }],
4308 None,
4309 cx,
4310 );
4311 });
4312
4313 // Move down so that the cursor touches the block.
4314 cx.update_editor(|editor, window, cx| {
4315 editor.move_down(&Default::default(), window, cx);
4316 });
4317 cx.assert_editor_state(
4318 &"
4319 zero
4320 «one
4321 two
4322 threeˇ»
4323 four
4324 five
4325 "
4326 .unindent(),
4327 );
4328
4329 // Move down past the block.
4330 cx.update_editor(|editor, window, cx| {
4331 editor.move_down(&Default::default(), window, cx);
4332 });
4333 cx.assert_editor_state(
4334 &"
4335 zero
4336 one
4337 two
4338 three
4339 ˇfour
4340 five
4341 "
4342 .unindent(),
4343 );
4344}
4345
4346#[gpui::test]
4347fn test_transpose(cx: &mut TestAppContext) {
4348 init_test(cx, |_| {});
4349
4350 _ = cx.add_window(|window, cx| {
4351 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4352 editor.set_style(EditorStyle::default(), window, cx);
4353 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4354 editor.transpose(&Default::default(), window, cx);
4355 assert_eq!(editor.text(cx), "bac");
4356 assert_eq!(editor.selections.ranges(cx), [2..2]);
4357
4358 editor.transpose(&Default::default(), window, cx);
4359 assert_eq!(editor.text(cx), "bca");
4360 assert_eq!(editor.selections.ranges(cx), [3..3]);
4361
4362 editor.transpose(&Default::default(), window, cx);
4363 assert_eq!(editor.text(cx), "bac");
4364 assert_eq!(editor.selections.ranges(cx), [3..3]);
4365
4366 editor
4367 });
4368
4369 _ = cx.add_window(|window, cx| {
4370 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4371 editor.set_style(EditorStyle::default(), window, cx);
4372 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4373 editor.transpose(&Default::default(), window, cx);
4374 assert_eq!(editor.text(cx), "acb\nde");
4375 assert_eq!(editor.selections.ranges(cx), [3..3]);
4376
4377 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4378 editor.transpose(&Default::default(), window, cx);
4379 assert_eq!(editor.text(cx), "acbd\ne");
4380 assert_eq!(editor.selections.ranges(cx), [5..5]);
4381
4382 editor.transpose(&Default::default(), window, cx);
4383 assert_eq!(editor.text(cx), "acbde\n");
4384 assert_eq!(editor.selections.ranges(cx), [6..6]);
4385
4386 editor.transpose(&Default::default(), window, cx);
4387 assert_eq!(editor.text(cx), "acbd\ne");
4388 assert_eq!(editor.selections.ranges(cx), [6..6]);
4389
4390 editor
4391 });
4392
4393 _ = cx.add_window(|window, cx| {
4394 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4395 editor.set_style(EditorStyle::default(), window, cx);
4396 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4397 editor.transpose(&Default::default(), window, cx);
4398 assert_eq!(editor.text(cx), "bacd\ne");
4399 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4400
4401 editor.transpose(&Default::default(), window, cx);
4402 assert_eq!(editor.text(cx), "bcade\n");
4403 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4404
4405 editor.transpose(&Default::default(), window, cx);
4406 assert_eq!(editor.text(cx), "bcda\ne");
4407 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4408
4409 editor.transpose(&Default::default(), window, cx);
4410 assert_eq!(editor.text(cx), "bcade\n");
4411 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4412
4413 editor.transpose(&Default::default(), window, cx);
4414 assert_eq!(editor.text(cx), "bcaed\n");
4415 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4416
4417 editor
4418 });
4419
4420 _ = cx.add_window(|window, cx| {
4421 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4422 editor.set_style(EditorStyle::default(), window, cx);
4423 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4424 editor.transpose(&Default::default(), window, cx);
4425 assert_eq!(editor.text(cx), "🏀🍐✋");
4426 assert_eq!(editor.selections.ranges(cx), [8..8]);
4427
4428 editor.transpose(&Default::default(), window, cx);
4429 assert_eq!(editor.text(cx), "🏀✋🍐");
4430 assert_eq!(editor.selections.ranges(cx), [11..11]);
4431
4432 editor.transpose(&Default::default(), window, cx);
4433 assert_eq!(editor.text(cx), "🏀🍐✋");
4434 assert_eq!(editor.selections.ranges(cx), [11..11]);
4435
4436 editor
4437 });
4438}
4439
4440#[gpui::test]
4441async fn test_rewrap(cx: &mut TestAppContext) {
4442 init_test(cx, |settings| {
4443 settings.languages.extend([
4444 (
4445 "Markdown".into(),
4446 LanguageSettingsContent {
4447 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4448 ..Default::default()
4449 },
4450 ),
4451 (
4452 "Plain Text".into(),
4453 LanguageSettingsContent {
4454 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4455 ..Default::default()
4456 },
4457 ),
4458 ])
4459 });
4460
4461 let mut cx = EditorTestContext::new(cx).await;
4462
4463 let language_with_c_comments = Arc::new(Language::new(
4464 LanguageConfig {
4465 line_comments: vec!["// ".into()],
4466 ..LanguageConfig::default()
4467 },
4468 None,
4469 ));
4470 let language_with_pound_comments = Arc::new(Language::new(
4471 LanguageConfig {
4472 line_comments: vec!["# ".into()],
4473 ..LanguageConfig::default()
4474 },
4475 None,
4476 ));
4477 let markdown_language = Arc::new(Language::new(
4478 LanguageConfig {
4479 name: "Markdown".into(),
4480 ..LanguageConfig::default()
4481 },
4482 None,
4483 ));
4484 let language_with_doc_comments = Arc::new(Language::new(
4485 LanguageConfig {
4486 line_comments: vec!["// ".into(), "/// ".into()],
4487 ..LanguageConfig::default()
4488 },
4489 Some(tree_sitter_rust::LANGUAGE.into()),
4490 ));
4491
4492 let plaintext_language = Arc::new(Language::new(
4493 LanguageConfig {
4494 name: "Plain Text".into(),
4495 ..LanguageConfig::default()
4496 },
4497 None,
4498 ));
4499
4500 assert_rewrap(
4501 indoc! {"
4502 // ˇ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.
4503 "},
4504 indoc! {"
4505 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4506 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4507 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4508 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4509 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4510 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4511 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4512 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4513 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4514 // porttitor id. Aliquam id accumsan eros.
4515 "},
4516 language_with_c_comments.clone(),
4517 &mut cx,
4518 );
4519
4520 // Test that rewrapping works inside of a selection
4521 assert_rewrap(
4522 indoc! {"
4523 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. 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.ˇ»
4524 "},
4525 indoc! {"
4526 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4527 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4528 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4529 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4530 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4531 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4532 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4533 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4534 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4535 // porttitor id. Aliquam id accumsan eros.ˇ»
4536 "},
4537 language_with_c_comments.clone(),
4538 &mut cx,
4539 );
4540
4541 // Test that cursors that expand to the same region are collapsed.
4542 assert_rewrap(
4543 indoc! {"
4544 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4545 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4546 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4547 // ˇ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.
4548 "},
4549 indoc! {"
4550 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4551 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4552 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4553 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4554 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4555 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4556 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4557 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4558 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4559 // porttitor id. Aliquam id accumsan eros.
4560 "},
4561 language_with_c_comments.clone(),
4562 &mut cx,
4563 );
4564
4565 // Test that non-contiguous selections are treated separately.
4566 assert_rewrap(
4567 indoc! {"
4568 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4569 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4570 //
4571 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4572 // ˇ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.
4573 "},
4574 indoc! {"
4575 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4576 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4577 // auctor, eu lacinia sapien scelerisque.
4578 //
4579 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4580 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4581 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4582 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4583 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4584 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4585 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4586 "},
4587 language_with_c_comments.clone(),
4588 &mut cx,
4589 );
4590
4591 // Test that different comment prefixes are supported.
4592 assert_rewrap(
4593 indoc! {"
4594 # ˇ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.
4595 "},
4596 indoc! {"
4597 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4598 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4599 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4600 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4601 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4602 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4603 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4604 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4605 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4606 # accumsan eros.
4607 "},
4608 language_with_pound_comments.clone(),
4609 &mut cx,
4610 );
4611
4612 // Test that rewrapping is ignored outside of comments in most languages.
4613 assert_rewrap(
4614 indoc! {"
4615 /// Adds two numbers.
4616 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4617 fn add(a: u32, b: u32) -> u32 {
4618 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ˇ
4619 }
4620 "},
4621 indoc! {"
4622 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4623 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4624 fn add(a: u32, b: u32) -> u32 {
4625 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ˇ
4626 }
4627 "},
4628 language_with_doc_comments.clone(),
4629 &mut cx,
4630 );
4631
4632 // Test that rewrapping works in Markdown and Plain Text languages.
4633 assert_rewrap(
4634 indoc! {"
4635 # Hello
4636
4637 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.
4638 "},
4639 indoc! {"
4640 # Hello
4641
4642 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4643 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4644 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4645 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4646 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4647 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4648 Integer sit amet scelerisque nisi.
4649 "},
4650 markdown_language,
4651 &mut cx,
4652 );
4653
4654 assert_rewrap(
4655 indoc! {"
4656 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.
4657 "},
4658 indoc! {"
4659 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4660 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4661 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4662 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4663 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4664 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4665 Integer sit amet scelerisque nisi.
4666 "},
4667 plaintext_language,
4668 &mut cx,
4669 );
4670
4671 // Test rewrapping unaligned comments in a selection.
4672 assert_rewrap(
4673 indoc! {"
4674 fn foo() {
4675 if true {
4676 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4677 // Praesent semper egestas tellus id dignissim.ˇ»
4678 do_something();
4679 } else {
4680 //
4681 }
4682 }
4683 "},
4684 indoc! {"
4685 fn foo() {
4686 if true {
4687 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4688 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4689 // egestas tellus id dignissim.ˇ»
4690 do_something();
4691 } else {
4692 //
4693 }
4694 }
4695 "},
4696 language_with_doc_comments.clone(),
4697 &mut cx,
4698 );
4699
4700 assert_rewrap(
4701 indoc! {"
4702 fn foo() {
4703 if true {
4704 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4705 // Praesent semper egestas tellus id dignissim.»
4706 do_something();
4707 } else {
4708 //
4709 }
4710
4711 }
4712 "},
4713 indoc! {"
4714 fn foo() {
4715 if true {
4716 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4717 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4718 // egestas tellus id dignissim.»
4719 do_something();
4720 } else {
4721 //
4722 }
4723
4724 }
4725 "},
4726 language_with_doc_comments.clone(),
4727 &mut cx,
4728 );
4729
4730 #[track_caller]
4731 fn assert_rewrap(
4732 unwrapped_text: &str,
4733 wrapped_text: &str,
4734 language: Arc<Language>,
4735 cx: &mut EditorTestContext,
4736 ) {
4737 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4738 cx.set_state(unwrapped_text);
4739 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4740 cx.assert_editor_state(wrapped_text);
4741 }
4742}
4743
4744#[gpui::test]
4745async fn test_hard_wrap(cx: &mut TestAppContext) {
4746 init_test(cx, |_| {});
4747 let mut cx = EditorTestContext::new(cx).await;
4748
4749 cx.update_editor(|editor, _, cx| {
4750 editor.set_hard_wrap(Some(14), cx);
4751 });
4752
4753 cx.set_state(indoc!(
4754 "
4755 one two three ˇ
4756 "
4757 ));
4758 cx.simulate_input("four");
4759 cx.run_until_parked();
4760
4761 cx.assert_editor_state(indoc!(
4762 "
4763 one two three
4764 fourˇ
4765 "
4766 ));
4767}
4768
4769#[gpui::test]
4770async fn test_clipboard(cx: &mut TestAppContext) {
4771 init_test(cx, |_| {});
4772
4773 let mut cx = EditorTestContext::new(cx).await;
4774
4775 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4776 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4777 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4778
4779 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4780 cx.set_state("two ˇfour ˇsix ˇ");
4781 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4782 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4783
4784 // Paste again but with only two cursors. Since the number of cursors doesn't
4785 // match the number of slices in the clipboard, the entire clipboard text
4786 // is pasted at each cursor.
4787 cx.set_state("ˇtwo one✅ four three six five ˇ");
4788 cx.update_editor(|e, window, cx| {
4789 e.handle_input("( ", window, cx);
4790 e.paste(&Paste, window, cx);
4791 e.handle_input(") ", window, cx);
4792 });
4793 cx.assert_editor_state(
4794 &([
4795 "( one✅ ",
4796 "three ",
4797 "five ) ˇtwo one✅ four three six five ( one✅ ",
4798 "three ",
4799 "five ) ˇ",
4800 ]
4801 .join("\n")),
4802 );
4803
4804 // Cut with three selections, one of which is full-line.
4805 cx.set_state(indoc! {"
4806 1«2ˇ»3
4807 4ˇ567
4808 «8ˇ»9"});
4809 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4810 cx.assert_editor_state(indoc! {"
4811 1ˇ3
4812 ˇ9"});
4813
4814 // Paste with three selections, noticing how the copied selection that was full-line
4815 // gets inserted before the second cursor.
4816 cx.set_state(indoc! {"
4817 1ˇ3
4818 9ˇ
4819 «oˇ»ne"});
4820 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4821 cx.assert_editor_state(indoc! {"
4822 12ˇ3
4823 4567
4824 9ˇ
4825 8ˇne"});
4826
4827 // Copy with a single cursor only, which writes the whole line into the clipboard.
4828 cx.set_state(indoc! {"
4829 The quick brown
4830 fox juˇmps over
4831 the lazy dog"});
4832 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4833 assert_eq!(
4834 cx.read_from_clipboard()
4835 .and_then(|item| item.text().as_deref().map(str::to_string)),
4836 Some("fox jumps over\n".to_string())
4837 );
4838
4839 // Paste with three selections, noticing how the copied full-line selection is inserted
4840 // before the empty selections but replaces the selection that is non-empty.
4841 cx.set_state(indoc! {"
4842 Tˇhe quick brown
4843 «foˇ»x jumps over
4844 tˇhe lazy dog"});
4845 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4846 cx.assert_editor_state(indoc! {"
4847 fox jumps over
4848 Tˇhe quick brown
4849 fox jumps over
4850 ˇx jumps over
4851 fox jumps over
4852 tˇhe lazy dog"});
4853}
4854
4855#[gpui::test]
4856async fn test_paste_multiline(cx: &mut TestAppContext) {
4857 init_test(cx, |_| {});
4858
4859 let mut cx = EditorTestContext::new(cx).await;
4860 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4861
4862 // Cut an indented block, without the leading whitespace.
4863 cx.set_state(indoc! {"
4864 const a: B = (
4865 c(),
4866 «d(
4867 e,
4868 f
4869 )ˇ»
4870 );
4871 "});
4872 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4873 cx.assert_editor_state(indoc! {"
4874 const a: B = (
4875 c(),
4876 ˇ
4877 );
4878 "});
4879
4880 // Paste it at the same position.
4881 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4882 cx.assert_editor_state(indoc! {"
4883 const a: B = (
4884 c(),
4885 d(
4886 e,
4887 f
4888 )ˇ
4889 );
4890 "});
4891
4892 // Paste it at a line with a lower indent level.
4893 cx.set_state(indoc! {"
4894 ˇ
4895 const a: B = (
4896 c(),
4897 );
4898 "});
4899 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4900 cx.assert_editor_state(indoc! {"
4901 d(
4902 e,
4903 f
4904 )ˇ
4905 const a: B = (
4906 c(),
4907 );
4908 "});
4909
4910 // Cut an indented block, with the leading whitespace.
4911 cx.set_state(indoc! {"
4912 const a: B = (
4913 c(),
4914 « d(
4915 e,
4916 f
4917 )
4918 ˇ»);
4919 "});
4920 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4921 cx.assert_editor_state(indoc! {"
4922 const a: B = (
4923 c(),
4924 ˇ);
4925 "});
4926
4927 // Paste it at the same position.
4928 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4929 cx.assert_editor_state(indoc! {"
4930 const a: B = (
4931 c(),
4932 d(
4933 e,
4934 f
4935 )
4936 ˇ);
4937 "});
4938
4939 // Paste it at a line with a higher indent level.
4940 cx.set_state(indoc! {"
4941 const a: B = (
4942 c(),
4943 d(
4944 e,
4945 fˇ
4946 )
4947 );
4948 "});
4949 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4950 cx.assert_editor_state(indoc! {"
4951 const a: B = (
4952 c(),
4953 d(
4954 e,
4955 f d(
4956 e,
4957 f
4958 )
4959 ˇ
4960 )
4961 );
4962 "});
4963
4964 // Copy an indented block, starting mid-line
4965 cx.set_state(indoc! {"
4966 const a: B = (
4967 c(),
4968 somethin«g(
4969 e,
4970 f
4971 )ˇ»
4972 );
4973 "});
4974 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4975
4976 // Paste it on a line with a lower indent level
4977 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4978 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4979 cx.assert_editor_state(indoc! {"
4980 const a: B = (
4981 c(),
4982 something(
4983 e,
4984 f
4985 )
4986 );
4987 g(
4988 e,
4989 f
4990 )ˇ"});
4991}
4992
4993#[gpui::test]
4994async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4995 init_test(cx, |_| {});
4996
4997 cx.write_to_clipboard(ClipboardItem::new_string(
4998 " d(\n e\n );\n".into(),
4999 ));
5000
5001 let mut cx = EditorTestContext::new(cx).await;
5002 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5003
5004 cx.set_state(indoc! {"
5005 fn a() {
5006 b();
5007 if c() {
5008 ˇ
5009 }
5010 }
5011 "});
5012
5013 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5014 cx.assert_editor_state(indoc! {"
5015 fn a() {
5016 b();
5017 if c() {
5018 d(
5019 e
5020 );
5021 ˇ
5022 }
5023 }
5024 "});
5025
5026 cx.set_state(indoc! {"
5027 fn a() {
5028 b();
5029 ˇ
5030 }
5031 "});
5032
5033 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5034 cx.assert_editor_state(indoc! {"
5035 fn a() {
5036 b();
5037 d(
5038 e
5039 );
5040 ˇ
5041 }
5042 "});
5043}
5044
5045#[gpui::test]
5046fn test_select_all(cx: &mut TestAppContext) {
5047 init_test(cx, |_| {});
5048
5049 let editor = cx.add_window(|window, cx| {
5050 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5051 build_editor(buffer, window, cx)
5052 });
5053 _ = editor.update(cx, |editor, window, cx| {
5054 editor.select_all(&SelectAll, window, cx);
5055 assert_eq!(
5056 editor.selections.display_ranges(cx),
5057 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5058 );
5059 });
5060}
5061
5062#[gpui::test]
5063fn test_select_line(cx: &mut TestAppContext) {
5064 init_test(cx, |_| {});
5065
5066 let editor = cx.add_window(|window, cx| {
5067 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5068 build_editor(buffer, window, cx)
5069 });
5070 _ = editor.update(cx, |editor, window, cx| {
5071 editor.change_selections(None, window, cx, |s| {
5072 s.select_display_ranges([
5073 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5074 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5075 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5076 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5077 ])
5078 });
5079 editor.select_line(&SelectLine, window, cx);
5080 assert_eq!(
5081 editor.selections.display_ranges(cx),
5082 vec![
5083 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5084 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5085 ]
5086 );
5087 });
5088
5089 _ = editor.update(cx, |editor, window, cx| {
5090 editor.select_line(&SelectLine, window, cx);
5091 assert_eq!(
5092 editor.selections.display_ranges(cx),
5093 vec![
5094 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5095 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5096 ]
5097 );
5098 });
5099
5100 _ = editor.update(cx, |editor, window, cx| {
5101 editor.select_line(&SelectLine, window, cx);
5102 assert_eq!(
5103 editor.selections.display_ranges(cx),
5104 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5105 );
5106 });
5107}
5108
5109#[gpui::test]
5110async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5111 init_test(cx, |_| {});
5112 let mut cx = EditorTestContext::new(cx).await;
5113
5114 #[track_caller]
5115 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5116 cx.set_state(initial_state);
5117 cx.update_editor(|e, window, cx| {
5118 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5119 });
5120 cx.assert_editor_state(expected_state);
5121 }
5122
5123 // Selection starts and ends at the middle of lines, left-to-right
5124 test(
5125 &mut cx,
5126 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5127 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5128 );
5129 // Same thing, right-to-left
5130 test(
5131 &mut cx,
5132 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5133 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5134 );
5135
5136 // Whole buffer, left-to-right, last line *doesn't* end with newline
5137 test(
5138 &mut cx,
5139 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5140 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5141 );
5142 // Same thing, right-to-left
5143 test(
5144 &mut cx,
5145 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5146 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5147 );
5148
5149 // Whole buffer, left-to-right, last line ends with newline
5150 test(
5151 &mut cx,
5152 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5153 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5154 );
5155 // Same thing, right-to-left
5156 test(
5157 &mut cx,
5158 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5159 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5160 );
5161
5162 // Starts at the end of a line, ends at the start of another
5163 test(
5164 &mut cx,
5165 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5166 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5167 );
5168}
5169
5170#[gpui::test]
5171async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5172 init_test(cx, |_| {});
5173
5174 let editor = cx.add_window(|window, cx| {
5175 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5176 build_editor(buffer, window, cx)
5177 });
5178
5179 // setup
5180 _ = editor.update(cx, |editor, window, cx| {
5181 editor.fold_creases(
5182 vec![
5183 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5184 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5185 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5186 ],
5187 true,
5188 window,
5189 cx,
5190 );
5191 assert_eq!(
5192 editor.display_text(cx),
5193 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5194 );
5195 });
5196
5197 _ = editor.update(cx, |editor, window, cx| {
5198 editor.change_selections(None, window, cx, |s| {
5199 s.select_display_ranges([
5200 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5201 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5202 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5203 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5204 ])
5205 });
5206 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5207 assert_eq!(
5208 editor.display_text(cx),
5209 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5210 );
5211 });
5212 EditorTestContext::for_editor(editor, cx)
5213 .await
5214 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5215
5216 _ = editor.update(cx, |editor, window, cx| {
5217 editor.change_selections(None, window, cx, |s| {
5218 s.select_display_ranges([
5219 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5220 ])
5221 });
5222 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5223 assert_eq!(
5224 editor.display_text(cx),
5225 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5226 );
5227 assert_eq!(
5228 editor.selections.display_ranges(cx),
5229 [
5230 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5231 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5232 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5233 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5234 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5235 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5236 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5237 ]
5238 );
5239 });
5240 EditorTestContext::for_editor(editor, cx)
5241 .await
5242 .assert_editor_state(
5243 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5244 );
5245}
5246
5247#[gpui::test]
5248async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5249 init_test(cx, |_| {});
5250
5251 let mut cx = EditorTestContext::new(cx).await;
5252
5253 cx.set_state(indoc!(
5254 r#"abc
5255 defˇghi
5256
5257 jk
5258 nlmo
5259 "#
5260 ));
5261
5262 cx.update_editor(|editor, window, cx| {
5263 editor.add_selection_above(&Default::default(), window, cx);
5264 });
5265
5266 cx.assert_editor_state(indoc!(
5267 r#"abcˇ
5268 defˇghi
5269
5270 jk
5271 nlmo
5272 "#
5273 ));
5274
5275 cx.update_editor(|editor, window, cx| {
5276 editor.add_selection_above(&Default::default(), window, cx);
5277 });
5278
5279 cx.assert_editor_state(indoc!(
5280 r#"abcˇ
5281 defˇghi
5282
5283 jk
5284 nlmo
5285 "#
5286 ));
5287
5288 cx.update_editor(|editor, window, cx| {
5289 editor.add_selection_below(&Default::default(), window, cx);
5290 });
5291
5292 cx.assert_editor_state(indoc!(
5293 r#"abc
5294 defˇghi
5295
5296 jk
5297 nlmo
5298 "#
5299 ));
5300
5301 cx.update_editor(|editor, window, cx| {
5302 editor.undo_selection(&Default::default(), window, cx);
5303 });
5304
5305 cx.assert_editor_state(indoc!(
5306 r#"abcˇ
5307 defˇghi
5308
5309 jk
5310 nlmo
5311 "#
5312 ));
5313
5314 cx.update_editor(|editor, window, cx| {
5315 editor.redo_selection(&Default::default(), window, cx);
5316 });
5317
5318 cx.assert_editor_state(indoc!(
5319 r#"abc
5320 defˇghi
5321
5322 jk
5323 nlmo
5324 "#
5325 ));
5326
5327 cx.update_editor(|editor, window, cx| {
5328 editor.add_selection_below(&Default::default(), window, cx);
5329 });
5330
5331 cx.assert_editor_state(indoc!(
5332 r#"abc
5333 defˇghi
5334
5335 jk
5336 nlmˇo
5337 "#
5338 ));
5339
5340 cx.update_editor(|editor, window, cx| {
5341 editor.add_selection_below(&Default::default(), window, cx);
5342 });
5343
5344 cx.assert_editor_state(indoc!(
5345 r#"abc
5346 defˇghi
5347
5348 jk
5349 nlmˇo
5350 "#
5351 ));
5352
5353 // change selections
5354 cx.set_state(indoc!(
5355 r#"abc
5356 def«ˇg»hi
5357
5358 jk
5359 nlmo
5360 "#
5361 ));
5362
5363 cx.update_editor(|editor, window, cx| {
5364 editor.add_selection_below(&Default::default(), window, cx);
5365 });
5366
5367 cx.assert_editor_state(indoc!(
5368 r#"abc
5369 def«ˇg»hi
5370
5371 jk
5372 nlm«ˇo»
5373 "#
5374 ));
5375
5376 cx.update_editor(|editor, window, cx| {
5377 editor.add_selection_below(&Default::default(), window, cx);
5378 });
5379
5380 cx.assert_editor_state(indoc!(
5381 r#"abc
5382 def«ˇg»hi
5383
5384 jk
5385 nlm«ˇo»
5386 "#
5387 ));
5388
5389 cx.update_editor(|editor, window, cx| {
5390 editor.add_selection_above(&Default::default(), window, cx);
5391 });
5392
5393 cx.assert_editor_state(indoc!(
5394 r#"abc
5395 def«ˇg»hi
5396
5397 jk
5398 nlmo
5399 "#
5400 ));
5401
5402 cx.update_editor(|editor, window, cx| {
5403 editor.add_selection_above(&Default::default(), window, cx);
5404 });
5405
5406 cx.assert_editor_state(indoc!(
5407 r#"abc
5408 def«ˇg»hi
5409
5410 jk
5411 nlmo
5412 "#
5413 ));
5414
5415 // Change selections again
5416 cx.set_state(indoc!(
5417 r#"a«bc
5418 defgˇ»hi
5419
5420 jk
5421 nlmo
5422 "#
5423 ));
5424
5425 cx.update_editor(|editor, window, cx| {
5426 editor.add_selection_below(&Default::default(), window, cx);
5427 });
5428
5429 cx.assert_editor_state(indoc!(
5430 r#"a«bcˇ»
5431 d«efgˇ»hi
5432
5433 j«kˇ»
5434 nlmo
5435 "#
5436 ));
5437
5438 cx.update_editor(|editor, window, cx| {
5439 editor.add_selection_below(&Default::default(), window, cx);
5440 });
5441 cx.assert_editor_state(indoc!(
5442 r#"a«bcˇ»
5443 d«efgˇ»hi
5444
5445 j«kˇ»
5446 n«lmoˇ»
5447 "#
5448 ));
5449 cx.update_editor(|editor, window, cx| {
5450 editor.add_selection_above(&Default::default(), window, cx);
5451 });
5452
5453 cx.assert_editor_state(indoc!(
5454 r#"a«bcˇ»
5455 d«efgˇ»hi
5456
5457 j«kˇ»
5458 nlmo
5459 "#
5460 ));
5461
5462 // Change selections again
5463 cx.set_state(indoc!(
5464 r#"abc
5465 d«ˇefghi
5466
5467 jk
5468 nlm»o
5469 "#
5470 ));
5471
5472 cx.update_editor(|editor, window, cx| {
5473 editor.add_selection_above(&Default::default(), window, cx);
5474 });
5475
5476 cx.assert_editor_state(indoc!(
5477 r#"a«ˇbc»
5478 d«ˇef»ghi
5479
5480 j«ˇk»
5481 n«ˇlm»o
5482 "#
5483 ));
5484
5485 cx.update_editor(|editor, window, cx| {
5486 editor.add_selection_below(&Default::default(), window, cx);
5487 });
5488
5489 cx.assert_editor_state(indoc!(
5490 r#"abc
5491 d«ˇef»ghi
5492
5493 j«ˇk»
5494 n«ˇlm»o
5495 "#
5496 ));
5497}
5498
5499#[gpui::test]
5500async fn test_select_next(cx: &mut TestAppContext) {
5501 init_test(cx, |_| {});
5502
5503 let mut cx = EditorTestContext::new(cx).await;
5504 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5505
5506 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5507 .unwrap();
5508 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5509
5510 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5511 .unwrap();
5512 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5513
5514 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5515 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5516
5517 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5518 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5519
5520 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5521 .unwrap();
5522 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5523
5524 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5525 .unwrap();
5526 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5527}
5528
5529#[gpui::test]
5530async fn test_select_all_matches(cx: &mut TestAppContext) {
5531 init_test(cx, |_| {});
5532
5533 let mut cx = EditorTestContext::new(cx).await;
5534
5535 // Test caret-only selections
5536 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5537 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5538 .unwrap();
5539 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5540
5541 // Test left-to-right selections
5542 cx.set_state("abc\n«abcˇ»\nabc");
5543 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5544 .unwrap();
5545 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5546
5547 // Test right-to-left selections
5548 cx.set_state("abc\n«ˇabc»\nabc");
5549 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5550 .unwrap();
5551 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5552
5553 // Test selecting whitespace with caret selection
5554 cx.set_state("abc\nˇ abc\nabc");
5555 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5556 .unwrap();
5557 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5558
5559 // Test selecting whitespace with left-to-right selection
5560 cx.set_state("abc\n«ˇ »abc\nabc");
5561 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5562 .unwrap();
5563 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5564
5565 // Test no matches with right-to-left selection
5566 cx.set_state("abc\n« ˇ»abc\nabc");
5567 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5568 .unwrap();
5569 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5570}
5571
5572#[gpui::test]
5573async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5574 init_test(cx, |_| {});
5575
5576 let mut cx = EditorTestContext::new(cx).await;
5577 cx.set_state(
5578 r#"let foo = 2;
5579lˇet foo = 2;
5580let fooˇ = 2;
5581let foo = 2;
5582let foo = ˇ2;"#,
5583 );
5584
5585 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5586 .unwrap();
5587 cx.assert_editor_state(
5588 r#"let foo = 2;
5589«letˇ» foo = 2;
5590let «fooˇ» = 2;
5591let foo = 2;
5592let foo = «2ˇ»;"#,
5593 );
5594
5595 // noop for multiple selections with different contents
5596 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5597 .unwrap();
5598 cx.assert_editor_state(
5599 r#"let foo = 2;
5600«letˇ» foo = 2;
5601let «fooˇ» = 2;
5602let foo = 2;
5603let foo = «2ˇ»;"#,
5604 );
5605}
5606
5607#[gpui::test]
5608async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5609 init_test(cx, |_| {});
5610
5611 let mut cx =
5612 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5613
5614 cx.assert_editor_state(indoc! {"
5615 ˇbbb
5616 ccc
5617
5618 bbb
5619 ccc
5620 "});
5621 cx.dispatch_action(SelectPrevious::default());
5622 cx.assert_editor_state(indoc! {"
5623 «bbbˇ»
5624 ccc
5625
5626 bbb
5627 ccc
5628 "});
5629 cx.dispatch_action(SelectPrevious::default());
5630 cx.assert_editor_state(indoc! {"
5631 «bbbˇ»
5632 ccc
5633
5634 «bbbˇ»
5635 ccc
5636 "});
5637}
5638
5639#[gpui::test]
5640async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5641 init_test(cx, |_| {});
5642
5643 let mut cx = EditorTestContext::new(cx).await;
5644 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5645
5646 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5647 .unwrap();
5648 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5649
5650 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5651 .unwrap();
5652 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5653
5654 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5655 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5656
5657 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5658 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5659
5660 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5661 .unwrap();
5662 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5663
5664 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5665 .unwrap();
5666 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5667
5668 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5669 .unwrap();
5670 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5671}
5672
5673#[gpui::test]
5674async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5675 init_test(cx, |_| {});
5676
5677 let mut cx = EditorTestContext::new(cx).await;
5678 cx.set_state("aˇ");
5679
5680 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5681 .unwrap();
5682 cx.assert_editor_state("«aˇ»");
5683 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5684 .unwrap();
5685 cx.assert_editor_state("«aˇ»");
5686}
5687
5688#[gpui::test]
5689async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5690 init_test(cx, |_| {});
5691
5692 let mut cx = EditorTestContext::new(cx).await;
5693 cx.set_state(
5694 r#"let foo = 2;
5695lˇet foo = 2;
5696let fooˇ = 2;
5697let foo = 2;
5698let foo = ˇ2;"#,
5699 );
5700
5701 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5702 .unwrap();
5703 cx.assert_editor_state(
5704 r#"let foo = 2;
5705«letˇ» foo = 2;
5706let «fooˇ» = 2;
5707let foo = 2;
5708let foo = «2ˇ»;"#,
5709 );
5710
5711 // noop for multiple selections with different contents
5712 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5713 .unwrap();
5714 cx.assert_editor_state(
5715 r#"let foo = 2;
5716«letˇ» foo = 2;
5717let «fooˇ» = 2;
5718let foo = 2;
5719let foo = «2ˇ»;"#,
5720 );
5721}
5722
5723#[gpui::test]
5724async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5725 init_test(cx, |_| {});
5726
5727 let mut cx = EditorTestContext::new(cx).await;
5728 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
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\ndefabc\nabc");
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\ndefabc\n«abcˇ»");
5737
5738 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5739 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5740
5741 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5742 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5743
5744 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5745 .unwrap();
5746 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5747
5748 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5749 .unwrap();
5750 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5751}
5752
5753#[gpui::test]
5754async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5755 init_test(cx, |_| {});
5756
5757 let language = Arc::new(Language::new(
5758 LanguageConfig::default(),
5759 Some(tree_sitter_rust::LANGUAGE.into()),
5760 ));
5761
5762 let text = r#"
5763 use mod1::mod2::{mod3, mod4};
5764
5765 fn fn_1(param1: bool, param2: &str) {
5766 let var1 = "text";
5767 }
5768 "#
5769 .unindent();
5770
5771 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5772 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5773 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5774
5775 editor
5776 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5777 .await;
5778
5779 editor.update_in(cx, |editor, window, cx| {
5780 editor.change_selections(None, window, cx, |s| {
5781 s.select_display_ranges([
5782 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5783 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5784 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5785 ]);
5786 });
5787 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5788 });
5789 editor.update(cx, |editor, cx| {
5790 assert_text_with_selections(
5791 editor,
5792 indoc! {r#"
5793 use mod1::mod2::{mod3, «mod4ˇ»};
5794
5795 fn fn_1«ˇ(param1: bool, param2: &str)» {
5796 let var1 = "«textˇ»";
5797 }
5798 "#},
5799 cx,
5800 );
5801 });
5802
5803 editor.update_in(cx, |editor, window, cx| {
5804 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5805 });
5806 editor.update(cx, |editor, cx| {
5807 assert_text_with_selections(
5808 editor,
5809 indoc! {r#"
5810 use mod1::mod2::«{mod3, mod4}ˇ»;
5811
5812 «ˇfn fn_1(param1: bool, param2: &str) {
5813 let var1 = "text";
5814 }»
5815 "#},
5816 cx,
5817 );
5818 });
5819
5820 editor.update_in(cx, |editor, window, cx| {
5821 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5822 });
5823 assert_eq!(
5824 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5825 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5826 );
5827
5828 // Trying to expand the selected syntax node one more time has no effect.
5829 editor.update_in(cx, |editor, window, cx| {
5830 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5831 });
5832 assert_eq!(
5833 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5834 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5835 );
5836
5837 editor.update_in(cx, |editor, window, cx| {
5838 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5839 });
5840 editor.update(cx, |editor, cx| {
5841 assert_text_with_selections(
5842 editor,
5843 indoc! {r#"
5844 use mod1::mod2::«{mod3, mod4}ˇ»;
5845
5846 «ˇfn fn_1(param1: bool, param2: &str) {
5847 let var1 = "text";
5848 }»
5849 "#},
5850 cx,
5851 );
5852 });
5853
5854 editor.update_in(cx, |editor, window, cx| {
5855 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5856 });
5857 editor.update(cx, |editor, cx| {
5858 assert_text_with_selections(
5859 editor,
5860 indoc! {r#"
5861 use mod1::mod2::{mod3, «mod4ˇ»};
5862
5863 fn fn_1«ˇ(param1: bool, param2: &str)» {
5864 let var1 = "«textˇ»";
5865 }
5866 "#},
5867 cx,
5868 );
5869 });
5870
5871 editor.update_in(cx, |editor, window, cx| {
5872 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5873 });
5874 editor.update(cx, |editor, cx| {
5875 assert_text_with_selections(
5876 editor,
5877 indoc! {r#"
5878 use mod1::mod2::{mod3, mo«ˇ»d4};
5879
5880 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5881 let var1 = "te«ˇ»xt";
5882 }
5883 "#},
5884 cx,
5885 );
5886 });
5887
5888 // Trying to shrink the selected syntax node one more time has no effect.
5889 editor.update_in(cx, |editor, window, cx| {
5890 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5891 });
5892 editor.update_in(cx, |editor, _, cx| {
5893 assert_text_with_selections(
5894 editor,
5895 indoc! {r#"
5896 use mod1::mod2::{mod3, mo«ˇ»d4};
5897
5898 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5899 let var1 = "te«ˇ»xt";
5900 }
5901 "#},
5902 cx,
5903 );
5904 });
5905
5906 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5907 // a fold.
5908 editor.update_in(cx, |editor, window, cx| {
5909 editor.fold_creases(
5910 vec![
5911 Crease::simple(
5912 Point::new(0, 21)..Point::new(0, 24),
5913 FoldPlaceholder::test(),
5914 ),
5915 Crease::simple(
5916 Point::new(3, 20)..Point::new(3, 22),
5917 FoldPlaceholder::test(),
5918 ),
5919 ],
5920 true,
5921 window,
5922 cx,
5923 );
5924 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5925 });
5926 editor.update(cx, |editor, cx| {
5927 assert_text_with_selections(
5928 editor,
5929 indoc! {r#"
5930 use mod1::mod2::«{mod3, mod4}ˇ»;
5931
5932 fn fn_1«ˇ(param1: bool, param2: &str)» {
5933 «let var1 = "text";ˇ»
5934 }
5935 "#},
5936 cx,
5937 );
5938 });
5939}
5940
5941#[gpui::test]
5942async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5943 init_test(cx, |_| {});
5944
5945 let base_text = r#"
5946 impl A {
5947 // this is an uncommitted comment
5948
5949 fn b() {
5950 c();
5951 }
5952
5953 // this is another uncommitted comment
5954
5955 fn d() {
5956 // e
5957 // f
5958 }
5959 }
5960
5961 fn g() {
5962 // h
5963 }
5964 "#
5965 .unindent();
5966
5967 let text = r#"
5968 ˇimpl A {
5969
5970 fn b() {
5971 c();
5972 }
5973
5974 fn d() {
5975 // e
5976 // f
5977 }
5978 }
5979
5980 fn g() {
5981 // h
5982 }
5983 "#
5984 .unindent();
5985
5986 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5987 cx.set_state(&text);
5988 cx.set_head_text(&base_text);
5989 cx.update_editor(|editor, window, cx| {
5990 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5991 });
5992
5993 cx.assert_state_with_diff(
5994 "
5995 ˇimpl A {
5996 - // this is an uncommitted comment
5997
5998 fn b() {
5999 c();
6000 }
6001
6002 - // this is another uncommitted comment
6003 -
6004 fn d() {
6005 // e
6006 // f
6007 }
6008 }
6009
6010 fn g() {
6011 // h
6012 }
6013 "
6014 .unindent(),
6015 );
6016
6017 let expected_display_text = "
6018 impl A {
6019 // this is an uncommitted comment
6020
6021 fn b() {
6022 ⋯
6023 }
6024
6025 // this is another uncommitted comment
6026
6027 fn d() {
6028 ⋯
6029 }
6030 }
6031
6032 fn g() {
6033 ⋯
6034 }
6035 "
6036 .unindent();
6037
6038 cx.update_editor(|editor, window, cx| {
6039 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6040 assert_eq!(editor.display_text(cx), expected_display_text);
6041 });
6042}
6043
6044#[gpui::test]
6045async fn test_autoindent(cx: &mut TestAppContext) {
6046 init_test(cx, |_| {});
6047
6048 let language = Arc::new(
6049 Language::new(
6050 LanguageConfig {
6051 brackets: BracketPairConfig {
6052 pairs: vec![
6053 BracketPair {
6054 start: "{".to_string(),
6055 end: "}".to_string(),
6056 close: false,
6057 surround: false,
6058 newline: true,
6059 },
6060 BracketPair {
6061 start: "(".to_string(),
6062 end: ")".to_string(),
6063 close: false,
6064 surround: false,
6065 newline: true,
6066 },
6067 ],
6068 ..Default::default()
6069 },
6070 ..Default::default()
6071 },
6072 Some(tree_sitter_rust::LANGUAGE.into()),
6073 )
6074 .with_indents_query(
6075 r#"
6076 (_ "(" ")" @end) @indent
6077 (_ "{" "}" @end) @indent
6078 "#,
6079 )
6080 .unwrap(),
6081 );
6082
6083 let text = "fn a() {}";
6084
6085 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6086 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6087 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6088 editor
6089 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6090 .await;
6091
6092 editor.update_in(cx, |editor, window, cx| {
6093 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6094 editor.newline(&Newline, window, cx);
6095 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6096 assert_eq!(
6097 editor.selections.ranges(cx),
6098 &[
6099 Point::new(1, 4)..Point::new(1, 4),
6100 Point::new(3, 4)..Point::new(3, 4),
6101 Point::new(5, 0)..Point::new(5, 0)
6102 ]
6103 );
6104 });
6105}
6106
6107#[gpui::test]
6108async fn test_autoindent_selections(cx: &mut TestAppContext) {
6109 init_test(cx, |_| {});
6110
6111 {
6112 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6113 cx.set_state(indoc! {"
6114 impl A {
6115
6116 fn b() {}
6117
6118 «fn c() {
6119
6120 }ˇ»
6121 }
6122 "});
6123
6124 cx.update_editor(|editor, window, cx| {
6125 editor.autoindent(&Default::default(), window, cx);
6126 });
6127
6128 cx.assert_editor_state(indoc! {"
6129 impl A {
6130
6131 fn b() {}
6132
6133 «fn c() {
6134
6135 }ˇ»
6136 }
6137 "});
6138 }
6139
6140 {
6141 let mut cx = EditorTestContext::new_multibuffer(
6142 cx,
6143 [indoc! { "
6144 impl A {
6145 «
6146 // a
6147 fn b(){}
6148 »
6149 «
6150 }
6151 fn c(){}
6152 »
6153 "}],
6154 );
6155
6156 let buffer = cx.update_editor(|editor, _, cx| {
6157 let buffer = editor.buffer().update(cx, |buffer, _| {
6158 buffer.all_buffers().iter().next().unwrap().clone()
6159 });
6160 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6161 buffer
6162 });
6163
6164 cx.run_until_parked();
6165 cx.update_editor(|editor, window, cx| {
6166 editor.select_all(&Default::default(), window, cx);
6167 editor.autoindent(&Default::default(), window, cx)
6168 });
6169 cx.run_until_parked();
6170
6171 cx.update(|_, cx| {
6172 pretty_assertions::assert_eq!(
6173 buffer.read(cx).text(),
6174 indoc! { "
6175 impl A {
6176
6177 // a
6178 fn b(){}
6179
6180
6181 }
6182 fn c(){}
6183
6184 " }
6185 )
6186 });
6187 }
6188}
6189
6190#[gpui::test]
6191async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6192 init_test(cx, |_| {});
6193
6194 let mut cx = EditorTestContext::new(cx).await;
6195
6196 let language = Arc::new(Language::new(
6197 LanguageConfig {
6198 brackets: BracketPairConfig {
6199 pairs: vec![
6200 BracketPair {
6201 start: "{".to_string(),
6202 end: "}".to_string(),
6203 close: true,
6204 surround: true,
6205 newline: true,
6206 },
6207 BracketPair {
6208 start: "(".to_string(),
6209 end: ")".to_string(),
6210 close: true,
6211 surround: true,
6212 newline: true,
6213 },
6214 BracketPair {
6215 start: "/*".to_string(),
6216 end: " */".to_string(),
6217 close: true,
6218 surround: true,
6219 newline: true,
6220 },
6221 BracketPair {
6222 start: "[".to_string(),
6223 end: "]".to_string(),
6224 close: false,
6225 surround: false,
6226 newline: true,
6227 },
6228 BracketPair {
6229 start: "\"".to_string(),
6230 end: "\"".to_string(),
6231 close: true,
6232 surround: true,
6233 newline: false,
6234 },
6235 BracketPair {
6236 start: "<".to_string(),
6237 end: ">".to_string(),
6238 close: false,
6239 surround: true,
6240 newline: true,
6241 },
6242 ],
6243 ..Default::default()
6244 },
6245 autoclose_before: "})]".to_string(),
6246 ..Default::default()
6247 },
6248 Some(tree_sitter_rust::LANGUAGE.into()),
6249 ));
6250
6251 cx.language_registry().add(language.clone());
6252 cx.update_buffer(|buffer, cx| {
6253 buffer.set_language(Some(language), cx);
6254 });
6255
6256 cx.set_state(
6257 &r#"
6258 🏀ˇ
6259 εˇ
6260 ❤️ˇ
6261 "#
6262 .unindent(),
6263 );
6264
6265 // autoclose multiple nested brackets at multiple cursors
6266 cx.update_editor(|editor, window, cx| {
6267 editor.handle_input("{", window, cx);
6268 editor.handle_input("{", window, cx);
6269 editor.handle_input("{", window, cx);
6270 });
6271 cx.assert_editor_state(
6272 &"
6273 🏀{{{ˇ}}}
6274 ε{{{ˇ}}}
6275 ❤️{{{ˇ}}}
6276 "
6277 .unindent(),
6278 );
6279
6280 // insert a different closing bracket
6281 cx.update_editor(|editor, window, cx| {
6282 editor.handle_input(")", window, cx);
6283 });
6284 cx.assert_editor_state(
6285 &"
6286 🏀{{{)ˇ}}}
6287 ε{{{)ˇ}}}
6288 ❤️{{{)ˇ}}}
6289 "
6290 .unindent(),
6291 );
6292
6293 // skip over the auto-closed brackets when typing a closing bracket
6294 cx.update_editor(|editor, window, cx| {
6295 editor.move_right(&MoveRight, window, cx);
6296 editor.handle_input("}", window, cx);
6297 editor.handle_input("}", window, cx);
6298 editor.handle_input("}", window, cx);
6299 });
6300 cx.assert_editor_state(
6301 &"
6302 🏀{{{)}}}}ˇ
6303 ε{{{)}}}}ˇ
6304 ❤️{{{)}}}}ˇ
6305 "
6306 .unindent(),
6307 );
6308
6309 // autoclose multi-character pairs
6310 cx.set_state(
6311 &"
6312 ˇ
6313 ˇ
6314 "
6315 .unindent(),
6316 );
6317 cx.update_editor(|editor, window, cx| {
6318 editor.handle_input("/", window, cx);
6319 editor.handle_input("*", window, cx);
6320 });
6321 cx.assert_editor_state(
6322 &"
6323 /*ˇ */
6324 /*ˇ */
6325 "
6326 .unindent(),
6327 );
6328
6329 // one cursor autocloses a multi-character pair, one cursor
6330 // does not autoclose.
6331 cx.set_state(
6332 &"
6333 /ˇ
6334 ˇ
6335 "
6336 .unindent(),
6337 );
6338 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6339 cx.assert_editor_state(
6340 &"
6341 /*ˇ */
6342 *ˇ
6343 "
6344 .unindent(),
6345 );
6346
6347 // Don't autoclose if the next character isn't whitespace and isn't
6348 // listed in the language's "autoclose_before" section.
6349 cx.set_state("ˇa b");
6350 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6351 cx.assert_editor_state("{ˇa b");
6352
6353 // Don't autoclose if `close` is false for the bracket pair
6354 cx.set_state("ˇ");
6355 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6356 cx.assert_editor_state("[ˇ");
6357
6358 // Surround with brackets if text is selected
6359 cx.set_state("«aˇ» b");
6360 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6361 cx.assert_editor_state("{«aˇ»} b");
6362
6363 // Autoclose when not immediately after a word character
6364 cx.set_state("a ˇ");
6365 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6366 cx.assert_editor_state("a \"ˇ\"");
6367
6368 // Autoclose pair where the start and end characters are the same
6369 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6370 cx.assert_editor_state("a \"\"ˇ");
6371
6372 // Don't autoclose when immediately after a word character
6373 cx.set_state("aˇ");
6374 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6375 cx.assert_editor_state("a\"ˇ");
6376
6377 // Do autoclose when after a non-word character
6378 cx.set_state("{ˇ");
6379 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6380 cx.assert_editor_state("{\"ˇ\"");
6381
6382 // Non identical pairs autoclose regardless of preceding character
6383 cx.set_state("aˇ");
6384 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6385 cx.assert_editor_state("a{ˇ}");
6386
6387 // Don't autoclose pair if autoclose is disabled
6388 cx.set_state("ˇ");
6389 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6390 cx.assert_editor_state("<ˇ");
6391
6392 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6393 cx.set_state("«aˇ» b");
6394 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6395 cx.assert_editor_state("<«aˇ»> b");
6396}
6397
6398#[gpui::test]
6399async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6400 init_test(cx, |settings| {
6401 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6402 });
6403
6404 let mut cx = EditorTestContext::new(cx).await;
6405
6406 let language = Arc::new(Language::new(
6407 LanguageConfig {
6408 brackets: BracketPairConfig {
6409 pairs: vec![
6410 BracketPair {
6411 start: "{".to_string(),
6412 end: "}".to_string(),
6413 close: true,
6414 surround: true,
6415 newline: true,
6416 },
6417 BracketPair {
6418 start: "(".to_string(),
6419 end: ")".to_string(),
6420 close: true,
6421 surround: true,
6422 newline: true,
6423 },
6424 BracketPair {
6425 start: "[".to_string(),
6426 end: "]".to_string(),
6427 close: false,
6428 surround: false,
6429 newline: true,
6430 },
6431 ],
6432 ..Default::default()
6433 },
6434 autoclose_before: "})]".to_string(),
6435 ..Default::default()
6436 },
6437 Some(tree_sitter_rust::LANGUAGE.into()),
6438 ));
6439
6440 cx.language_registry().add(language.clone());
6441 cx.update_buffer(|buffer, cx| {
6442 buffer.set_language(Some(language), cx);
6443 });
6444
6445 cx.set_state(
6446 &"
6447 ˇ
6448 ˇ
6449 ˇ
6450 "
6451 .unindent(),
6452 );
6453
6454 // ensure only matching closing brackets are skipped over
6455 cx.update_editor(|editor, window, cx| {
6456 editor.handle_input("}", window, cx);
6457 editor.move_left(&MoveLeft, window, cx);
6458 editor.handle_input(")", window, cx);
6459 editor.move_left(&MoveLeft, window, cx);
6460 });
6461 cx.assert_editor_state(
6462 &"
6463 ˇ)}
6464 ˇ)}
6465 ˇ)}
6466 "
6467 .unindent(),
6468 );
6469
6470 // skip-over closing brackets at multiple cursors
6471 cx.update_editor(|editor, window, cx| {
6472 editor.handle_input(")", window, cx);
6473 editor.handle_input("}", window, cx);
6474 });
6475 cx.assert_editor_state(
6476 &"
6477 )}ˇ
6478 )}ˇ
6479 )}ˇ
6480 "
6481 .unindent(),
6482 );
6483
6484 // ignore non-close brackets
6485 cx.update_editor(|editor, window, cx| {
6486 editor.handle_input("]", window, cx);
6487 editor.move_left(&MoveLeft, window, cx);
6488 editor.handle_input("]", window, cx);
6489 });
6490 cx.assert_editor_state(
6491 &"
6492 )}]ˇ]
6493 )}]ˇ]
6494 )}]ˇ]
6495 "
6496 .unindent(),
6497 );
6498}
6499
6500#[gpui::test]
6501async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6502 init_test(cx, |_| {});
6503
6504 let mut cx = EditorTestContext::new(cx).await;
6505
6506 let html_language = Arc::new(
6507 Language::new(
6508 LanguageConfig {
6509 name: "HTML".into(),
6510 brackets: BracketPairConfig {
6511 pairs: vec![
6512 BracketPair {
6513 start: "<".into(),
6514 end: ">".into(),
6515 close: true,
6516 ..Default::default()
6517 },
6518 BracketPair {
6519 start: "{".into(),
6520 end: "}".into(),
6521 close: true,
6522 ..Default::default()
6523 },
6524 BracketPair {
6525 start: "(".into(),
6526 end: ")".into(),
6527 close: true,
6528 ..Default::default()
6529 },
6530 ],
6531 ..Default::default()
6532 },
6533 autoclose_before: "})]>".into(),
6534 ..Default::default()
6535 },
6536 Some(tree_sitter_html::LANGUAGE.into()),
6537 )
6538 .with_injection_query(
6539 r#"
6540 (script_element
6541 (raw_text) @injection.content
6542 (#set! injection.language "javascript"))
6543 "#,
6544 )
6545 .unwrap(),
6546 );
6547
6548 let javascript_language = Arc::new(Language::new(
6549 LanguageConfig {
6550 name: "JavaScript".into(),
6551 brackets: BracketPairConfig {
6552 pairs: vec![
6553 BracketPair {
6554 start: "/*".into(),
6555 end: " */".into(),
6556 close: true,
6557 ..Default::default()
6558 },
6559 BracketPair {
6560 start: "{".into(),
6561 end: "}".into(),
6562 close: true,
6563 ..Default::default()
6564 },
6565 BracketPair {
6566 start: "(".into(),
6567 end: ")".into(),
6568 close: true,
6569 ..Default::default()
6570 },
6571 ],
6572 ..Default::default()
6573 },
6574 autoclose_before: "})]>".into(),
6575 ..Default::default()
6576 },
6577 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6578 ));
6579
6580 cx.language_registry().add(html_language.clone());
6581 cx.language_registry().add(javascript_language.clone());
6582
6583 cx.update_buffer(|buffer, cx| {
6584 buffer.set_language(Some(html_language), cx);
6585 });
6586
6587 cx.set_state(
6588 &r#"
6589 <body>ˇ
6590 <script>
6591 var x = 1;ˇ
6592 </script>
6593 </body>ˇ
6594 "#
6595 .unindent(),
6596 );
6597
6598 // Precondition: different languages are active at different locations.
6599 cx.update_editor(|editor, window, cx| {
6600 let snapshot = editor.snapshot(window, cx);
6601 let cursors = editor.selections.ranges::<usize>(cx);
6602 let languages = cursors
6603 .iter()
6604 .map(|c| snapshot.language_at(c.start).unwrap().name())
6605 .collect::<Vec<_>>();
6606 assert_eq!(
6607 languages,
6608 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6609 );
6610 });
6611
6612 // Angle brackets autoclose in HTML, but not JavaScript.
6613 cx.update_editor(|editor, window, cx| {
6614 editor.handle_input("<", window, cx);
6615 editor.handle_input("a", window, cx);
6616 });
6617 cx.assert_editor_state(
6618 &r#"
6619 <body><aˇ>
6620 <script>
6621 var x = 1;<aˇ
6622 </script>
6623 </body><aˇ>
6624 "#
6625 .unindent(),
6626 );
6627
6628 // Curly braces and parens autoclose in both HTML and JavaScript.
6629 cx.update_editor(|editor, window, cx| {
6630 editor.handle_input(" b=", window, cx);
6631 editor.handle_input("{", window, cx);
6632 editor.handle_input("c", window, cx);
6633 editor.handle_input("(", window, cx);
6634 });
6635 cx.assert_editor_state(
6636 &r#"
6637 <body><a b={c(ˇ)}>
6638 <script>
6639 var x = 1;<a b={c(ˇ)}
6640 </script>
6641 </body><a b={c(ˇ)}>
6642 "#
6643 .unindent(),
6644 );
6645
6646 // Brackets that were already autoclosed are skipped.
6647 cx.update_editor(|editor, window, cx| {
6648 editor.handle_input(")", window, cx);
6649 editor.handle_input("d", window, cx);
6650 editor.handle_input("}", window, cx);
6651 });
6652 cx.assert_editor_state(
6653 &r#"
6654 <body><a b={c()d}ˇ>
6655 <script>
6656 var x = 1;<a b={c()d}ˇ
6657 </script>
6658 </body><a b={c()d}ˇ>
6659 "#
6660 .unindent(),
6661 );
6662 cx.update_editor(|editor, window, cx| {
6663 editor.handle_input(">", window, cx);
6664 });
6665 cx.assert_editor_state(
6666 &r#"
6667 <body><a b={c()d}>ˇ
6668 <script>
6669 var x = 1;<a b={c()d}>ˇ
6670 </script>
6671 </body><a b={c()d}>ˇ
6672 "#
6673 .unindent(),
6674 );
6675
6676 // Reset
6677 cx.set_state(
6678 &r#"
6679 <body>ˇ
6680 <script>
6681 var x = 1;ˇ
6682 </script>
6683 </body>ˇ
6684 "#
6685 .unindent(),
6686 );
6687
6688 cx.update_editor(|editor, window, cx| {
6689 editor.handle_input("<", window, cx);
6690 });
6691 cx.assert_editor_state(
6692 &r#"
6693 <body><ˇ>
6694 <script>
6695 var x = 1;<ˇ
6696 </script>
6697 </body><ˇ>
6698 "#
6699 .unindent(),
6700 );
6701
6702 // When backspacing, the closing angle brackets are removed.
6703 cx.update_editor(|editor, window, cx| {
6704 editor.backspace(&Backspace, window, cx);
6705 });
6706 cx.assert_editor_state(
6707 &r#"
6708 <body>ˇ
6709 <script>
6710 var x = 1;ˇ
6711 </script>
6712 </body>ˇ
6713 "#
6714 .unindent(),
6715 );
6716
6717 // Block comments autoclose in JavaScript, but not HTML.
6718 cx.update_editor(|editor, window, cx| {
6719 editor.handle_input("/", window, cx);
6720 editor.handle_input("*", window, cx);
6721 });
6722 cx.assert_editor_state(
6723 &r#"
6724 <body>/*ˇ
6725 <script>
6726 var x = 1;/*ˇ */
6727 </script>
6728 </body>/*ˇ
6729 "#
6730 .unindent(),
6731 );
6732}
6733
6734#[gpui::test]
6735async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6736 init_test(cx, |_| {});
6737
6738 let mut cx = EditorTestContext::new(cx).await;
6739
6740 let rust_language = Arc::new(
6741 Language::new(
6742 LanguageConfig {
6743 name: "Rust".into(),
6744 brackets: serde_json::from_value(json!([
6745 { "start": "{", "end": "}", "close": true, "newline": true },
6746 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6747 ]))
6748 .unwrap(),
6749 autoclose_before: "})]>".into(),
6750 ..Default::default()
6751 },
6752 Some(tree_sitter_rust::LANGUAGE.into()),
6753 )
6754 .with_override_query("(string_literal) @string")
6755 .unwrap(),
6756 );
6757
6758 cx.language_registry().add(rust_language.clone());
6759 cx.update_buffer(|buffer, cx| {
6760 buffer.set_language(Some(rust_language), cx);
6761 });
6762
6763 cx.set_state(
6764 &r#"
6765 let x = ˇ
6766 "#
6767 .unindent(),
6768 );
6769
6770 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6771 cx.update_editor(|editor, window, cx| {
6772 editor.handle_input("\"", window, cx);
6773 });
6774 cx.assert_editor_state(
6775 &r#"
6776 let x = "ˇ"
6777 "#
6778 .unindent(),
6779 );
6780
6781 // Inserting another quotation mark. The cursor moves across the existing
6782 // automatically-inserted quotation mark.
6783 cx.update_editor(|editor, window, cx| {
6784 editor.handle_input("\"", window, cx);
6785 });
6786 cx.assert_editor_state(
6787 &r#"
6788 let x = ""ˇ
6789 "#
6790 .unindent(),
6791 );
6792
6793 // Reset
6794 cx.set_state(
6795 &r#"
6796 let x = ˇ
6797 "#
6798 .unindent(),
6799 );
6800
6801 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6802 cx.update_editor(|editor, window, cx| {
6803 editor.handle_input("\"", window, cx);
6804 editor.handle_input(" ", window, cx);
6805 editor.move_left(&Default::default(), window, cx);
6806 editor.handle_input("\\", window, cx);
6807 editor.handle_input("\"", window, cx);
6808 });
6809 cx.assert_editor_state(
6810 &r#"
6811 let x = "\"ˇ "
6812 "#
6813 .unindent(),
6814 );
6815
6816 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6817 // mark. Nothing is inserted.
6818 cx.update_editor(|editor, window, cx| {
6819 editor.move_right(&Default::default(), window, cx);
6820 editor.handle_input("\"", window, cx);
6821 });
6822 cx.assert_editor_state(
6823 &r#"
6824 let x = "\" "ˇ
6825 "#
6826 .unindent(),
6827 );
6828}
6829
6830#[gpui::test]
6831async fn test_surround_with_pair(cx: &mut TestAppContext) {
6832 init_test(cx, |_| {});
6833
6834 let language = Arc::new(Language::new(
6835 LanguageConfig {
6836 brackets: BracketPairConfig {
6837 pairs: vec![
6838 BracketPair {
6839 start: "{".to_string(),
6840 end: "}".to_string(),
6841 close: true,
6842 surround: true,
6843 newline: true,
6844 },
6845 BracketPair {
6846 start: "/* ".to_string(),
6847 end: "*/".to_string(),
6848 close: true,
6849 surround: true,
6850 ..Default::default()
6851 },
6852 ],
6853 ..Default::default()
6854 },
6855 ..Default::default()
6856 },
6857 Some(tree_sitter_rust::LANGUAGE.into()),
6858 ));
6859
6860 let text = r#"
6861 a
6862 b
6863 c
6864 "#
6865 .unindent();
6866
6867 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6868 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6869 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6870 editor
6871 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6872 .await;
6873
6874 editor.update_in(cx, |editor, window, cx| {
6875 editor.change_selections(None, window, cx, |s| {
6876 s.select_display_ranges([
6877 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6878 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6879 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6880 ])
6881 });
6882
6883 editor.handle_input("{", window, cx);
6884 editor.handle_input("{", window, cx);
6885 editor.handle_input("{", window, cx);
6886 assert_eq!(
6887 editor.text(cx),
6888 "
6889 {{{a}}}
6890 {{{b}}}
6891 {{{c}}}
6892 "
6893 .unindent()
6894 );
6895 assert_eq!(
6896 editor.selections.display_ranges(cx),
6897 [
6898 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6899 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6900 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6901 ]
6902 );
6903
6904 editor.undo(&Undo, window, cx);
6905 editor.undo(&Undo, window, cx);
6906 editor.undo(&Undo, window, cx);
6907 assert_eq!(
6908 editor.text(cx),
6909 "
6910 a
6911 b
6912 c
6913 "
6914 .unindent()
6915 );
6916 assert_eq!(
6917 editor.selections.display_ranges(cx),
6918 [
6919 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6920 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6921 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6922 ]
6923 );
6924
6925 // Ensure inserting the first character of a multi-byte bracket pair
6926 // doesn't surround the selections with the bracket.
6927 editor.handle_input("/", window, cx);
6928 assert_eq!(
6929 editor.text(cx),
6930 "
6931 /
6932 /
6933 /
6934 "
6935 .unindent()
6936 );
6937 assert_eq!(
6938 editor.selections.display_ranges(cx),
6939 [
6940 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6941 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6942 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6943 ]
6944 );
6945
6946 editor.undo(&Undo, window, cx);
6947 assert_eq!(
6948 editor.text(cx),
6949 "
6950 a
6951 b
6952 c
6953 "
6954 .unindent()
6955 );
6956 assert_eq!(
6957 editor.selections.display_ranges(cx),
6958 [
6959 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6960 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6961 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6962 ]
6963 );
6964
6965 // Ensure inserting the last character of a multi-byte bracket pair
6966 // doesn't surround the selections with the bracket.
6967 editor.handle_input("*", window, cx);
6968 assert_eq!(
6969 editor.text(cx),
6970 "
6971 *
6972 *
6973 *
6974 "
6975 .unindent()
6976 );
6977 assert_eq!(
6978 editor.selections.display_ranges(cx),
6979 [
6980 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6981 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6982 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6983 ]
6984 );
6985 });
6986}
6987
6988#[gpui::test]
6989async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6990 init_test(cx, |_| {});
6991
6992 let language = Arc::new(Language::new(
6993 LanguageConfig {
6994 brackets: BracketPairConfig {
6995 pairs: vec![BracketPair {
6996 start: "{".to_string(),
6997 end: "}".to_string(),
6998 close: true,
6999 surround: true,
7000 newline: true,
7001 }],
7002 ..Default::default()
7003 },
7004 autoclose_before: "}".to_string(),
7005 ..Default::default()
7006 },
7007 Some(tree_sitter_rust::LANGUAGE.into()),
7008 ));
7009
7010 let text = r#"
7011 a
7012 b
7013 c
7014 "#
7015 .unindent();
7016
7017 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
7018 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7019 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7020 editor
7021 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7022 .await;
7023
7024 editor.update_in(cx, |editor, window, cx| {
7025 editor.change_selections(None, window, cx, |s| {
7026 s.select_ranges([
7027 Point::new(0, 1)..Point::new(0, 1),
7028 Point::new(1, 1)..Point::new(1, 1),
7029 Point::new(2, 1)..Point::new(2, 1),
7030 ])
7031 });
7032
7033 editor.handle_input("{", window, cx);
7034 editor.handle_input("{", window, cx);
7035 editor.handle_input("_", window, cx);
7036 assert_eq!(
7037 editor.text(cx),
7038 "
7039 a{{_}}
7040 b{{_}}
7041 c{{_}}
7042 "
7043 .unindent()
7044 );
7045 assert_eq!(
7046 editor.selections.ranges::<Point>(cx),
7047 [
7048 Point::new(0, 4)..Point::new(0, 4),
7049 Point::new(1, 4)..Point::new(1, 4),
7050 Point::new(2, 4)..Point::new(2, 4)
7051 ]
7052 );
7053
7054 editor.backspace(&Default::default(), window, cx);
7055 editor.backspace(&Default::default(), window, cx);
7056 assert_eq!(
7057 editor.text(cx),
7058 "
7059 a{}
7060 b{}
7061 c{}
7062 "
7063 .unindent()
7064 );
7065 assert_eq!(
7066 editor.selections.ranges::<Point>(cx),
7067 [
7068 Point::new(0, 2)..Point::new(0, 2),
7069 Point::new(1, 2)..Point::new(1, 2),
7070 Point::new(2, 2)..Point::new(2, 2)
7071 ]
7072 );
7073
7074 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7075 assert_eq!(
7076 editor.text(cx),
7077 "
7078 a
7079 b
7080 c
7081 "
7082 .unindent()
7083 );
7084 assert_eq!(
7085 editor.selections.ranges::<Point>(cx),
7086 [
7087 Point::new(0, 1)..Point::new(0, 1),
7088 Point::new(1, 1)..Point::new(1, 1),
7089 Point::new(2, 1)..Point::new(2, 1)
7090 ]
7091 );
7092 });
7093}
7094
7095#[gpui::test]
7096async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7097 init_test(cx, |settings| {
7098 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7099 });
7100
7101 let mut cx = EditorTestContext::new(cx).await;
7102
7103 let language = Arc::new(Language::new(
7104 LanguageConfig {
7105 brackets: BracketPairConfig {
7106 pairs: vec![
7107 BracketPair {
7108 start: "{".to_string(),
7109 end: "}".to_string(),
7110 close: true,
7111 surround: true,
7112 newline: true,
7113 },
7114 BracketPair {
7115 start: "(".to_string(),
7116 end: ")".to_string(),
7117 close: true,
7118 surround: true,
7119 newline: true,
7120 },
7121 BracketPair {
7122 start: "[".to_string(),
7123 end: "]".to_string(),
7124 close: false,
7125 surround: true,
7126 newline: true,
7127 },
7128 ],
7129 ..Default::default()
7130 },
7131 autoclose_before: "})]".to_string(),
7132 ..Default::default()
7133 },
7134 Some(tree_sitter_rust::LANGUAGE.into()),
7135 ));
7136
7137 cx.language_registry().add(language.clone());
7138 cx.update_buffer(|buffer, cx| {
7139 buffer.set_language(Some(language), cx);
7140 });
7141
7142 cx.set_state(
7143 &"
7144 {(ˇ)}
7145 [[ˇ]]
7146 {(ˇ)}
7147 "
7148 .unindent(),
7149 );
7150
7151 cx.update_editor(|editor, window, cx| {
7152 editor.backspace(&Default::default(), window, cx);
7153 editor.backspace(&Default::default(), window, cx);
7154 });
7155
7156 cx.assert_editor_state(
7157 &"
7158 ˇ
7159 ˇ]]
7160 ˇ
7161 "
7162 .unindent(),
7163 );
7164
7165 cx.update_editor(|editor, window, cx| {
7166 editor.handle_input("{", window, cx);
7167 editor.handle_input("{", window, cx);
7168 editor.move_right(&MoveRight, window, cx);
7169 editor.move_right(&MoveRight, window, cx);
7170 editor.move_left(&MoveLeft, window, cx);
7171 editor.move_left(&MoveLeft, window, cx);
7172 editor.backspace(&Default::default(), window, cx);
7173 });
7174
7175 cx.assert_editor_state(
7176 &"
7177 {ˇ}
7178 {ˇ}]]
7179 {ˇ}
7180 "
7181 .unindent(),
7182 );
7183
7184 cx.update_editor(|editor, window, cx| {
7185 editor.backspace(&Default::default(), window, cx);
7186 });
7187
7188 cx.assert_editor_state(
7189 &"
7190 ˇ
7191 ˇ]]
7192 ˇ
7193 "
7194 .unindent(),
7195 );
7196}
7197
7198#[gpui::test]
7199async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7200 init_test(cx, |_| {});
7201
7202 let language = Arc::new(Language::new(
7203 LanguageConfig::default(),
7204 Some(tree_sitter_rust::LANGUAGE.into()),
7205 ));
7206
7207 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7208 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7209 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7210 editor
7211 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7212 .await;
7213
7214 editor.update_in(cx, |editor, window, cx| {
7215 editor.set_auto_replace_emoji_shortcode(true);
7216
7217 editor.handle_input("Hello ", window, cx);
7218 editor.handle_input(":wave", window, cx);
7219 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7220
7221 editor.handle_input(":", window, cx);
7222 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7223
7224 editor.handle_input(" :smile", window, cx);
7225 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7226
7227 editor.handle_input(":", window, cx);
7228 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7229
7230 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7231 editor.handle_input(":wave", window, cx);
7232 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7233
7234 editor.handle_input(":", window, cx);
7235 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7236
7237 editor.handle_input(":1", window, cx);
7238 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7239
7240 editor.handle_input(":", window, cx);
7241 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7242
7243 // Ensure shortcode does not get replaced when it is part of a word
7244 editor.handle_input(" Test:wave", window, cx);
7245 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7246
7247 editor.handle_input(":", window, cx);
7248 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7249
7250 editor.set_auto_replace_emoji_shortcode(false);
7251
7252 // Ensure shortcode does not get replaced when auto replace is off
7253 editor.handle_input(" :wave", window, cx);
7254 assert_eq!(
7255 editor.text(cx),
7256 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7257 );
7258
7259 editor.handle_input(":", window, cx);
7260 assert_eq!(
7261 editor.text(cx),
7262 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7263 );
7264 });
7265}
7266
7267#[gpui::test]
7268async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7269 init_test(cx, |_| {});
7270
7271 let (text, insertion_ranges) = marked_text_ranges(
7272 indoc! {"
7273 ˇ
7274 "},
7275 false,
7276 );
7277
7278 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7279 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7280
7281 _ = editor.update_in(cx, |editor, window, cx| {
7282 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7283
7284 editor
7285 .insert_snippet(&insertion_ranges, snippet, window, cx)
7286 .unwrap();
7287
7288 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7289 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7290 assert_eq!(editor.text(cx), expected_text);
7291 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7292 }
7293
7294 assert(
7295 editor,
7296 cx,
7297 indoc! {"
7298 type «» =•
7299 "},
7300 );
7301
7302 assert!(editor.context_menu_visible(), "There should be a matches");
7303 });
7304}
7305
7306#[gpui::test]
7307async fn test_snippets(cx: &mut TestAppContext) {
7308 init_test(cx, |_| {});
7309
7310 let (text, insertion_ranges) = marked_text_ranges(
7311 indoc! {"
7312 a.ˇ b
7313 a.ˇ b
7314 a.ˇ b
7315 "},
7316 false,
7317 );
7318
7319 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7320 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7321
7322 editor.update_in(cx, |editor, window, cx| {
7323 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7324
7325 editor
7326 .insert_snippet(&insertion_ranges, snippet, window, cx)
7327 .unwrap();
7328
7329 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7330 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7331 assert_eq!(editor.text(cx), expected_text);
7332 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7333 }
7334
7335 assert(
7336 editor,
7337 cx,
7338 indoc! {"
7339 a.f(«one», two, «three») b
7340 a.f(«one», two, «three») b
7341 a.f(«one», two, «three») b
7342 "},
7343 );
7344
7345 // Can't move earlier than the first tab stop
7346 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7347 assert(
7348 editor,
7349 cx,
7350 indoc! {"
7351 a.f(«one», two, «three») b
7352 a.f(«one», two, «three») b
7353 a.f(«one», two, «three») b
7354 "},
7355 );
7356
7357 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7358 assert(
7359 editor,
7360 cx,
7361 indoc! {"
7362 a.f(one, «two», three) b
7363 a.f(one, «two», three) b
7364 a.f(one, «two», three) b
7365 "},
7366 );
7367
7368 editor.move_to_prev_snippet_tabstop(window, cx);
7369 assert(
7370 editor,
7371 cx,
7372 indoc! {"
7373 a.f(«one», two, «three») b
7374 a.f(«one», two, «three») b
7375 a.f(«one», two, «three») b
7376 "},
7377 );
7378
7379 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7380 assert(
7381 editor,
7382 cx,
7383 indoc! {"
7384 a.f(one, «two», three) b
7385 a.f(one, «two», three) b
7386 a.f(one, «two», three) b
7387 "},
7388 );
7389 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7390 assert(
7391 editor,
7392 cx,
7393 indoc! {"
7394 a.f(one, two, three)ˇ b
7395 a.f(one, two, three)ˇ b
7396 a.f(one, two, three)ˇ b
7397 "},
7398 );
7399
7400 // As soon as the last tab stop is reached, snippet state is gone
7401 editor.move_to_prev_snippet_tabstop(window, cx);
7402 assert(
7403 editor,
7404 cx,
7405 indoc! {"
7406 a.f(one, two, three)ˇ b
7407 a.f(one, two, three)ˇ b
7408 a.f(one, two, three)ˇ b
7409 "},
7410 );
7411 });
7412}
7413
7414#[gpui::test]
7415async fn test_document_format_during_save(cx: &mut TestAppContext) {
7416 init_test(cx, |_| {});
7417
7418 let fs = FakeFs::new(cx.executor());
7419 fs.insert_file(path!("/file.rs"), Default::default()).await;
7420
7421 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7422
7423 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7424 language_registry.add(rust_lang());
7425 let mut fake_servers = language_registry.register_fake_lsp(
7426 "Rust",
7427 FakeLspAdapter {
7428 capabilities: lsp::ServerCapabilities {
7429 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7430 ..Default::default()
7431 },
7432 ..Default::default()
7433 },
7434 );
7435
7436 let buffer = project
7437 .update(cx, |project, cx| {
7438 project.open_local_buffer(path!("/file.rs"), cx)
7439 })
7440 .await
7441 .unwrap();
7442
7443 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7444 let (editor, cx) = cx.add_window_view(|window, cx| {
7445 build_editor_with_project(project.clone(), buffer, window, cx)
7446 });
7447 editor.update_in(cx, |editor, window, cx| {
7448 editor.set_text("one\ntwo\nthree\n", window, cx)
7449 });
7450 assert!(cx.read(|cx| editor.is_dirty(cx)));
7451
7452 cx.executor().start_waiting();
7453 let fake_server = fake_servers.next().await.unwrap();
7454
7455 let save = editor
7456 .update_in(cx, |editor, window, cx| {
7457 editor.save(true, project.clone(), window, cx)
7458 })
7459 .unwrap();
7460 fake_server
7461 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7462 assert_eq!(
7463 params.text_document.uri,
7464 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7465 );
7466 assert_eq!(params.options.tab_size, 4);
7467 Ok(Some(vec![lsp::TextEdit::new(
7468 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7469 ", ".to_string(),
7470 )]))
7471 })
7472 .next()
7473 .await;
7474 cx.executor().start_waiting();
7475 save.await;
7476
7477 assert_eq!(
7478 editor.update(cx, |editor, cx| editor.text(cx)),
7479 "one, two\nthree\n"
7480 );
7481 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7482
7483 editor.update_in(cx, |editor, window, cx| {
7484 editor.set_text("one\ntwo\nthree\n", window, cx)
7485 });
7486 assert!(cx.read(|cx| editor.is_dirty(cx)));
7487
7488 // Ensure we can still save even if formatting hangs.
7489 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7490 assert_eq!(
7491 params.text_document.uri,
7492 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7493 );
7494 futures::future::pending::<()>().await;
7495 unreachable!()
7496 });
7497 let save = editor
7498 .update_in(cx, |editor, window, cx| {
7499 editor.save(true, project.clone(), window, cx)
7500 })
7501 .unwrap();
7502 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7503 cx.executor().start_waiting();
7504 save.await;
7505 assert_eq!(
7506 editor.update(cx, |editor, cx| editor.text(cx)),
7507 "one\ntwo\nthree\n"
7508 );
7509 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7510
7511 // For non-dirty buffer, no formatting request should be sent
7512 let save = editor
7513 .update_in(cx, |editor, window, cx| {
7514 editor.save(true, project.clone(), window, cx)
7515 })
7516 .unwrap();
7517 let _pending_format_request = fake_server
7518 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7519 panic!("Should not be invoked on non-dirty buffer");
7520 })
7521 .next();
7522 cx.executor().start_waiting();
7523 save.await;
7524
7525 // Set rust language override and assert overridden tabsize is sent to language server
7526 update_test_language_settings(cx, |settings| {
7527 settings.languages.insert(
7528 "Rust".into(),
7529 LanguageSettingsContent {
7530 tab_size: NonZeroU32::new(8),
7531 ..Default::default()
7532 },
7533 );
7534 });
7535
7536 editor.update_in(cx, |editor, window, cx| {
7537 editor.set_text("somehting_new\n", window, cx)
7538 });
7539 assert!(cx.read(|cx| editor.is_dirty(cx)));
7540 let save = editor
7541 .update_in(cx, |editor, window, cx| {
7542 editor.save(true, project.clone(), window, cx)
7543 })
7544 .unwrap();
7545 fake_server
7546 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7547 assert_eq!(
7548 params.text_document.uri,
7549 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7550 );
7551 assert_eq!(params.options.tab_size, 8);
7552 Ok(Some(vec![]))
7553 })
7554 .next()
7555 .await;
7556 cx.executor().start_waiting();
7557 save.await;
7558}
7559
7560#[gpui::test]
7561async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7562 init_test(cx, |_| {});
7563
7564 let cols = 4;
7565 let rows = 10;
7566 let sample_text_1 = sample_text(rows, cols, 'a');
7567 assert_eq!(
7568 sample_text_1,
7569 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7570 );
7571 let sample_text_2 = sample_text(rows, cols, 'l');
7572 assert_eq!(
7573 sample_text_2,
7574 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7575 );
7576 let sample_text_3 = sample_text(rows, cols, 'v');
7577 assert_eq!(
7578 sample_text_3,
7579 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7580 );
7581
7582 let fs = FakeFs::new(cx.executor());
7583 fs.insert_tree(
7584 path!("/a"),
7585 json!({
7586 "main.rs": sample_text_1,
7587 "other.rs": sample_text_2,
7588 "lib.rs": sample_text_3,
7589 }),
7590 )
7591 .await;
7592
7593 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7594 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7595 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7596
7597 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7598 language_registry.add(rust_lang());
7599 let mut fake_servers = language_registry.register_fake_lsp(
7600 "Rust",
7601 FakeLspAdapter {
7602 capabilities: lsp::ServerCapabilities {
7603 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7604 ..Default::default()
7605 },
7606 ..Default::default()
7607 },
7608 );
7609
7610 let worktree = project.update(cx, |project, cx| {
7611 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7612 assert_eq!(worktrees.len(), 1);
7613 worktrees.pop().unwrap()
7614 });
7615 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7616
7617 let buffer_1 = project
7618 .update(cx, |project, cx| {
7619 project.open_buffer((worktree_id, "main.rs"), cx)
7620 })
7621 .await
7622 .unwrap();
7623 let buffer_2 = project
7624 .update(cx, |project, cx| {
7625 project.open_buffer((worktree_id, "other.rs"), cx)
7626 })
7627 .await
7628 .unwrap();
7629 let buffer_3 = project
7630 .update(cx, |project, cx| {
7631 project.open_buffer((worktree_id, "lib.rs"), cx)
7632 })
7633 .await
7634 .unwrap();
7635
7636 let multi_buffer = cx.new(|cx| {
7637 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7638 multi_buffer.push_excerpts(
7639 buffer_1.clone(),
7640 [
7641 ExcerptRange {
7642 context: Point::new(0, 0)..Point::new(3, 0),
7643 primary: None,
7644 },
7645 ExcerptRange {
7646 context: Point::new(5, 0)..Point::new(7, 0),
7647 primary: None,
7648 },
7649 ExcerptRange {
7650 context: Point::new(9, 0)..Point::new(10, 4),
7651 primary: None,
7652 },
7653 ],
7654 cx,
7655 );
7656 multi_buffer.push_excerpts(
7657 buffer_2.clone(),
7658 [
7659 ExcerptRange {
7660 context: Point::new(0, 0)..Point::new(3, 0),
7661 primary: None,
7662 },
7663 ExcerptRange {
7664 context: Point::new(5, 0)..Point::new(7, 0),
7665 primary: None,
7666 },
7667 ExcerptRange {
7668 context: Point::new(9, 0)..Point::new(10, 4),
7669 primary: None,
7670 },
7671 ],
7672 cx,
7673 );
7674 multi_buffer.push_excerpts(
7675 buffer_3.clone(),
7676 [
7677 ExcerptRange {
7678 context: Point::new(0, 0)..Point::new(3, 0),
7679 primary: None,
7680 },
7681 ExcerptRange {
7682 context: Point::new(5, 0)..Point::new(7, 0),
7683 primary: None,
7684 },
7685 ExcerptRange {
7686 context: Point::new(9, 0)..Point::new(10, 4),
7687 primary: None,
7688 },
7689 ],
7690 cx,
7691 );
7692 multi_buffer
7693 });
7694 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7695 Editor::new(
7696 EditorMode::Full,
7697 multi_buffer,
7698 Some(project.clone()),
7699 window,
7700 cx,
7701 )
7702 });
7703
7704 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7705 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7706 s.select_ranges(Some(1..2))
7707 });
7708 editor.insert("|one|two|three|", window, cx);
7709 });
7710 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7711 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7712 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7713 s.select_ranges(Some(60..70))
7714 });
7715 editor.insert("|four|five|six|", window, cx);
7716 });
7717 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7718
7719 // First two buffers should be edited, but not the third one.
7720 assert_eq!(
7721 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7722 "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}",
7723 );
7724 buffer_1.update(cx, |buffer, _| {
7725 assert!(buffer.is_dirty());
7726 assert_eq!(
7727 buffer.text(),
7728 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7729 )
7730 });
7731 buffer_2.update(cx, |buffer, _| {
7732 assert!(buffer.is_dirty());
7733 assert_eq!(
7734 buffer.text(),
7735 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7736 )
7737 });
7738 buffer_3.update(cx, |buffer, _| {
7739 assert!(!buffer.is_dirty());
7740 assert_eq!(buffer.text(), sample_text_3,)
7741 });
7742 cx.executor().run_until_parked();
7743
7744 cx.executor().start_waiting();
7745 let save = multi_buffer_editor
7746 .update_in(cx, |editor, window, cx| {
7747 editor.save(true, project.clone(), window, cx)
7748 })
7749 .unwrap();
7750
7751 let fake_server = fake_servers.next().await.unwrap();
7752 fake_server
7753 .server
7754 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7755 Ok(Some(vec![lsp::TextEdit::new(
7756 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7757 format!("[{} formatted]", params.text_document.uri),
7758 )]))
7759 })
7760 .detach();
7761 save.await;
7762
7763 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7764 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7765 assert_eq!(
7766 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7767 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}"),
7768 );
7769 buffer_1.update(cx, |buffer, _| {
7770 assert!(!buffer.is_dirty());
7771 assert_eq!(
7772 buffer.text(),
7773 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7774 )
7775 });
7776 buffer_2.update(cx, |buffer, _| {
7777 assert!(!buffer.is_dirty());
7778 assert_eq!(
7779 buffer.text(),
7780 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7781 )
7782 });
7783 buffer_3.update(cx, |buffer, _| {
7784 assert!(!buffer.is_dirty());
7785 assert_eq!(buffer.text(), sample_text_3,)
7786 });
7787}
7788
7789#[gpui::test]
7790async fn test_range_format_during_save(cx: &mut TestAppContext) {
7791 init_test(cx, |_| {});
7792
7793 let fs = FakeFs::new(cx.executor());
7794 fs.insert_file(path!("/file.rs"), Default::default()).await;
7795
7796 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7797
7798 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7799 language_registry.add(rust_lang());
7800 let mut fake_servers = language_registry.register_fake_lsp(
7801 "Rust",
7802 FakeLspAdapter {
7803 capabilities: lsp::ServerCapabilities {
7804 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7805 ..Default::default()
7806 },
7807 ..Default::default()
7808 },
7809 );
7810
7811 let buffer = project
7812 .update(cx, |project, cx| {
7813 project.open_local_buffer(path!("/file.rs"), cx)
7814 })
7815 .await
7816 .unwrap();
7817
7818 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7819 let (editor, cx) = cx.add_window_view(|window, cx| {
7820 build_editor_with_project(project.clone(), buffer, window, cx)
7821 });
7822 editor.update_in(cx, |editor, window, cx| {
7823 editor.set_text("one\ntwo\nthree\n", window, cx)
7824 });
7825 assert!(cx.read(|cx| editor.is_dirty(cx)));
7826
7827 cx.executor().start_waiting();
7828 let fake_server = fake_servers.next().await.unwrap();
7829
7830 let save = editor
7831 .update_in(cx, |editor, window, cx| {
7832 editor.save(true, project.clone(), window, cx)
7833 })
7834 .unwrap();
7835 fake_server
7836 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7837 assert_eq!(
7838 params.text_document.uri,
7839 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7840 );
7841 assert_eq!(params.options.tab_size, 4);
7842 Ok(Some(vec![lsp::TextEdit::new(
7843 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7844 ", ".to_string(),
7845 )]))
7846 })
7847 .next()
7848 .await;
7849 cx.executor().start_waiting();
7850 save.await;
7851 assert_eq!(
7852 editor.update(cx, |editor, cx| editor.text(cx)),
7853 "one, two\nthree\n"
7854 );
7855 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7856
7857 editor.update_in(cx, |editor, window, cx| {
7858 editor.set_text("one\ntwo\nthree\n", window, cx)
7859 });
7860 assert!(cx.read(|cx| editor.is_dirty(cx)));
7861
7862 // Ensure we can still save even if formatting hangs.
7863 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7864 move |params, _| async move {
7865 assert_eq!(
7866 params.text_document.uri,
7867 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7868 );
7869 futures::future::pending::<()>().await;
7870 unreachable!()
7871 },
7872 );
7873 let save = editor
7874 .update_in(cx, |editor, window, cx| {
7875 editor.save(true, project.clone(), window, cx)
7876 })
7877 .unwrap();
7878 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7879 cx.executor().start_waiting();
7880 save.await;
7881 assert_eq!(
7882 editor.update(cx, |editor, cx| editor.text(cx)),
7883 "one\ntwo\nthree\n"
7884 );
7885 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7886
7887 // For non-dirty buffer, no formatting request should be sent
7888 let save = editor
7889 .update_in(cx, |editor, window, cx| {
7890 editor.save(true, project.clone(), window, cx)
7891 })
7892 .unwrap();
7893 let _pending_format_request = fake_server
7894 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7895 panic!("Should not be invoked on non-dirty buffer");
7896 })
7897 .next();
7898 cx.executor().start_waiting();
7899 save.await;
7900
7901 // Set Rust language override and assert overridden tabsize is sent to language server
7902 update_test_language_settings(cx, |settings| {
7903 settings.languages.insert(
7904 "Rust".into(),
7905 LanguageSettingsContent {
7906 tab_size: NonZeroU32::new(8),
7907 ..Default::default()
7908 },
7909 );
7910 });
7911
7912 editor.update_in(cx, |editor, window, cx| {
7913 editor.set_text("somehting_new\n", window, cx)
7914 });
7915 assert!(cx.read(|cx| editor.is_dirty(cx)));
7916 let save = editor
7917 .update_in(cx, |editor, window, cx| {
7918 editor.save(true, project.clone(), window, cx)
7919 })
7920 .unwrap();
7921 fake_server
7922 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7923 assert_eq!(
7924 params.text_document.uri,
7925 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7926 );
7927 assert_eq!(params.options.tab_size, 8);
7928 Ok(Some(vec![]))
7929 })
7930 .next()
7931 .await;
7932 cx.executor().start_waiting();
7933 save.await;
7934}
7935
7936#[gpui::test]
7937async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7938 init_test(cx, |settings| {
7939 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7940 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7941 ))
7942 });
7943
7944 let fs = FakeFs::new(cx.executor());
7945 fs.insert_file(path!("/file.rs"), Default::default()).await;
7946
7947 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7948
7949 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7950 language_registry.add(Arc::new(Language::new(
7951 LanguageConfig {
7952 name: "Rust".into(),
7953 matcher: LanguageMatcher {
7954 path_suffixes: vec!["rs".to_string()],
7955 ..Default::default()
7956 },
7957 ..LanguageConfig::default()
7958 },
7959 Some(tree_sitter_rust::LANGUAGE.into()),
7960 )));
7961 update_test_language_settings(cx, |settings| {
7962 // Enable Prettier formatting for the same buffer, and ensure
7963 // LSP is called instead of Prettier.
7964 settings.defaults.prettier = Some(PrettierSettings {
7965 allowed: true,
7966 ..PrettierSettings::default()
7967 });
7968 });
7969 let mut fake_servers = language_registry.register_fake_lsp(
7970 "Rust",
7971 FakeLspAdapter {
7972 capabilities: lsp::ServerCapabilities {
7973 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7974 ..Default::default()
7975 },
7976 ..Default::default()
7977 },
7978 );
7979
7980 let buffer = project
7981 .update(cx, |project, cx| {
7982 project.open_local_buffer(path!("/file.rs"), cx)
7983 })
7984 .await
7985 .unwrap();
7986
7987 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7988 let (editor, cx) = cx.add_window_view(|window, cx| {
7989 build_editor_with_project(project.clone(), buffer, window, cx)
7990 });
7991 editor.update_in(cx, |editor, window, cx| {
7992 editor.set_text("one\ntwo\nthree\n", window, cx)
7993 });
7994
7995 cx.executor().start_waiting();
7996 let fake_server = fake_servers.next().await.unwrap();
7997
7998 let format = editor
7999 .update_in(cx, |editor, window, cx| {
8000 editor.perform_format(
8001 project.clone(),
8002 FormatTrigger::Manual,
8003 FormatTarget::Buffers,
8004 window,
8005 cx,
8006 )
8007 })
8008 .unwrap();
8009 fake_server
8010 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8011 assert_eq!(
8012 params.text_document.uri,
8013 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8014 );
8015 assert_eq!(params.options.tab_size, 4);
8016 Ok(Some(vec![lsp::TextEdit::new(
8017 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
8018 ", ".to_string(),
8019 )]))
8020 })
8021 .next()
8022 .await;
8023 cx.executor().start_waiting();
8024 format.await;
8025 assert_eq!(
8026 editor.update(cx, |editor, cx| editor.text(cx)),
8027 "one, two\nthree\n"
8028 );
8029
8030 editor.update_in(cx, |editor, window, cx| {
8031 editor.set_text("one\ntwo\nthree\n", window, cx)
8032 });
8033 // Ensure we don't lock if formatting hangs.
8034 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8035 assert_eq!(
8036 params.text_document.uri,
8037 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8038 );
8039 futures::future::pending::<()>().await;
8040 unreachable!()
8041 });
8042 let format = editor
8043 .update_in(cx, |editor, window, cx| {
8044 editor.perform_format(
8045 project,
8046 FormatTrigger::Manual,
8047 FormatTarget::Buffers,
8048 window,
8049 cx,
8050 )
8051 })
8052 .unwrap();
8053 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8054 cx.executor().start_waiting();
8055 format.await;
8056 assert_eq!(
8057 editor.update(cx, |editor, cx| editor.text(cx)),
8058 "one\ntwo\nthree\n"
8059 );
8060}
8061
8062#[gpui::test]
8063async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8064 init_test(cx, |settings| {
8065 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8066 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8067 ))
8068 });
8069
8070 let fs = FakeFs::new(cx.executor());
8071 fs.insert_file(path!("/file.ts"), Default::default()).await;
8072
8073 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8074
8075 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8076 language_registry.add(Arc::new(Language::new(
8077 LanguageConfig {
8078 name: "TypeScript".into(),
8079 matcher: LanguageMatcher {
8080 path_suffixes: vec!["ts".to_string()],
8081 ..Default::default()
8082 },
8083 ..LanguageConfig::default()
8084 },
8085 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8086 )));
8087 update_test_language_settings(cx, |settings| {
8088 settings.defaults.prettier = Some(PrettierSettings {
8089 allowed: true,
8090 ..PrettierSettings::default()
8091 });
8092 });
8093 let mut fake_servers = language_registry.register_fake_lsp(
8094 "TypeScript",
8095 FakeLspAdapter {
8096 capabilities: lsp::ServerCapabilities {
8097 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8098 ..Default::default()
8099 },
8100 ..Default::default()
8101 },
8102 );
8103
8104 let buffer = project
8105 .update(cx, |project, cx| {
8106 project.open_local_buffer(path!("/file.ts"), cx)
8107 })
8108 .await
8109 .unwrap();
8110
8111 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8112 let (editor, cx) = cx.add_window_view(|window, cx| {
8113 build_editor_with_project(project.clone(), buffer, window, cx)
8114 });
8115 editor.update_in(cx, |editor, window, cx| {
8116 editor.set_text(
8117 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8118 window,
8119 cx,
8120 )
8121 });
8122
8123 cx.executor().start_waiting();
8124 let fake_server = fake_servers.next().await.unwrap();
8125
8126 let format = editor
8127 .update_in(cx, |editor, window, cx| {
8128 editor.perform_code_action_kind(
8129 project.clone(),
8130 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8131 window,
8132 cx,
8133 )
8134 })
8135 .unwrap();
8136 fake_server
8137 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8138 assert_eq!(
8139 params.text_document.uri,
8140 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8141 );
8142 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8143 lsp::CodeAction {
8144 title: "Organize Imports".to_string(),
8145 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8146 edit: Some(lsp::WorkspaceEdit {
8147 changes: Some(
8148 [(
8149 params.text_document.uri.clone(),
8150 vec![lsp::TextEdit::new(
8151 lsp::Range::new(
8152 lsp::Position::new(1, 0),
8153 lsp::Position::new(2, 0),
8154 ),
8155 "".to_string(),
8156 )],
8157 )]
8158 .into_iter()
8159 .collect(),
8160 ),
8161 ..Default::default()
8162 }),
8163 ..Default::default()
8164 },
8165 )]))
8166 })
8167 .next()
8168 .await;
8169 cx.executor().start_waiting();
8170 format.await;
8171 assert_eq!(
8172 editor.update(cx, |editor, cx| editor.text(cx)),
8173 "import { a } from 'module';\n\nconst x = a;\n"
8174 );
8175
8176 editor.update_in(cx, |editor, window, cx| {
8177 editor.set_text(
8178 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8179 window,
8180 cx,
8181 )
8182 });
8183 // Ensure we don't lock if code action hangs.
8184 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8185 move |params, _| async move {
8186 assert_eq!(
8187 params.text_document.uri,
8188 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8189 );
8190 futures::future::pending::<()>().await;
8191 unreachable!()
8192 },
8193 );
8194 let format = editor
8195 .update_in(cx, |editor, window, cx| {
8196 editor.perform_code_action_kind(
8197 project,
8198 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8199 window,
8200 cx,
8201 )
8202 })
8203 .unwrap();
8204 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8205 cx.executor().start_waiting();
8206 format.await;
8207 assert_eq!(
8208 editor.update(cx, |editor, cx| editor.text(cx)),
8209 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8210 );
8211}
8212
8213#[gpui::test]
8214async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8215 init_test(cx, |_| {});
8216
8217 let mut cx = EditorLspTestContext::new_rust(
8218 lsp::ServerCapabilities {
8219 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8220 ..Default::default()
8221 },
8222 cx,
8223 )
8224 .await;
8225
8226 cx.set_state(indoc! {"
8227 one.twoˇ
8228 "});
8229
8230 // The format request takes a long time. When it completes, it inserts
8231 // a newline and an indent before the `.`
8232 cx.lsp
8233 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8234 let executor = cx.background_executor().clone();
8235 async move {
8236 executor.timer(Duration::from_millis(100)).await;
8237 Ok(Some(vec![lsp::TextEdit {
8238 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8239 new_text: "\n ".into(),
8240 }]))
8241 }
8242 });
8243
8244 // Submit a format request.
8245 let format_1 = cx
8246 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8247 .unwrap();
8248 cx.executor().run_until_parked();
8249
8250 // Submit a second format request.
8251 let format_2 = cx
8252 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8253 .unwrap();
8254 cx.executor().run_until_parked();
8255
8256 // Wait for both format requests to complete
8257 cx.executor().advance_clock(Duration::from_millis(200));
8258 cx.executor().start_waiting();
8259 format_1.await.unwrap();
8260 cx.executor().start_waiting();
8261 format_2.await.unwrap();
8262
8263 // The formatting edits only happens once.
8264 cx.assert_editor_state(indoc! {"
8265 one
8266 .twoˇ
8267 "});
8268}
8269
8270#[gpui::test]
8271async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8272 init_test(cx, |settings| {
8273 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8274 });
8275
8276 let mut cx = EditorLspTestContext::new_rust(
8277 lsp::ServerCapabilities {
8278 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8279 ..Default::default()
8280 },
8281 cx,
8282 )
8283 .await;
8284
8285 // Set up a buffer white some trailing whitespace and no trailing newline.
8286 cx.set_state(
8287 &[
8288 "one ", //
8289 "twoˇ", //
8290 "three ", //
8291 "four", //
8292 ]
8293 .join("\n"),
8294 );
8295
8296 // Submit a format request.
8297 let format = cx
8298 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8299 .unwrap();
8300
8301 // Record which buffer changes have been sent to the language server
8302 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8303 cx.lsp
8304 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8305 let buffer_changes = buffer_changes.clone();
8306 move |params, _| {
8307 buffer_changes.lock().extend(
8308 params
8309 .content_changes
8310 .into_iter()
8311 .map(|e| (e.range.unwrap(), e.text)),
8312 );
8313 }
8314 });
8315
8316 // Handle formatting requests to the language server.
8317 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8318 let buffer_changes = buffer_changes.clone();
8319 move |_, _| {
8320 // When formatting is requested, trailing whitespace has already been stripped,
8321 // and the trailing newline has already been added.
8322 assert_eq!(
8323 &buffer_changes.lock()[1..],
8324 &[
8325 (
8326 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8327 "".into()
8328 ),
8329 (
8330 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8331 "".into()
8332 ),
8333 (
8334 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8335 "\n".into()
8336 ),
8337 ]
8338 );
8339
8340 // Insert blank lines between each line of the buffer.
8341 async move {
8342 Ok(Some(vec![
8343 lsp::TextEdit {
8344 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8345 new_text: "\n".into(),
8346 },
8347 lsp::TextEdit {
8348 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8349 new_text: "\n".into(),
8350 },
8351 ]))
8352 }
8353 }
8354 });
8355
8356 // After formatting the buffer, the trailing whitespace is stripped,
8357 // a newline is appended, and the edits provided by the language server
8358 // have been applied.
8359 format.await.unwrap();
8360 cx.assert_editor_state(
8361 &[
8362 "one", //
8363 "", //
8364 "twoˇ", //
8365 "", //
8366 "three", //
8367 "four", //
8368 "", //
8369 ]
8370 .join("\n"),
8371 );
8372
8373 // Undoing the formatting undoes the trailing whitespace removal, the
8374 // trailing newline, and the LSP edits.
8375 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8376 cx.assert_editor_state(
8377 &[
8378 "one ", //
8379 "twoˇ", //
8380 "three ", //
8381 "four", //
8382 ]
8383 .join("\n"),
8384 );
8385}
8386
8387#[gpui::test]
8388async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8389 cx: &mut TestAppContext,
8390) {
8391 init_test(cx, |_| {});
8392
8393 cx.update(|cx| {
8394 cx.update_global::<SettingsStore, _>(|settings, cx| {
8395 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8396 settings.auto_signature_help = Some(true);
8397 });
8398 });
8399 });
8400
8401 let mut cx = EditorLspTestContext::new_rust(
8402 lsp::ServerCapabilities {
8403 signature_help_provider: Some(lsp::SignatureHelpOptions {
8404 ..Default::default()
8405 }),
8406 ..Default::default()
8407 },
8408 cx,
8409 )
8410 .await;
8411
8412 let language = Language::new(
8413 LanguageConfig {
8414 name: "Rust".into(),
8415 brackets: BracketPairConfig {
8416 pairs: vec![
8417 BracketPair {
8418 start: "{".to_string(),
8419 end: "}".to_string(),
8420 close: true,
8421 surround: true,
8422 newline: true,
8423 },
8424 BracketPair {
8425 start: "(".to_string(),
8426 end: ")".to_string(),
8427 close: true,
8428 surround: true,
8429 newline: true,
8430 },
8431 BracketPair {
8432 start: "/*".to_string(),
8433 end: " */".to_string(),
8434 close: true,
8435 surround: true,
8436 newline: true,
8437 },
8438 BracketPair {
8439 start: "[".to_string(),
8440 end: "]".to_string(),
8441 close: false,
8442 surround: false,
8443 newline: true,
8444 },
8445 BracketPair {
8446 start: "\"".to_string(),
8447 end: "\"".to_string(),
8448 close: true,
8449 surround: true,
8450 newline: false,
8451 },
8452 BracketPair {
8453 start: "<".to_string(),
8454 end: ">".to_string(),
8455 close: false,
8456 surround: true,
8457 newline: true,
8458 },
8459 ],
8460 ..Default::default()
8461 },
8462 autoclose_before: "})]".to_string(),
8463 ..Default::default()
8464 },
8465 Some(tree_sitter_rust::LANGUAGE.into()),
8466 );
8467 let language = Arc::new(language);
8468
8469 cx.language_registry().add(language.clone());
8470 cx.update_buffer(|buffer, cx| {
8471 buffer.set_language(Some(language), cx);
8472 });
8473
8474 cx.set_state(
8475 &r#"
8476 fn main() {
8477 sampleˇ
8478 }
8479 "#
8480 .unindent(),
8481 );
8482
8483 cx.update_editor(|editor, window, cx| {
8484 editor.handle_input("(", window, cx);
8485 });
8486 cx.assert_editor_state(
8487 &"
8488 fn main() {
8489 sample(ˇ)
8490 }
8491 "
8492 .unindent(),
8493 );
8494
8495 let mocked_response = lsp::SignatureHelp {
8496 signatures: vec![lsp::SignatureInformation {
8497 label: "fn sample(param1: u8, param2: u8)".to_string(),
8498 documentation: None,
8499 parameters: Some(vec![
8500 lsp::ParameterInformation {
8501 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8502 documentation: None,
8503 },
8504 lsp::ParameterInformation {
8505 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8506 documentation: None,
8507 },
8508 ]),
8509 active_parameter: None,
8510 }],
8511 active_signature: Some(0),
8512 active_parameter: Some(0),
8513 };
8514 handle_signature_help_request(&mut cx, mocked_response).await;
8515
8516 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8517 .await;
8518
8519 cx.editor(|editor, _, _| {
8520 let signature_help_state = editor.signature_help_state.popover().cloned();
8521 assert_eq!(
8522 signature_help_state.unwrap().label,
8523 "param1: u8, param2: u8"
8524 );
8525 });
8526}
8527
8528#[gpui::test]
8529async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8530 init_test(cx, |_| {});
8531
8532 cx.update(|cx| {
8533 cx.update_global::<SettingsStore, _>(|settings, cx| {
8534 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8535 settings.auto_signature_help = Some(false);
8536 settings.show_signature_help_after_edits = Some(false);
8537 });
8538 });
8539 });
8540
8541 let mut cx = EditorLspTestContext::new_rust(
8542 lsp::ServerCapabilities {
8543 signature_help_provider: Some(lsp::SignatureHelpOptions {
8544 ..Default::default()
8545 }),
8546 ..Default::default()
8547 },
8548 cx,
8549 )
8550 .await;
8551
8552 let language = Language::new(
8553 LanguageConfig {
8554 name: "Rust".into(),
8555 brackets: BracketPairConfig {
8556 pairs: vec![
8557 BracketPair {
8558 start: "{".to_string(),
8559 end: "}".to_string(),
8560 close: true,
8561 surround: true,
8562 newline: true,
8563 },
8564 BracketPair {
8565 start: "(".to_string(),
8566 end: ")".to_string(),
8567 close: true,
8568 surround: true,
8569 newline: true,
8570 },
8571 BracketPair {
8572 start: "/*".to_string(),
8573 end: " */".to_string(),
8574 close: true,
8575 surround: true,
8576 newline: true,
8577 },
8578 BracketPair {
8579 start: "[".to_string(),
8580 end: "]".to_string(),
8581 close: false,
8582 surround: false,
8583 newline: true,
8584 },
8585 BracketPair {
8586 start: "\"".to_string(),
8587 end: "\"".to_string(),
8588 close: true,
8589 surround: true,
8590 newline: false,
8591 },
8592 BracketPair {
8593 start: "<".to_string(),
8594 end: ">".to_string(),
8595 close: false,
8596 surround: true,
8597 newline: true,
8598 },
8599 ],
8600 ..Default::default()
8601 },
8602 autoclose_before: "})]".to_string(),
8603 ..Default::default()
8604 },
8605 Some(tree_sitter_rust::LANGUAGE.into()),
8606 );
8607 let language = Arc::new(language);
8608
8609 cx.language_registry().add(language.clone());
8610 cx.update_buffer(|buffer, cx| {
8611 buffer.set_language(Some(language), cx);
8612 });
8613
8614 // Ensure that signature_help is not called when no signature help is enabled.
8615 cx.set_state(
8616 &r#"
8617 fn main() {
8618 sampleˇ
8619 }
8620 "#
8621 .unindent(),
8622 );
8623 cx.update_editor(|editor, window, cx| {
8624 editor.handle_input("(", window, cx);
8625 });
8626 cx.assert_editor_state(
8627 &"
8628 fn main() {
8629 sample(ˇ)
8630 }
8631 "
8632 .unindent(),
8633 );
8634 cx.editor(|editor, _, _| {
8635 assert!(editor.signature_help_state.task().is_none());
8636 });
8637
8638 let mocked_response = lsp::SignatureHelp {
8639 signatures: vec![lsp::SignatureInformation {
8640 label: "fn sample(param1: u8, param2: u8)".to_string(),
8641 documentation: None,
8642 parameters: Some(vec![
8643 lsp::ParameterInformation {
8644 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8645 documentation: None,
8646 },
8647 lsp::ParameterInformation {
8648 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8649 documentation: None,
8650 },
8651 ]),
8652 active_parameter: None,
8653 }],
8654 active_signature: Some(0),
8655 active_parameter: Some(0),
8656 };
8657
8658 // Ensure that signature_help is called when enabled afte edits
8659 cx.update(|_, cx| {
8660 cx.update_global::<SettingsStore, _>(|settings, cx| {
8661 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8662 settings.auto_signature_help = Some(false);
8663 settings.show_signature_help_after_edits = Some(true);
8664 });
8665 });
8666 });
8667 cx.set_state(
8668 &r#"
8669 fn main() {
8670 sampleˇ
8671 }
8672 "#
8673 .unindent(),
8674 );
8675 cx.update_editor(|editor, window, cx| {
8676 editor.handle_input("(", window, cx);
8677 });
8678 cx.assert_editor_state(
8679 &"
8680 fn main() {
8681 sample(ˇ)
8682 }
8683 "
8684 .unindent(),
8685 );
8686 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8687 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8688 .await;
8689 cx.update_editor(|editor, _, _| {
8690 let signature_help_state = editor.signature_help_state.popover().cloned();
8691 assert!(signature_help_state.is_some());
8692 assert_eq!(
8693 signature_help_state.unwrap().label,
8694 "param1: u8, param2: u8"
8695 );
8696 editor.signature_help_state = SignatureHelpState::default();
8697 });
8698
8699 // Ensure that signature_help is called when auto signature help override is enabled
8700 cx.update(|_, cx| {
8701 cx.update_global::<SettingsStore, _>(|settings, cx| {
8702 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8703 settings.auto_signature_help = Some(true);
8704 settings.show_signature_help_after_edits = Some(false);
8705 });
8706 });
8707 });
8708 cx.set_state(
8709 &r#"
8710 fn main() {
8711 sampleˇ
8712 }
8713 "#
8714 .unindent(),
8715 );
8716 cx.update_editor(|editor, window, cx| {
8717 editor.handle_input("(", window, cx);
8718 });
8719 cx.assert_editor_state(
8720 &"
8721 fn main() {
8722 sample(ˇ)
8723 }
8724 "
8725 .unindent(),
8726 );
8727 handle_signature_help_request(&mut cx, mocked_response).await;
8728 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8729 .await;
8730 cx.editor(|editor, _, _| {
8731 let signature_help_state = editor.signature_help_state.popover().cloned();
8732 assert!(signature_help_state.is_some());
8733 assert_eq!(
8734 signature_help_state.unwrap().label,
8735 "param1: u8, param2: u8"
8736 );
8737 });
8738}
8739
8740#[gpui::test]
8741async fn test_signature_help(cx: &mut TestAppContext) {
8742 init_test(cx, |_| {});
8743 cx.update(|cx| {
8744 cx.update_global::<SettingsStore, _>(|settings, cx| {
8745 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8746 settings.auto_signature_help = Some(true);
8747 });
8748 });
8749 });
8750
8751 let mut cx = EditorLspTestContext::new_rust(
8752 lsp::ServerCapabilities {
8753 signature_help_provider: Some(lsp::SignatureHelpOptions {
8754 ..Default::default()
8755 }),
8756 ..Default::default()
8757 },
8758 cx,
8759 )
8760 .await;
8761
8762 // A test that directly calls `show_signature_help`
8763 cx.update_editor(|editor, window, cx| {
8764 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8765 });
8766
8767 let mocked_response = lsp::SignatureHelp {
8768 signatures: vec![lsp::SignatureInformation {
8769 label: "fn sample(param1: u8, param2: u8)".to_string(),
8770 documentation: None,
8771 parameters: Some(vec![
8772 lsp::ParameterInformation {
8773 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8774 documentation: None,
8775 },
8776 lsp::ParameterInformation {
8777 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8778 documentation: None,
8779 },
8780 ]),
8781 active_parameter: None,
8782 }],
8783 active_signature: Some(0),
8784 active_parameter: Some(0),
8785 };
8786 handle_signature_help_request(&mut cx, mocked_response).await;
8787
8788 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8789 .await;
8790
8791 cx.editor(|editor, _, _| {
8792 let signature_help_state = editor.signature_help_state.popover().cloned();
8793 assert!(signature_help_state.is_some());
8794 assert_eq!(
8795 signature_help_state.unwrap().label,
8796 "param1: u8, param2: u8"
8797 );
8798 });
8799
8800 // When exiting outside from inside the brackets, `signature_help` is closed.
8801 cx.set_state(indoc! {"
8802 fn main() {
8803 sample(ˇ);
8804 }
8805
8806 fn sample(param1: u8, param2: u8) {}
8807 "});
8808
8809 cx.update_editor(|editor, window, cx| {
8810 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8811 });
8812
8813 let mocked_response = lsp::SignatureHelp {
8814 signatures: Vec::new(),
8815 active_signature: None,
8816 active_parameter: None,
8817 };
8818 handle_signature_help_request(&mut cx, mocked_response).await;
8819
8820 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8821 .await;
8822
8823 cx.editor(|editor, _, _| {
8824 assert!(!editor.signature_help_state.is_shown());
8825 });
8826
8827 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8828 cx.set_state(indoc! {"
8829 fn main() {
8830 sample(ˇ);
8831 }
8832
8833 fn sample(param1: u8, param2: u8) {}
8834 "});
8835
8836 let mocked_response = lsp::SignatureHelp {
8837 signatures: vec![lsp::SignatureInformation {
8838 label: "fn sample(param1: u8, param2: u8)".to_string(),
8839 documentation: None,
8840 parameters: Some(vec![
8841 lsp::ParameterInformation {
8842 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8843 documentation: None,
8844 },
8845 lsp::ParameterInformation {
8846 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8847 documentation: None,
8848 },
8849 ]),
8850 active_parameter: None,
8851 }],
8852 active_signature: Some(0),
8853 active_parameter: Some(0),
8854 };
8855 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8856 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8857 .await;
8858 cx.editor(|editor, _, _| {
8859 assert!(editor.signature_help_state.is_shown());
8860 });
8861
8862 // Restore the popover with more parameter input
8863 cx.set_state(indoc! {"
8864 fn main() {
8865 sample(param1, param2ˇ);
8866 }
8867
8868 fn sample(param1: u8, param2: u8) {}
8869 "});
8870
8871 let mocked_response = lsp::SignatureHelp {
8872 signatures: vec![lsp::SignatureInformation {
8873 label: "fn sample(param1: u8, param2: u8)".to_string(),
8874 documentation: None,
8875 parameters: Some(vec![
8876 lsp::ParameterInformation {
8877 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8878 documentation: None,
8879 },
8880 lsp::ParameterInformation {
8881 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8882 documentation: None,
8883 },
8884 ]),
8885 active_parameter: None,
8886 }],
8887 active_signature: Some(0),
8888 active_parameter: Some(1),
8889 };
8890 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8891 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8892 .await;
8893
8894 // When selecting a range, the popover is gone.
8895 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8896 cx.update_editor(|editor, window, cx| {
8897 editor.change_selections(None, window, cx, |s| {
8898 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8899 })
8900 });
8901 cx.assert_editor_state(indoc! {"
8902 fn main() {
8903 sample(param1, «ˇparam2»);
8904 }
8905
8906 fn sample(param1: u8, param2: u8) {}
8907 "});
8908 cx.editor(|editor, _, _| {
8909 assert!(!editor.signature_help_state.is_shown());
8910 });
8911
8912 // When unselecting again, the popover is back if within the brackets.
8913 cx.update_editor(|editor, window, cx| {
8914 editor.change_selections(None, window, cx, |s| {
8915 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8916 })
8917 });
8918 cx.assert_editor_state(indoc! {"
8919 fn main() {
8920 sample(param1, ˇparam2);
8921 }
8922
8923 fn sample(param1: u8, param2: u8) {}
8924 "});
8925 handle_signature_help_request(&mut cx, mocked_response).await;
8926 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8927 .await;
8928 cx.editor(|editor, _, _| {
8929 assert!(editor.signature_help_state.is_shown());
8930 });
8931
8932 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8933 cx.update_editor(|editor, window, cx| {
8934 editor.change_selections(None, window, cx, |s| {
8935 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8936 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8937 })
8938 });
8939 cx.assert_editor_state(indoc! {"
8940 fn main() {
8941 sample(param1, ˇparam2);
8942 }
8943
8944 fn sample(param1: u8, param2: u8) {}
8945 "});
8946
8947 let mocked_response = lsp::SignatureHelp {
8948 signatures: vec![lsp::SignatureInformation {
8949 label: "fn sample(param1: u8, param2: u8)".to_string(),
8950 documentation: None,
8951 parameters: Some(vec![
8952 lsp::ParameterInformation {
8953 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8954 documentation: None,
8955 },
8956 lsp::ParameterInformation {
8957 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8958 documentation: None,
8959 },
8960 ]),
8961 active_parameter: None,
8962 }],
8963 active_signature: Some(0),
8964 active_parameter: Some(1),
8965 };
8966 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8967 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8968 .await;
8969 cx.update_editor(|editor, _, cx| {
8970 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8971 });
8972 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8973 .await;
8974 cx.update_editor(|editor, window, cx| {
8975 editor.change_selections(None, window, cx, |s| {
8976 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8977 })
8978 });
8979 cx.assert_editor_state(indoc! {"
8980 fn main() {
8981 sample(param1, «ˇparam2»);
8982 }
8983
8984 fn sample(param1: u8, param2: u8) {}
8985 "});
8986 cx.update_editor(|editor, window, cx| {
8987 editor.change_selections(None, window, cx, |s| {
8988 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8989 })
8990 });
8991 cx.assert_editor_state(indoc! {"
8992 fn main() {
8993 sample(param1, ˇparam2);
8994 }
8995
8996 fn sample(param1: u8, param2: u8) {}
8997 "});
8998 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8999 .await;
9000}
9001
9002#[gpui::test]
9003async fn test_completion(cx: &mut TestAppContext) {
9004 init_test(cx, |_| {});
9005
9006 let mut cx = EditorLspTestContext::new_rust(
9007 lsp::ServerCapabilities {
9008 completion_provider: Some(lsp::CompletionOptions {
9009 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9010 resolve_provider: Some(true),
9011 ..Default::default()
9012 }),
9013 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9014 ..Default::default()
9015 },
9016 cx,
9017 )
9018 .await;
9019 let counter = Arc::new(AtomicUsize::new(0));
9020
9021 cx.set_state(indoc! {"
9022 oneˇ
9023 two
9024 three
9025 "});
9026 cx.simulate_keystroke(".");
9027 handle_completion_request(
9028 &mut cx,
9029 indoc! {"
9030 one.|<>
9031 two
9032 three
9033 "},
9034 vec!["first_completion", "second_completion"],
9035 counter.clone(),
9036 )
9037 .await;
9038 cx.condition(|editor, _| editor.context_menu_visible())
9039 .await;
9040 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9041
9042 let _handler = handle_signature_help_request(
9043 &mut cx,
9044 lsp::SignatureHelp {
9045 signatures: vec![lsp::SignatureInformation {
9046 label: "test signature".to_string(),
9047 documentation: None,
9048 parameters: Some(vec![lsp::ParameterInformation {
9049 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9050 documentation: None,
9051 }]),
9052 active_parameter: None,
9053 }],
9054 active_signature: None,
9055 active_parameter: None,
9056 },
9057 );
9058 cx.update_editor(|editor, window, cx| {
9059 assert!(
9060 !editor.signature_help_state.is_shown(),
9061 "No signature help was called for"
9062 );
9063 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9064 });
9065 cx.run_until_parked();
9066 cx.update_editor(|editor, _, _| {
9067 assert!(
9068 !editor.signature_help_state.is_shown(),
9069 "No signature help should be shown when completions menu is open"
9070 );
9071 });
9072
9073 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9074 editor.context_menu_next(&Default::default(), window, cx);
9075 editor
9076 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9077 .unwrap()
9078 });
9079 cx.assert_editor_state(indoc! {"
9080 one.second_completionˇ
9081 two
9082 three
9083 "});
9084
9085 handle_resolve_completion_request(
9086 &mut cx,
9087 Some(vec![
9088 (
9089 //This overlaps with the primary completion edit which is
9090 //misbehavior from the LSP spec, test that we filter it out
9091 indoc! {"
9092 one.second_ˇcompletion
9093 two
9094 threeˇ
9095 "},
9096 "overlapping additional edit",
9097 ),
9098 (
9099 indoc! {"
9100 one.second_completion
9101 two
9102 threeˇ
9103 "},
9104 "\nadditional edit",
9105 ),
9106 ]),
9107 )
9108 .await;
9109 apply_additional_edits.await.unwrap();
9110 cx.assert_editor_state(indoc! {"
9111 one.second_completionˇ
9112 two
9113 three
9114 additional edit
9115 "});
9116
9117 cx.set_state(indoc! {"
9118 one.second_completion
9119 twoˇ
9120 threeˇ
9121 additional edit
9122 "});
9123 cx.simulate_keystroke(" ");
9124 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9125 cx.simulate_keystroke("s");
9126 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9127
9128 cx.assert_editor_state(indoc! {"
9129 one.second_completion
9130 two sˇ
9131 three sˇ
9132 additional edit
9133 "});
9134 handle_completion_request(
9135 &mut cx,
9136 indoc! {"
9137 one.second_completion
9138 two s
9139 three <s|>
9140 additional edit
9141 "},
9142 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9143 counter.clone(),
9144 )
9145 .await;
9146 cx.condition(|editor, _| editor.context_menu_visible())
9147 .await;
9148 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9149
9150 cx.simulate_keystroke("i");
9151
9152 handle_completion_request(
9153 &mut cx,
9154 indoc! {"
9155 one.second_completion
9156 two si
9157 three <si|>
9158 additional edit
9159 "},
9160 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9161 counter.clone(),
9162 )
9163 .await;
9164 cx.condition(|editor, _| editor.context_menu_visible())
9165 .await;
9166 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9167
9168 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9169 editor
9170 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9171 .unwrap()
9172 });
9173 cx.assert_editor_state(indoc! {"
9174 one.second_completion
9175 two sixth_completionˇ
9176 three sixth_completionˇ
9177 additional edit
9178 "});
9179
9180 apply_additional_edits.await.unwrap();
9181
9182 update_test_language_settings(&mut cx, |settings| {
9183 settings.defaults.show_completions_on_input = Some(false);
9184 });
9185 cx.set_state("editorˇ");
9186 cx.simulate_keystroke(".");
9187 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9188 cx.simulate_keystroke("c");
9189 cx.simulate_keystroke("l");
9190 cx.simulate_keystroke("o");
9191 cx.assert_editor_state("editor.cloˇ");
9192 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9193 cx.update_editor(|editor, window, cx| {
9194 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9195 });
9196 handle_completion_request(
9197 &mut cx,
9198 "editor.<clo|>",
9199 vec!["close", "clobber"],
9200 counter.clone(),
9201 )
9202 .await;
9203 cx.condition(|editor, _| editor.context_menu_visible())
9204 .await;
9205 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9206
9207 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9208 editor
9209 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9210 .unwrap()
9211 });
9212 cx.assert_editor_state("editor.closeˇ");
9213 handle_resolve_completion_request(&mut cx, None).await;
9214 apply_additional_edits.await.unwrap();
9215}
9216
9217#[gpui::test]
9218async fn test_word_completion(cx: &mut TestAppContext) {
9219 let lsp_fetch_timeout_ms = 10;
9220 init_test(cx, |language_settings| {
9221 language_settings.defaults.completions = Some(CompletionSettings {
9222 words: WordsCompletionMode::Fallback,
9223 lsp: true,
9224 lsp_fetch_timeout_ms: 10,
9225 });
9226 });
9227
9228 let mut cx = EditorLspTestContext::new_rust(
9229 lsp::ServerCapabilities {
9230 completion_provider: Some(lsp::CompletionOptions {
9231 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9232 ..lsp::CompletionOptions::default()
9233 }),
9234 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9235 ..lsp::ServerCapabilities::default()
9236 },
9237 cx,
9238 )
9239 .await;
9240
9241 let throttle_completions = Arc::new(AtomicBool::new(false));
9242
9243 let lsp_throttle_completions = throttle_completions.clone();
9244 let _completion_requests_handler =
9245 cx.lsp
9246 .server
9247 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9248 let lsp_throttle_completions = lsp_throttle_completions.clone();
9249 async move {
9250 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9251 cx.background_executor()
9252 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9253 .await;
9254 }
9255 Ok(Some(lsp::CompletionResponse::Array(vec![
9256 lsp::CompletionItem {
9257 label: "first".into(),
9258 ..lsp::CompletionItem::default()
9259 },
9260 lsp::CompletionItem {
9261 label: "last".into(),
9262 ..lsp::CompletionItem::default()
9263 },
9264 ])))
9265 }
9266 });
9267
9268 cx.set_state(indoc! {"
9269 oneˇ
9270 two
9271 three
9272 "});
9273 cx.simulate_keystroke(".");
9274 cx.executor().run_until_parked();
9275 cx.condition(|editor, _| editor.context_menu_visible())
9276 .await;
9277 cx.update_editor(|editor, window, cx| {
9278 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9279 {
9280 assert_eq!(
9281 completion_menu_entries(&menu),
9282 &["first", "last"],
9283 "When LSP server is fast to reply, no fallback word completions are used"
9284 );
9285 } else {
9286 panic!("expected completion menu to be open");
9287 }
9288 editor.cancel(&Cancel, window, cx);
9289 });
9290 cx.executor().run_until_parked();
9291 cx.condition(|editor, _| !editor.context_menu_visible())
9292 .await;
9293
9294 throttle_completions.store(true, atomic::Ordering::Release);
9295 cx.simulate_keystroke(".");
9296 cx.executor()
9297 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9298 cx.executor().run_until_parked();
9299 cx.condition(|editor, _| editor.context_menu_visible())
9300 .await;
9301 cx.update_editor(|editor, _, _| {
9302 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9303 {
9304 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9305 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9306 } else {
9307 panic!("expected completion menu to be open");
9308 }
9309 });
9310}
9311
9312#[gpui::test]
9313async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9314 init_test(cx, |language_settings| {
9315 language_settings.defaults.completions = Some(CompletionSettings {
9316 words: WordsCompletionMode::Enabled,
9317 lsp: true,
9318 lsp_fetch_timeout_ms: 0,
9319 });
9320 });
9321
9322 let mut cx = EditorLspTestContext::new_rust(
9323 lsp::ServerCapabilities {
9324 completion_provider: Some(lsp::CompletionOptions {
9325 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9326 ..lsp::CompletionOptions::default()
9327 }),
9328 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9329 ..lsp::ServerCapabilities::default()
9330 },
9331 cx,
9332 )
9333 .await;
9334
9335 let _completion_requests_handler =
9336 cx.lsp
9337 .server
9338 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9339 Ok(Some(lsp::CompletionResponse::Array(vec![
9340 lsp::CompletionItem {
9341 label: "first".into(),
9342 ..lsp::CompletionItem::default()
9343 },
9344 lsp::CompletionItem {
9345 label: "last".into(),
9346 ..lsp::CompletionItem::default()
9347 },
9348 ])))
9349 });
9350
9351 cx.set_state(indoc! {"ˇ
9352 first
9353 last
9354 second
9355 "});
9356 cx.simulate_keystroke(".");
9357 cx.executor().run_until_parked();
9358 cx.condition(|editor, _| editor.context_menu_visible())
9359 .await;
9360 cx.update_editor(|editor, _, _| {
9361 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9362 {
9363 assert_eq!(
9364 completion_menu_entries(&menu),
9365 &["first", "last", "second"],
9366 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9367 );
9368 } else {
9369 panic!("expected completion menu to be open");
9370 }
9371 });
9372}
9373
9374#[gpui::test]
9375async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
9376 init_test(cx, |language_settings| {
9377 language_settings.defaults.completions = Some(CompletionSettings {
9378 words: WordsCompletionMode::Disabled,
9379 lsp: true,
9380 lsp_fetch_timeout_ms: 0,
9381 });
9382 });
9383
9384 let mut cx = EditorLspTestContext::new_rust(
9385 lsp::ServerCapabilities {
9386 completion_provider: Some(lsp::CompletionOptions {
9387 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9388 ..lsp::CompletionOptions::default()
9389 }),
9390 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9391 ..lsp::ServerCapabilities::default()
9392 },
9393 cx,
9394 )
9395 .await;
9396
9397 let _completion_requests_handler =
9398 cx.lsp
9399 .server
9400 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9401 panic!("LSP completions should not be queried when dealing with word completions")
9402 });
9403
9404 cx.set_state(indoc! {"ˇ
9405 first
9406 last
9407 second
9408 "});
9409 cx.update_editor(|editor, window, cx| {
9410 editor.show_word_completions(&ShowWordCompletions, window, cx);
9411 });
9412 cx.executor().run_until_parked();
9413 cx.condition(|editor, _| editor.context_menu_visible())
9414 .await;
9415 cx.update_editor(|editor, _, _| {
9416 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9417 {
9418 assert_eq!(
9419 completion_menu_entries(&menu),
9420 &["first", "last", "second"],
9421 "`ShowWordCompletions` action should show word completions"
9422 );
9423 } else {
9424 panic!("expected completion menu to be open");
9425 }
9426 });
9427
9428 cx.simulate_keystroke("s");
9429 cx.executor().run_until_parked();
9430 cx.condition(|editor, _| editor.context_menu_visible())
9431 .await;
9432 cx.update_editor(|editor, _, _| {
9433 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9434 {
9435 assert_eq!(
9436 completion_menu_entries(&menu),
9437 &["second"],
9438 "After showing word completions, further editing should filter them and not query the LSP"
9439 );
9440 } else {
9441 panic!("expected completion menu to be open");
9442 }
9443 });
9444}
9445
9446#[gpui::test]
9447async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
9448 init_test(cx, |language_settings| {
9449 language_settings.defaults.completions = Some(CompletionSettings {
9450 words: WordsCompletionMode::Fallback,
9451 lsp: false,
9452 lsp_fetch_timeout_ms: 0,
9453 });
9454 });
9455
9456 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9457
9458 cx.set_state(indoc! {"ˇ
9459 0_usize
9460 let
9461 33
9462 4.5f32
9463 "});
9464 cx.update_editor(|editor, window, cx| {
9465 editor.show_completions(&ShowCompletions::default(), window, cx);
9466 });
9467 cx.executor().run_until_parked();
9468 cx.condition(|editor, _| editor.context_menu_visible())
9469 .await;
9470 cx.update_editor(|editor, window, cx| {
9471 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9472 {
9473 assert_eq!(
9474 completion_menu_entries(&menu),
9475 &["let"],
9476 "With no digits in the completion query, no digits should be in the word completions"
9477 );
9478 } else {
9479 panic!("expected completion menu to be open");
9480 }
9481 editor.cancel(&Cancel, window, cx);
9482 });
9483
9484 cx.set_state(indoc! {"3ˇ
9485 0_usize
9486 let
9487 3
9488 33.35f32
9489 "});
9490 cx.update_editor(|editor, window, cx| {
9491 editor.show_completions(&ShowCompletions::default(), window, cx);
9492 });
9493 cx.executor().run_until_parked();
9494 cx.condition(|editor, _| editor.context_menu_visible())
9495 .await;
9496 cx.update_editor(|editor, _, _| {
9497 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9498 {
9499 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
9500 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
9501 } else {
9502 panic!("expected completion menu to be open");
9503 }
9504 });
9505}
9506
9507#[gpui::test]
9508async fn test_multiline_completion(cx: &mut TestAppContext) {
9509 init_test(cx, |_| {});
9510
9511 let fs = FakeFs::new(cx.executor());
9512 fs.insert_tree(
9513 path!("/a"),
9514 json!({
9515 "main.ts": "a",
9516 }),
9517 )
9518 .await;
9519
9520 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9521 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9522 let typescript_language = Arc::new(Language::new(
9523 LanguageConfig {
9524 name: "TypeScript".into(),
9525 matcher: LanguageMatcher {
9526 path_suffixes: vec!["ts".to_string()],
9527 ..LanguageMatcher::default()
9528 },
9529 line_comments: vec!["// ".into()],
9530 ..LanguageConfig::default()
9531 },
9532 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9533 ));
9534 language_registry.add(typescript_language.clone());
9535 let mut fake_servers = language_registry.register_fake_lsp(
9536 "TypeScript",
9537 FakeLspAdapter {
9538 capabilities: lsp::ServerCapabilities {
9539 completion_provider: Some(lsp::CompletionOptions {
9540 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9541 ..lsp::CompletionOptions::default()
9542 }),
9543 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9544 ..lsp::ServerCapabilities::default()
9545 },
9546 // Emulate vtsls label generation
9547 label_for_completion: Some(Box::new(|item, _| {
9548 let text = if let Some(description) = item
9549 .label_details
9550 .as_ref()
9551 .and_then(|label_details| label_details.description.as_ref())
9552 {
9553 format!("{} {}", item.label, description)
9554 } else if let Some(detail) = &item.detail {
9555 format!("{} {}", item.label, detail)
9556 } else {
9557 item.label.clone()
9558 };
9559 let len = text.len();
9560 Some(language::CodeLabel {
9561 text,
9562 runs: Vec::new(),
9563 filter_range: 0..len,
9564 })
9565 })),
9566 ..FakeLspAdapter::default()
9567 },
9568 );
9569 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9570 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9571 let worktree_id = workspace
9572 .update(cx, |workspace, _window, cx| {
9573 workspace.project().update(cx, |project, cx| {
9574 project.worktrees(cx).next().unwrap().read(cx).id()
9575 })
9576 })
9577 .unwrap();
9578 let _buffer = project
9579 .update(cx, |project, cx| {
9580 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9581 })
9582 .await
9583 .unwrap();
9584 let editor = workspace
9585 .update(cx, |workspace, window, cx| {
9586 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9587 })
9588 .unwrap()
9589 .await
9590 .unwrap()
9591 .downcast::<Editor>()
9592 .unwrap();
9593 let fake_server = fake_servers.next().await.unwrap();
9594
9595 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9596 let multiline_label_2 = "a\nb\nc\n";
9597 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9598 let multiline_description = "d\ne\nf\n";
9599 let multiline_detail_2 = "g\nh\ni\n";
9600
9601 let mut completion_handle =
9602 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9603 Ok(Some(lsp::CompletionResponse::Array(vec![
9604 lsp::CompletionItem {
9605 label: multiline_label.to_string(),
9606 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9607 range: lsp::Range {
9608 start: lsp::Position {
9609 line: params.text_document_position.position.line,
9610 character: params.text_document_position.position.character,
9611 },
9612 end: lsp::Position {
9613 line: params.text_document_position.position.line,
9614 character: params.text_document_position.position.character,
9615 },
9616 },
9617 new_text: "new_text_1".to_string(),
9618 })),
9619 ..lsp::CompletionItem::default()
9620 },
9621 lsp::CompletionItem {
9622 label: "single line label 1".to_string(),
9623 detail: Some(multiline_detail.to_string()),
9624 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9625 range: lsp::Range {
9626 start: lsp::Position {
9627 line: params.text_document_position.position.line,
9628 character: params.text_document_position.position.character,
9629 },
9630 end: lsp::Position {
9631 line: params.text_document_position.position.line,
9632 character: params.text_document_position.position.character,
9633 },
9634 },
9635 new_text: "new_text_2".to_string(),
9636 })),
9637 ..lsp::CompletionItem::default()
9638 },
9639 lsp::CompletionItem {
9640 label: "single line label 2".to_string(),
9641 label_details: Some(lsp::CompletionItemLabelDetails {
9642 description: Some(multiline_description.to_string()),
9643 detail: None,
9644 }),
9645 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9646 range: lsp::Range {
9647 start: lsp::Position {
9648 line: params.text_document_position.position.line,
9649 character: params.text_document_position.position.character,
9650 },
9651 end: lsp::Position {
9652 line: params.text_document_position.position.line,
9653 character: params.text_document_position.position.character,
9654 },
9655 },
9656 new_text: "new_text_2".to_string(),
9657 })),
9658 ..lsp::CompletionItem::default()
9659 },
9660 lsp::CompletionItem {
9661 label: multiline_label_2.to_string(),
9662 detail: Some(multiline_detail_2.to_string()),
9663 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9664 range: lsp::Range {
9665 start: lsp::Position {
9666 line: params.text_document_position.position.line,
9667 character: params.text_document_position.position.character,
9668 },
9669 end: lsp::Position {
9670 line: params.text_document_position.position.line,
9671 character: params.text_document_position.position.character,
9672 },
9673 },
9674 new_text: "new_text_3".to_string(),
9675 })),
9676 ..lsp::CompletionItem::default()
9677 },
9678 lsp::CompletionItem {
9679 label: "Label with many spaces and \t but without newlines".to_string(),
9680 detail: Some(
9681 "Details with many spaces and \t but without newlines".to_string(),
9682 ),
9683 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9684 range: lsp::Range {
9685 start: lsp::Position {
9686 line: params.text_document_position.position.line,
9687 character: params.text_document_position.position.character,
9688 },
9689 end: lsp::Position {
9690 line: params.text_document_position.position.line,
9691 character: params.text_document_position.position.character,
9692 },
9693 },
9694 new_text: "new_text_4".to_string(),
9695 })),
9696 ..lsp::CompletionItem::default()
9697 },
9698 ])))
9699 });
9700
9701 editor.update_in(cx, |editor, window, cx| {
9702 cx.focus_self(window);
9703 editor.move_to_end(&MoveToEnd, window, cx);
9704 editor.handle_input(".", window, cx);
9705 });
9706 cx.run_until_parked();
9707 completion_handle.next().await.unwrap();
9708
9709 editor.update(cx, |editor, _| {
9710 assert!(editor.context_menu_visible());
9711 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9712 {
9713 let completion_labels = menu
9714 .completions
9715 .borrow()
9716 .iter()
9717 .map(|c| c.label.text.clone())
9718 .collect::<Vec<_>>();
9719 assert_eq!(
9720 completion_labels,
9721 &[
9722 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9723 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9724 "single line label 2 d e f ",
9725 "a b c g h i ",
9726 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9727 ],
9728 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9729 );
9730
9731 for completion in menu
9732 .completions
9733 .borrow()
9734 .iter() {
9735 assert_eq!(
9736 completion.label.filter_range,
9737 0..completion.label.text.len(),
9738 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9739 );
9740 }
9741
9742 } else {
9743 panic!("expected completion menu to be open");
9744 }
9745 });
9746}
9747
9748#[gpui::test]
9749async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9750 init_test(cx, |_| {});
9751 let mut cx = EditorLspTestContext::new_rust(
9752 lsp::ServerCapabilities {
9753 completion_provider: Some(lsp::CompletionOptions {
9754 trigger_characters: Some(vec![".".to_string()]),
9755 ..Default::default()
9756 }),
9757 ..Default::default()
9758 },
9759 cx,
9760 )
9761 .await;
9762 cx.lsp
9763 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9764 Ok(Some(lsp::CompletionResponse::Array(vec![
9765 lsp::CompletionItem {
9766 label: "first".into(),
9767 ..Default::default()
9768 },
9769 lsp::CompletionItem {
9770 label: "last".into(),
9771 ..Default::default()
9772 },
9773 ])))
9774 });
9775 cx.set_state("variableˇ");
9776 cx.simulate_keystroke(".");
9777 cx.executor().run_until_parked();
9778
9779 cx.update_editor(|editor, _, _| {
9780 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9781 {
9782 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9783 } else {
9784 panic!("expected completion menu to be open");
9785 }
9786 });
9787
9788 cx.update_editor(|editor, window, cx| {
9789 editor.move_page_down(&MovePageDown::default(), window, cx);
9790 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9791 {
9792 assert!(
9793 menu.selected_item == 1,
9794 "expected PageDown to select the last item from the context menu"
9795 );
9796 } else {
9797 panic!("expected completion menu to stay open after PageDown");
9798 }
9799 });
9800
9801 cx.update_editor(|editor, window, cx| {
9802 editor.move_page_up(&MovePageUp::default(), window, cx);
9803 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9804 {
9805 assert!(
9806 menu.selected_item == 0,
9807 "expected PageUp to select the first item from the context menu"
9808 );
9809 } else {
9810 panic!("expected completion menu to stay open after PageUp");
9811 }
9812 });
9813}
9814
9815#[gpui::test]
9816async fn test_completion_sort(cx: &mut TestAppContext) {
9817 init_test(cx, |_| {});
9818 let mut cx = EditorLspTestContext::new_rust(
9819 lsp::ServerCapabilities {
9820 completion_provider: Some(lsp::CompletionOptions {
9821 trigger_characters: Some(vec![".".to_string()]),
9822 ..Default::default()
9823 }),
9824 ..Default::default()
9825 },
9826 cx,
9827 )
9828 .await;
9829 cx.lsp
9830 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9831 Ok(Some(lsp::CompletionResponse::Array(vec![
9832 lsp::CompletionItem {
9833 label: "Range".into(),
9834 sort_text: Some("a".into()),
9835 ..Default::default()
9836 },
9837 lsp::CompletionItem {
9838 label: "r".into(),
9839 sort_text: Some("b".into()),
9840 ..Default::default()
9841 },
9842 lsp::CompletionItem {
9843 label: "ret".into(),
9844 sort_text: Some("c".into()),
9845 ..Default::default()
9846 },
9847 lsp::CompletionItem {
9848 label: "return".into(),
9849 sort_text: Some("d".into()),
9850 ..Default::default()
9851 },
9852 lsp::CompletionItem {
9853 label: "slice".into(),
9854 sort_text: Some("d".into()),
9855 ..Default::default()
9856 },
9857 ])))
9858 });
9859 cx.set_state("rˇ");
9860 cx.executor().run_until_parked();
9861 cx.update_editor(|editor, window, cx| {
9862 editor.show_completions(
9863 &ShowCompletions {
9864 trigger: Some("r".into()),
9865 },
9866 window,
9867 cx,
9868 );
9869 });
9870 cx.executor().run_until_parked();
9871
9872 cx.update_editor(|editor, _, _| {
9873 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9874 {
9875 assert_eq!(
9876 completion_menu_entries(&menu),
9877 &["r", "ret", "Range", "return"]
9878 );
9879 } else {
9880 panic!("expected completion menu to be open");
9881 }
9882 });
9883}
9884
9885#[gpui::test]
9886async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9887 init_test(cx, |_| {});
9888
9889 let mut cx = EditorLspTestContext::new_rust(
9890 lsp::ServerCapabilities {
9891 completion_provider: Some(lsp::CompletionOptions {
9892 trigger_characters: Some(vec![".".to_string()]),
9893 resolve_provider: Some(true),
9894 ..Default::default()
9895 }),
9896 ..Default::default()
9897 },
9898 cx,
9899 )
9900 .await;
9901
9902 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9903 cx.simulate_keystroke(".");
9904 let completion_item = lsp::CompletionItem {
9905 label: "Some".into(),
9906 kind: Some(lsp::CompletionItemKind::SNIPPET),
9907 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9908 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9909 kind: lsp::MarkupKind::Markdown,
9910 value: "```rust\nSome(2)\n```".to_string(),
9911 })),
9912 deprecated: Some(false),
9913 sort_text: Some("Some".to_string()),
9914 filter_text: Some("Some".to_string()),
9915 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9916 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9917 range: lsp::Range {
9918 start: lsp::Position {
9919 line: 0,
9920 character: 22,
9921 },
9922 end: lsp::Position {
9923 line: 0,
9924 character: 22,
9925 },
9926 },
9927 new_text: "Some(2)".to_string(),
9928 })),
9929 additional_text_edits: Some(vec![lsp::TextEdit {
9930 range: lsp::Range {
9931 start: lsp::Position {
9932 line: 0,
9933 character: 20,
9934 },
9935 end: lsp::Position {
9936 line: 0,
9937 character: 22,
9938 },
9939 },
9940 new_text: "".to_string(),
9941 }]),
9942 ..Default::default()
9943 };
9944
9945 let closure_completion_item = completion_item.clone();
9946 let counter = Arc::new(AtomicUsize::new(0));
9947 let counter_clone = counter.clone();
9948 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9949 let task_completion_item = closure_completion_item.clone();
9950 counter_clone.fetch_add(1, atomic::Ordering::Release);
9951 async move {
9952 Ok(Some(lsp::CompletionResponse::Array(vec![
9953 task_completion_item,
9954 ])))
9955 }
9956 });
9957
9958 cx.condition(|editor, _| editor.context_menu_visible())
9959 .await;
9960 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9961 assert!(request.next().await.is_some());
9962 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9963
9964 cx.simulate_keystroke("S");
9965 cx.simulate_keystroke("o");
9966 cx.simulate_keystroke("m");
9967 cx.condition(|editor, _| editor.context_menu_visible())
9968 .await;
9969 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9970 assert!(request.next().await.is_some());
9971 assert!(request.next().await.is_some());
9972 assert!(request.next().await.is_some());
9973 request.close();
9974 assert!(request.next().await.is_none());
9975 assert_eq!(
9976 counter.load(atomic::Ordering::Acquire),
9977 4,
9978 "With the completions menu open, only one LSP request should happen per input"
9979 );
9980}
9981
9982#[gpui::test]
9983async fn test_toggle_comment(cx: &mut TestAppContext) {
9984 init_test(cx, |_| {});
9985 let mut cx = EditorTestContext::new(cx).await;
9986 let language = Arc::new(Language::new(
9987 LanguageConfig {
9988 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9989 ..Default::default()
9990 },
9991 Some(tree_sitter_rust::LANGUAGE.into()),
9992 ));
9993 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9994
9995 // If multiple selections intersect a line, the line is only toggled once.
9996 cx.set_state(indoc! {"
9997 fn a() {
9998 «//b();
9999 ˇ»// «c();
10000 //ˇ» d();
10001 }
10002 "});
10003
10004 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10005
10006 cx.assert_editor_state(indoc! {"
10007 fn a() {
10008 «b();
10009 c();
10010 ˇ» d();
10011 }
10012 "});
10013
10014 // The comment prefix is inserted at the same column for every line in a
10015 // selection.
10016 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10017
10018 cx.assert_editor_state(indoc! {"
10019 fn a() {
10020 // «b();
10021 // c();
10022 ˇ»// d();
10023 }
10024 "});
10025
10026 // If a selection ends at the beginning of a line, that line is not toggled.
10027 cx.set_selections_state(indoc! {"
10028 fn a() {
10029 // b();
10030 «// c();
10031 ˇ» // d();
10032 }
10033 "});
10034
10035 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10036
10037 cx.assert_editor_state(indoc! {"
10038 fn a() {
10039 // b();
10040 «c();
10041 ˇ» // d();
10042 }
10043 "});
10044
10045 // If a selection span a single line and is empty, the line is toggled.
10046 cx.set_state(indoc! {"
10047 fn a() {
10048 a();
10049 b();
10050 ˇ
10051 }
10052 "});
10053
10054 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10055
10056 cx.assert_editor_state(indoc! {"
10057 fn a() {
10058 a();
10059 b();
10060 //•ˇ
10061 }
10062 "});
10063
10064 // If a selection span multiple lines, empty lines are not toggled.
10065 cx.set_state(indoc! {"
10066 fn a() {
10067 «a();
10068
10069 c();ˇ»
10070 }
10071 "});
10072
10073 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10074
10075 cx.assert_editor_state(indoc! {"
10076 fn a() {
10077 // «a();
10078
10079 // c();ˇ»
10080 }
10081 "});
10082
10083 // If a selection includes multiple comment prefixes, all lines are uncommented.
10084 cx.set_state(indoc! {"
10085 fn a() {
10086 «// a();
10087 /// b();
10088 //! c();ˇ»
10089 }
10090 "});
10091
10092 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10093
10094 cx.assert_editor_state(indoc! {"
10095 fn a() {
10096 «a();
10097 b();
10098 c();ˇ»
10099 }
10100 "});
10101}
10102
10103#[gpui::test]
10104async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10105 init_test(cx, |_| {});
10106 let mut cx = EditorTestContext::new(cx).await;
10107 let language = Arc::new(Language::new(
10108 LanguageConfig {
10109 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10110 ..Default::default()
10111 },
10112 Some(tree_sitter_rust::LANGUAGE.into()),
10113 ));
10114 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10115
10116 let toggle_comments = &ToggleComments {
10117 advance_downwards: false,
10118 ignore_indent: true,
10119 };
10120
10121 // If multiple selections intersect a line, the line is only toggled once.
10122 cx.set_state(indoc! {"
10123 fn a() {
10124 // «b();
10125 // c();
10126 // ˇ» d();
10127 }
10128 "});
10129
10130 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10131
10132 cx.assert_editor_state(indoc! {"
10133 fn a() {
10134 «b();
10135 c();
10136 ˇ» d();
10137 }
10138 "});
10139
10140 // The comment prefix is inserted at the beginning of each line
10141 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10142
10143 cx.assert_editor_state(indoc! {"
10144 fn a() {
10145 // «b();
10146 // c();
10147 // ˇ» d();
10148 }
10149 "});
10150
10151 // If a selection ends at the beginning of a line, that line is not toggled.
10152 cx.set_selections_state(indoc! {"
10153 fn a() {
10154 // b();
10155 // «c();
10156 ˇ»// d();
10157 }
10158 "});
10159
10160 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10161
10162 cx.assert_editor_state(indoc! {"
10163 fn a() {
10164 // b();
10165 «c();
10166 ˇ»// d();
10167 }
10168 "});
10169
10170 // If a selection span a single line and is empty, the line is toggled.
10171 cx.set_state(indoc! {"
10172 fn a() {
10173 a();
10174 b();
10175 ˇ
10176 }
10177 "});
10178
10179 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10180
10181 cx.assert_editor_state(indoc! {"
10182 fn a() {
10183 a();
10184 b();
10185 //ˇ
10186 }
10187 "});
10188
10189 // If a selection span multiple lines, empty lines are not toggled.
10190 cx.set_state(indoc! {"
10191 fn a() {
10192 «a();
10193
10194 c();ˇ»
10195 }
10196 "});
10197
10198 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10199
10200 cx.assert_editor_state(indoc! {"
10201 fn a() {
10202 // «a();
10203
10204 // c();ˇ»
10205 }
10206 "});
10207
10208 // If a selection includes multiple comment prefixes, all lines are uncommented.
10209 cx.set_state(indoc! {"
10210 fn a() {
10211 // «a();
10212 /// b();
10213 //! c();ˇ»
10214 }
10215 "});
10216
10217 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10218
10219 cx.assert_editor_state(indoc! {"
10220 fn a() {
10221 «a();
10222 b();
10223 c();ˇ»
10224 }
10225 "});
10226}
10227
10228#[gpui::test]
10229async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10230 init_test(cx, |_| {});
10231
10232 let language = Arc::new(Language::new(
10233 LanguageConfig {
10234 line_comments: vec!["// ".into()],
10235 ..Default::default()
10236 },
10237 Some(tree_sitter_rust::LANGUAGE.into()),
10238 ));
10239
10240 let mut cx = EditorTestContext::new(cx).await;
10241
10242 cx.language_registry().add(language.clone());
10243 cx.update_buffer(|buffer, cx| {
10244 buffer.set_language(Some(language), cx);
10245 });
10246
10247 let toggle_comments = &ToggleComments {
10248 advance_downwards: true,
10249 ignore_indent: false,
10250 };
10251
10252 // Single cursor on one line -> advance
10253 // Cursor moves horizontally 3 characters as well on non-blank line
10254 cx.set_state(indoc!(
10255 "fn a() {
10256 ˇdog();
10257 cat();
10258 }"
10259 ));
10260 cx.update_editor(|editor, window, cx| {
10261 editor.toggle_comments(toggle_comments, window, cx);
10262 });
10263 cx.assert_editor_state(indoc!(
10264 "fn a() {
10265 // dog();
10266 catˇ();
10267 }"
10268 ));
10269
10270 // Single selection on one line -> don't advance
10271 cx.set_state(indoc!(
10272 "fn a() {
10273 «dog()ˇ»;
10274 cat();
10275 }"
10276 ));
10277 cx.update_editor(|editor, window, cx| {
10278 editor.toggle_comments(toggle_comments, window, cx);
10279 });
10280 cx.assert_editor_state(indoc!(
10281 "fn a() {
10282 // «dog()ˇ»;
10283 cat();
10284 }"
10285 ));
10286
10287 // Multiple cursors on one line -> advance
10288 cx.set_state(indoc!(
10289 "fn a() {
10290 ˇdˇog();
10291 cat();
10292 }"
10293 ));
10294 cx.update_editor(|editor, window, cx| {
10295 editor.toggle_comments(toggle_comments, window, cx);
10296 });
10297 cx.assert_editor_state(indoc!(
10298 "fn a() {
10299 // dog();
10300 catˇ(ˇ);
10301 }"
10302 ));
10303
10304 // Multiple cursors on one line, with selection -> don't advance
10305 cx.set_state(indoc!(
10306 "fn a() {
10307 ˇdˇog«()ˇ»;
10308 cat();
10309 }"
10310 ));
10311 cx.update_editor(|editor, window, cx| {
10312 editor.toggle_comments(toggle_comments, window, cx);
10313 });
10314 cx.assert_editor_state(indoc!(
10315 "fn a() {
10316 // ˇdˇog«()ˇ»;
10317 cat();
10318 }"
10319 ));
10320
10321 // Single cursor on one line -> advance
10322 // Cursor moves to column 0 on blank line
10323 cx.set_state(indoc!(
10324 "fn a() {
10325 ˇdog();
10326
10327 cat();
10328 }"
10329 ));
10330 cx.update_editor(|editor, window, cx| {
10331 editor.toggle_comments(toggle_comments, window, cx);
10332 });
10333 cx.assert_editor_state(indoc!(
10334 "fn a() {
10335 // dog();
10336 ˇ
10337 cat();
10338 }"
10339 ));
10340
10341 // Single cursor on one line -> advance
10342 // Cursor starts and ends at column 0
10343 cx.set_state(indoc!(
10344 "fn a() {
10345 ˇ dog();
10346 cat();
10347 }"
10348 ));
10349 cx.update_editor(|editor, window, cx| {
10350 editor.toggle_comments(toggle_comments, window, cx);
10351 });
10352 cx.assert_editor_state(indoc!(
10353 "fn a() {
10354 // dog();
10355 ˇ cat();
10356 }"
10357 ));
10358}
10359
10360#[gpui::test]
10361async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10362 init_test(cx, |_| {});
10363
10364 let mut cx = EditorTestContext::new(cx).await;
10365
10366 let html_language = Arc::new(
10367 Language::new(
10368 LanguageConfig {
10369 name: "HTML".into(),
10370 block_comment: Some(("<!-- ".into(), " -->".into())),
10371 ..Default::default()
10372 },
10373 Some(tree_sitter_html::LANGUAGE.into()),
10374 )
10375 .with_injection_query(
10376 r#"
10377 (script_element
10378 (raw_text) @injection.content
10379 (#set! injection.language "javascript"))
10380 "#,
10381 )
10382 .unwrap(),
10383 );
10384
10385 let javascript_language = Arc::new(Language::new(
10386 LanguageConfig {
10387 name: "JavaScript".into(),
10388 line_comments: vec!["// ".into()],
10389 ..Default::default()
10390 },
10391 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10392 ));
10393
10394 cx.language_registry().add(html_language.clone());
10395 cx.language_registry().add(javascript_language.clone());
10396 cx.update_buffer(|buffer, cx| {
10397 buffer.set_language(Some(html_language), cx);
10398 });
10399
10400 // Toggle comments for empty selections
10401 cx.set_state(
10402 &r#"
10403 <p>A</p>ˇ
10404 <p>B</p>ˇ
10405 <p>C</p>ˇ
10406 "#
10407 .unindent(),
10408 );
10409 cx.update_editor(|editor, window, cx| {
10410 editor.toggle_comments(&ToggleComments::default(), window, cx)
10411 });
10412 cx.assert_editor_state(
10413 &r#"
10414 <!-- <p>A</p>ˇ -->
10415 <!-- <p>B</p>ˇ -->
10416 <!-- <p>C</p>ˇ -->
10417 "#
10418 .unindent(),
10419 );
10420 cx.update_editor(|editor, window, cx| {
10421 editor.toggle_comments(&ToggleComments::default(), window, cx)
10422 });
10423 cx.assert_editor_state(
10424 &r#"
10425 <p>A</p>ˇ
10426 <p>B</p>ˇ
10427 <p>C</p>ˇ
10428 "#
10429 .unindent(),
10430 );
10431
10432 // Toggle comments for mixture of empty and non-empty selections, where
10433 // multiple selections occupy a given line.
10434 cx.set_state(
10435 &r#"
10436 <p>A«</p>
10437 <p>ˇ»B</p>ˇ
10438 <p>C«</p>
10439 <p>ˇ»D</p>ˇ
10440 "#
10441 .unindent(),
10442 );
10443
10444 cx.update_editor(|editor, window, cx| {
10445 editor.toggle_comments(&ToggleComments::default(), window, cx)
10446 });
10447 cx.assert_editor_state(
10448 &r#"
10449 <!-- <p>A«</p>
10450 <p>ˇ»B</p>ˇ -->
10451 <!-- <p>C«</p>
10452 <p>ˇ»D</p>ˇ -->
10453 "#
10454 .unindent(),
10455 );
10456 cx.update_editor(|editor, window, cx| {
10457 editor.toggle_comments(&ToggleComments::default(), window, cx)
10458 });
10459 cx.assert_editor_state(
10460 &r#"
10461 <p>A«</p>
10462 <p>ˇ»B</p>ˇ
10463 <p>C«</p>
10464 <p>ˇ»D</p>ˇ
10465 "#
10466 .unindent(),
10467 );
10468
10469 // Toggle comments when different languages are active for different
10470 // selections.
10471 cx.set_state(
10472 &r#"
10473 ˇ<script>
10474 ˇvar x = new Y();
10475 ˇ</script>
10476 "#
10477 .unindent(),
10478 );
10479 cx.executor().run_until_parked();
10480 cx.update_editor(|editor, window, cx| {
10481 editor.toggle_comments(&ToggleComments::default(), window, cx)
10482 });
10483 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10484 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10485 cx.assert_editor_state(
10486 &r#"
10487 <!-- ˇ<script> -->
10488 // ˇvar x = new Y();
10489 <!-- ˇ</script> -->
10490 "#
10491 .unindent(),
10492 );
10493}
10494
10495#[gpui::test]
10496fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10497 init_test(cx, |_| {});
10498
10499 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10500 let multibuffer = cx.new(|cx| {
10501 let mut multibuffer = MultiBuffer::new(ReadWrite);
10502 multibuffer.push_excerpts(
10503 buffer.clone(),
10504 [
10505 ExcerptRange {
10506 context: Point::new(0, 0)..Point::new(0, 4),
10507 primary: None,
10508 },
10509 ExcerptRange {
10510 context: Point::new(1, 0)..Point::new(1, 4),
10511 primary: None,
10512 },
10513 ],
10514 cx,
10515 );
10516 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10517 multibuffer
10518 });
10519
10520 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10521 editor.update_in(cx, |editor, window, cx| {
10522 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10523 editor.change_selections(None, window, cx, |s| {
10524 s.select_ranges([
10525 Point::new(0, 0)..Point::new(0, 0),
10526 Point::new(1, 0)..Point::new(1, 0),
10527 ])
10528 });
10529
10530 editor.handle_input("X", window, cx);
10531 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10532 assert_eq!(
10533 editor.selections.ranges(cx),
10534 [
10535 Point::new(0, 1)..Point::new(0, 1),
10536 Point::new(1, 1)..Point::new(1, 1),
10537 ]
10538 );
10539
10540 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10541 editor.change_selections(None, window, cx, |s| {
10542 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10543 });
10544 editor.backspace(&Default::default(), window, cx);
10545 assert_eq!(editor.text(cx), "Xa\nbbb");
10546 assert_eq!(
10547 editor.selections.ranges(cx),
10548 [Point::new(1, 0)..Point::new(1, 0)]
10549 );
10550
10551 editor.change_selections(None, window, cx, |s| {
10552 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10553 });
10554 editor.backspace(&Default::default(), window, cx);
10555 assert_eq!(editor.text(cx), "X\nbb");
10556 assert_eq!(
10557 editor.selections.ranges(cx),
10558 [Point::new(0, 1)..Point::new(0, 1)]
10559 );
10560 });
10561}
10562
10563#[gpui::test]
10564fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10565 init_test(cx, |_| {});
10566
10567 let markers = vec![('[', ']').into(), ('(', ')').into()];
10568 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10569 indoc! {"
10570 [aaaa
10571 (bbbb]
10572 cccc)",
10573 },
10574 markers.clone(),
10575 );
10576 let excerpt_ranges = markers.into_iter().map(|marker| {
10577 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10578 ExcerptRange {
10579 context,
10580 primary: None,
10581 }
10582 });
10583 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10584 let multibuffer = cx.new(|cx| {
10585 let mut multibuffer = MultiBuffer::new(ReadWrite);
10586 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10587 multibuffer
10588 });
10589
10590 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10591 editor.update_in(cx, |editor, window, cx| {
10592 let (expected_text, selection_ranges) = marked_text_ranges(
10593 indoc! {"
10594 aaaa
10595 bˇbbb
10596 bˇbbˇb
10597 cccc"
10598 },
10599 true,
10600 );
10601 assert_eq!(editor.text(cx), expected_text);
10602 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10603
10604 editor.handle_input("X", window, cx);
10605
10606 let (expected_text, expected_selections) = marked_text_ranges(
10607 indoc! {"
10608 aaaa
10609 bXˇbbXb
10610 bXˇbbXˇb
10611 cccc"
10612 },
10613 false,
10614 );
10615 assert_eq!(editor.text(cx), expected_text);
10616 assert_eq!(editor.selections.ranges(cx), expected_selections);
10617
10618 editor.newline(&Newline, window, cx);
10619 let (expected_text, expected_selections) = marked_text_ranges(
10620 indoc! {"
10621 aaaa
10622 bX
10623 ˇbbX
10624 b
10625 bX
10626 ˇbbX
10627 ˇb
10628 cccc"
10629 },
10630 false,
10631 );
10632 assert_eq!(editor.text(cx), expected_text);
10633 assert_eq!(editor.selections.ranges(cx), expected_selections);
10634 });
10635}
10636
10637#[gpui::test]
10638fn test_refresh_selections(cx: &mut TestAppContext) {
10639 init_test(cx, |_| {});
10640
10641 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10642 let mut excerpt1_id = None;
10643 let multibuffer = cx.new(|cx| {
10644 let mut multibuffer = MultiBuffer::new(ReadWrite);
10645 excerpt1_id = multibuffer
10646 .push_excerpts(
10647 buffer.clone(),
10648 [
10649 ExcerptRange {
10650 context: Point::new(0, 0)..Point::new(1, 4),
10651 primary: None,
10652 },
10653 ExcerptRange {
10654 context: Point::new(1, 0)..Point::new(2, 4),
10655 primary: None,
10656 },
10657 ],
10658 cx,
10659 )
10660 .into_iter()
10661 .next();
10662 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10663 multibuffer
10664 });
10665
10666 let editor = cx.add_window(|window, cx| {
10667 let mut editor = build_editor(multibuffer.clone(), window, cx);
10668 let snapshot = editor.snapshot(window, cx);
10669 editor.change_selections(None, window, cx, |s| {
10670 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10671 });
10672 editor.begin_selection(
10673 Point::new(2, 1).to_display_point(&snapshot),
10674 true,
10675 1,
10676 window,
10677 cx,
10678 );
10679 assert_eq!(
10680 editor.selections.ranges(cx),
10681 [
10682 Point::new(1, 3)..Point::new(1, 3),
10683 Point::new(2, 1)..Point::new(2, 1),
10684 ]
10685 );
10686 editor
10687 });
10688
10689 // Refreshing selections is a no-op when excerpts haven't changed.
10690 _ = editor.update(cx, |editor, window, cx| {
10691 editor.change_selections(None, window, cx, |s| s.refresh());
10692 assert_eq!(
10693 editor.selections.ranges(cx),
10694 [
10695 Point::new(1, 3)..Point::new(1, 3),
10696 Point::new(2, 1)..Point::new(2, 1),
10697 ]
10698 );
10699 });
10700
10701 multibuffer.update(cx, |multibuffer, cx| {
10702 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10703 });
10704 _ = editor.update(cx, |editor, window, cx| {
10705 // Removing an excerpt causes the first selection to become degenerate.
10706 assert_eq!(
10707 editor.selections.ranges(cx),
10708 [
10709 Point::new(0, 0)..Point::new(0, 0),
10710 Point::new(0, 1)..Point::new(0, 1)
10711 ]
10712 );
10713
10714 // Refreshing selections will relocate the first selection to the original buffer
10715 // location.
10716 editor.change_selections(None, window, cx, |s| s.refresh());
10717 assert_eq!(
10718 editor.selections.ranges(cx),
10719 [
10720 Point::new(0, 1)..Point::new(0, 1),
10721 Point::new(0, 3)..Point::new(0, 3)
10722 ]
10723 );
10724 assert!(editor.selections.pending_anchor().is_some());
10725 });
10726}
10727
10728#[gpui::test]
10729fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10730 init_test(cx, |_| {});
10731
10732 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10733 let mut excerpt1_id = None;
10734 let multibuffer = cx.new(|cx| {
10735 let mut multibuffer = MultiBuffer::new(ReadWrite);
10736 excerpt1_id = multibuffer
10737 .push_excerpts(
10738 buffer.clone(),
10739 [
10740 ExcerptRange {
10741 context: Point::new(0, 0)..Point::new(1, 4),
10742 primary: None,
10743 },
10744 ExcerptRange {
10745 context: Point::new(1, 0)..Point::new(2, 4),
10746 primary: None,
10747 },
10748 ],
10749 cx,
10750 )
10751 .into_iter()
10752 .next();
10753 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10754 multibuffer
10755 });
10756
10757 let editor = cx.add_window(|window, cx| {
10758 let mut editor = build_editor(multibuffer.clone(), window, cx);
10759 let snapshot = editor.snapshot(window, cx);
10760 editor.begin_selection(
10761 Point::new(1, 3).to_display_point(&snapshot),
10762 false,
10763 1,
10764 window,
10765 cx,
10766 );
10767 assert_eq!(
10768 editor.selections.ranges(cx),
10769 [Point::new(1, 3)..Point::new(1, 3)]
10770 );
10771 editor
10772 });
10773
10774 multibuffer.update(cx, |multibuffer, cx| {
10775 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10776 });
10777 _ = editor.update(cx, |editor, window, cx| {
10778 assert_eq!(
10779 editor.selections.ranges(cx),
10780 [Point::new(0, 0)..Point::new(0, 0)]
10781 );
10782
10783 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10784 editor.change_selections(None, window, cx, |s| s.refresh());
10785 assert_eq!(
10786 editor.selections.ranges(cx),
10787 [Point::new(0, 3)..Point::new(0, 3)]
10788 );
10789 assert!(editor.selections.pending_anchor().is_some());
10790 });
10791}
10792
10793#[gpui::test]
10794async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10795 init_test(cx, |_| {});
10796
10797 let language = Arc::new(
10798 Language::new(
10799 LanguageConfig {
10800 brackets: BracketPairConfig {
10801 pairs: vec![
10802 BracketPair {
10803 start: "{".to_string(),
10804 end: "}".to_string(),
10805 close: true,
10806 surround: true,
10807 newline: true,
10808 },
10809 BracketPair {
10810 start: "/* ".to_string(),
10811 end: " */".to_string(),
10812 close: true,
10813 surround: true,
10814 newline: true,
10815 },
10816 ],
10817 ..Default::default()
10818 },
10819 ..Default::default()
10820 },
10821 Some(tree_sitter_rust::LANGUAGE.into()),
10822 )
10823 .with_indents_query("")
10824 .unwrap(),
10825 );
10826
10827 let text = concat!(
10828 "{ }\n", //
10829 " x\n", //
10830 " /* */\n", //
10831 "x\n", //
10832 "{{} }\n", //
10833 );
10834
10835 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10836 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10837 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10838 editor
10839 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10840 .await;
10841
10842 editor.update_in(cx, |editor, window, cx| {
10843 editor.change_selections(None, window, cx, |s| {
10844 s.select_display_ranges([
10845 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10846 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10847 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10848 ])
10849 });
10850 editor.newline(&Newline, window, cx);
10851
10852 assert_eq!(
10853 editor.buffer().read(cx).read(cx).text(),
10854 concat!(
10855 "{ \n", // Suppress rustfmt
10856 "\n", //
10857 "}\n", //
10858 " x\n", //
10859 " /* \n", //
10860 " \n", //
10861 " */\n", //
10862 "x\n", //
10863 "{{} \n", //
10864 "}\n", //
10865 )
10866 );
10867 });
10868}
10869
10870#[gpui::test]
10871fn test_highlighted_ranges(cx: &mut TestAppContext) {
10872 init_test(cx, |_| {});
10873
10874 let editor = cx.add_window(|window, cx| {
10875 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10876 build_editor(buffer.clone(), window, cx)
10877 });
10878
10879 _ = editor.update(cx, |editor, window, cx| {
10880 struct Type1;
10881 struct Type2;
10882
10883 let buffer = editor.buffer.read(cx).snapshot(cx);
10884
10885 let anchor_range =
10886 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10887
10888 editor.highlight_background::<Type1>(
10889 &[
10890 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10891 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10892 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10893 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10894 ],
10895 |_| Hsla::red(),
10896 cx,
10897 );
10898 editor.highlight_background::<Type2>(
10899 &[
10900 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10901 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10902 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10903 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10904 ],
10905 |_| Hsla::green(),
10906 cx,
10907 );
10908
10909 let snapshot = editor.snapshot(window, cx);
10910 let mut highlighted_ranges = editor.background_highlights_in_range(
10911 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10912 &snapshot,
10913 cx.theme().colors(),
10914 );
10915 // Enforce a consistent ordering based on color without relying on the ordering of the
10916 // highlight's `TypeId` which is non-executor.
10917 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10918 assert_eq!(
10919 highlighted_ranges,
10920 &[
10921 (
10922 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10923 Hsla::red(),
10924 ),
10925 (
10926 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10927 Hsla::red(),
10928 ),
10929 (
10930 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10931 Hsla::green(),
10932 ),
10933 (
10934 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10935 Hsla::green(),
10936 ),
10937 ]
10938 );
10939 assert_eq!(
10940 editor.background_highlights_in_range(
10941 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10942 &snapshot,
10943 cx.theme().colors(),
10944 ),
10945 &[(
10946 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10947 Hsla::red(),
10948 )]
10949 );
10950 });
10951}
10952
10953#[gpui::test]
10954async fn test_following(cx: &mut TestAppContext) {
10955 init_test(cx, |_| {});
10956
10957 let fs = FakeFs::new(cx.executor());
10958 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10959
10960 let buffer = project.update(cx, |project, cx| {
10961 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10962 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10963 });
10964 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10965 let follower = cx.update(|cx| {
10966 cx.open_window(
10967 WindowOptions {
10968 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10969 gpui::Point::new(px(0.), px(0.)),
10970 gpui::Point::new(px(10.), px(80.)),
10971 ))),
10972 ..Default::default()
10973 },
10974 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10975 )
10976 .unwrap()
10977 });
10978
10979 let is_still_following = Rc::new(RefCell::new(true));
10980 let follower_edit_event_count = Rc::new(RefCell::new(0));
10981 let pending_update = Rc::new(RefCell::new(None));
10982 let leader_entity = leader.root(cx).unwrap();
10983 let follower_entity = follower.root(cx).unwrap();
10984 _ = follower.update(cx, {
10985 let update = pending_update.clone();
10986 let is_still_following = is_still_following.clone();
10987 let follower_edit_event_count = follower_edit_event_count.clone();
10988 |_, window, cx| {
10989 cx.subscribe_in(
10990 &leader_entity,
10991 window,
10992 move |_, leader, event, window, cx| {
10993 leader.read(cx).add_event_to_update_proto(
10994 event,
10995 &mut update.borrow_mut(),
10996 window,
10997 cx,
10998 );
10999 },
11000 )
11001 .detach();
11002
11003 cx.subscribe_in(
11004 &follower_entity,
11005 window,
11006 move |_, _, event: &EditorEvent, _window, _cx| {
11007 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11008 *is_still_following.borrow_mut() = false;
11009 }
11010
11011 if let EditorEvent::BufferEdited = event {
11012 *follower_edit_event_count.borrow_mut() += 1;
11013 }
11014 },
11015 )
11016 .detach();
11017 }
11018 });
11019
11020 // Update the selections only
11021 _ = leader.update(cx, |leader, window, cx| {
11022 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11023 });
11024 follower
11025 .update(cx, |follower, window, cx| {
11026 follower.apply_update_proto(
11027 &project,
11028 pending_update.borrow_mut().take().unwrap(),
11029 window,
11030 cx,
11031 )
11032 })
11033 .unwrap()
11034 .await
11035 .unwrap();
11036 _ = follower.update(cx, |follower, _, cx| {
11037 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11038 });
11039 assert!(*is_still_following.borrow());
11040 assert_eq!(*follower_edit_event_count.borrow(), 0);
11041
11042 // Update the scroll position only
11043 _ = leader.update(cx, |leader, window, cx| {
11044 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11045 });
11046 follower
11047 .update(cx, |follower, window, cx| {
11048 follower.apply_update_proto(
11049 &project,
11050 pending_update.borrow_mut().take().unwrap(),
11051 window,
11052 cx,
11053 )
11054 })
11055 .unwrap()
11056 .await
11057 .unwrap();
11058 assert_eq!(
11059 follower
11060 .update(cx, |follower, _, cx| follower.scroll_position(cx))
11061 .unwrap(),
11062 gpui::Point::new(1.5, 3.5)
11063 );
11064 assert!(*is_still_following.borrow());
11065 assert_eq!(*follower_edit_event_count.borrow(), 0);
11066
11067 // Update the selections and scroll position. The follower's scroll position is updated
11068 // via autoscroll, not via the leader's exact scroll position.
11069 _ = leader.update(cx, |leader, window, cx| {
11070 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11071 leader.request_autoscroll(Autoscroll::newest(), cx);
11072 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11073 });
11074 follower
11075 .update(cx, |follower, window, cx| {
11076 follower.apply_update_proto(
11077 &project,
11078 pending_update.borrow_mut().take().unwrap(),
11079 window,
11080 cx,
11081 )
11082 })
11083 .unwrap()
11084 .await
11085 .unwrap();
11086 _ = follower.update(cx, |follower, _, cx| {
11087 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11088 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11089 });
11090 assert!(*is_still_following.borrow());
11091
11092 // Creating a pending selection that precedes another selection
11093 _ = leader.update(cx, |leader, window, cx| {
11094 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11095 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11096 });
11097 follower
11098 .update(cx, |follower, window, cx| {
11099 follower.apply_update_proto(
11100 &project,
11101 pending_update.borrow_mut().take().unwrap(),
11102 window,
11103 cx,
11104 )
11105 })
11106 .unwrap()
11107 .await
11108 .unwrap();
11109 _ = follower.update(cx, |follower, _, cx| {
11110 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11111 });
11112 assert!(*is_still_following.borrow());
11113
11114 // Extend the pending selection so that it surrounds another selection
11115 _ = leader.update(cx, |leader, window, cx| {
11116 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11117 });
11118 follower
11119 .update(cx, |follower, window, cx| {
11120 follower.apply_update_proto(
11121 &project,
11122 pending_update.borrow_mut().take().unwrap(),
11123 window,
11124 cx,
11125 )
11126 })
11127 .unwrap()
11128 .await
11129 .unwrap();
11130 _ = follower.update(cx, |follower, _, cx| {
11131 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11132 });
11133
11134 // Scrolling locally breaks the follow
11135 _ = follower.update(cx, |follower, window, cx| {
11136 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11137 follower.set_scroll_anchor(
11138 ScrollAnchor {
11139 anchor: top_anchor,
11140 offset: gpui::Point::new(0.0, 0.5),
11141 },
11142 window,
11143 cx,
11144 );
11145 });
11146 assert!(!(*is_still_following.borrow()));
11147}
11148
11149#[gpui::test]
11150async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11151 init_test(cx, |_| {});
11152
11153 let fs = FakeFs::new(cx.executor());
11154 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11155 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11156 let pane = workspace
11157 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11158 .unwrap();
11159
11160 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11161
11162 let leader = pane.update_in(cx, |_, window, cx| {
11163 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11164 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11165 });
11166
11167 // Start following the editor when it has no excerpts.
11168 let mut state_message =
11169 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11170 let workspace_entity = workspace.root(cx).unwrap();
11171 let follower_1 = cx
11172 .update_window(*workspace.deref(), |_, window, cx| {
11173 Editor::from_state_proto(
11174 workspace_entity,
11175 ViewId {
11176 creator: Default::default(),
11177 id: 0,
11178 },
11179 &mut state_message,
11180 window,
11181 cx,
11182 )
11183 })
11184 .unwrap()
11185 .unwrap()
11186 .await
11187 .unwrap();
11188
11189 let update_message = Rc::new(RefCell::new(None));
11190 follower_1.update_in(cx, {
11191 let update = update_message.clone();
11192 |_, window, cx| {
11193 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11194 leader.read(cx).add_event_to_update_proto(
11195 event,
11196 &mut update.borrow_mut(),
11197 window,
11198 cx,
11199 );
11200 })
11201 .detach();
11202 }
11203 });
11204
11205 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11206 (
11207 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11208 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11209 )
11210 });
11211
11212 // Insert some excerpts.
11213 leader.update(cx, |leader, cx| {
11214 leader.buffer.update(cx, |multibuffer, cx| {
11215 let excerpt_ids = multibuffer.push_excerpts(
11216 buffer_1.clone(),
11217 [
11218 ExcerptRange {
11219 context: 1..6,
11220 primary: None,
11221 },
11222 ExcerptRange {
11223 context: 12..15,
11224 primary: None,
11225 },
11226 ExcerptRange {
11227 context: 0..3,
11228 primary: None,
11229 },
11230 ],
11231 cx,
11232 );
11233 multibuffer.insert_excerpts_after(
11234 excerpt_ids[0],
11235 buffer_2.clone(),
11236 [
11237 ExcerptRange {
11238 context: 8..12,
11239 primary: None,
11240 },
11241 ExcerptRange {
11242 context: 0..6,
11243 primary: None,
11244 },
11245 ],
11246 cx,
11247 );
11248 });
11249 });
11250
11251 // Apply the update of adding the excerpts.
11252 follower_1
11253 .update_in(cx, |follower, window, cx| {
11254 follower.apply_update_proto(
11255 &project,
11256 update_message.borrow().clone().unwrap(),
11257 window,
11258 cx,
11259 )
11260 })
11261 .await
11262 .unwrap();
11263 assert_eq!(
11264 follower_1.update(cx, |editor, cx| editor.text(cx)),
11265 leader.update(cx, |editor, cx| editor.text(cx))
11266 );
11267 update_message.borrow_mut().take();
11268
11269 // Start following separately after it already has excerpts.
11270 let mut state_message =
11271 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11272 let workspace_entity = workspace.root(cx).unwrap();
11273 let follower_2 = cx
11274 .update_window(*workspace.deref(), |_, window, cx| {
11275 Editor::from_state_proto(
11276 workspace_entity,
11277 ViewId {
11278 creator: Default::default(),
11279 id: 0,
11280 },
11281 &mut state_message,
11282 window,
11283 cx,
11284 )
11285 })
11286 .unwrap()
11287 .unwrap()
11288 .await
11289 .unwrap();
11290 assert_eq!(
11291 follower_2.update(cx, |editor, cx| editor.text(cx)),
11292 leader.update(cx, |editor, cx| editor.text(cx))
11293 );
11294
11295 // Remove some excerpts.
11296 leader.update(cx, |leader, cx| {
11297 leader.buffer.update(cx, |multibuffer, cx| {
11298 let excerpt_ids = multibuffer.excerpt_ids();
11299 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11300 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11301 });
11302 });
11303
11304 // Apply the update of removing the excerpts.
11305 follower_1
11306 .update_in(cx, |follower, window, cx| {
11307 follower.apply_update_proto(
11308 &project,
11309 update_message.borrow().clone().unwrap(),
11310 window,
11311 cx,
11312 )
11313 })
11314 .await
11315 .unwrap();
11316 follower_2
11317 .update_in(cx, |follower, window, cx| {
11318 follower.apply_update_proto(
11319 &project,
11320 update_message.borrow().clone().unwrap(),
11321 window,
11322 cx,
11323 )
11324 })
11325 .await
11326 .unwrap();
11327 update_message.borrow_mut().take();
11328 assert_eq!(
11329 follower_1.update(cx, |editor, cx| editor.text(cx)),
11330 leader.update(cx, |editor, cx| editor.text(cx))
11331 );
11332}
11333
11334#[gpui::test]
11335async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11336 init_test(cx, |_| {});
11337
11338 let mut cx = EditorTestContext::new(cx).await;
11339 let lsp_store =
11340 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11341
11342 cx.set_state(indoc! {"
11343 ˇfn func(abc def: i32) -> u32 {
11344 }
11345 "});
11346
11347 cx.update(|_, cx| {
11348 lsp_store.update(cx, |lsp_store, cx| {
11349 lsp_store
11350 .update_diagnostics(
11351 LanguageServerId(0),
11352 lsp::PublishDiagnosticsParams {
11353 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11354 version: None,
11355 diagnostics: vec![
11356 lsp::Diagnostic {
11357 range: lsp::Range::new(
11358 lsp::Position::new(0, 11),
11359 lsp::Position::new(0, 12),
11360 ),
11361 severity: Some(lsp::DiagnosticSeverity::ERROR),
11362 ..Default::default()
11363 },
11364 lsp::Diagnostic {
11365 range: lsp::Range::new(
11366 lsp::Position::new(0, 12),
11367 lsp::Position::new(0, 15),
11368 ),
11369 severity: Some(lsp::DiagnosticSeverity::ERROR),
11370 ..Default::default()
11371 },
11372 lsp::Diagnostic {
11373 range: lsp::Range::new(
11374 lsp::Position::new(0, 25),
11375 lsp::Position::new(0, 28),
11376 ),
11377 severity: Some(lsp::DiagnosticSeverity::ERROR),
11378 ..Default::default()
11379 },
11380 ],
11381 },
11382 &[],
11383 cx,
11384 )
11385 .unwrap()
11386 });
11387 });
11388
11389 executor.run_until_parked();
11390
11391 cx.update_editor(|editor, window, cx| {
11392 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11393 });
11394
11395 cx.assert_editor_state(indoc! {"
11396 fn func(abc def: i32) -> ˇu32 {
11397 }
11398 "});
11399
11400 cx.update_editor(|editor, window, cx| {
11401 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11402 });
11403
11404 cx.assert_editor_state(indoc! {"
11405 fn func(abc ˇdef: i32) -> u32 {
11406 }
11407 "});
11408
11409 cx.update_editor(|editor, window, cx| {
11410 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11411 });
11412
11413 cx.assert_editor_state(indoc! {"
11414 fn func(abcˇ def: i32) -> u32 {
11415 }
11416 "});
11417
11418 cx.update_editor(|editor, window, cx| {
11419 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11420 });
11421
11422 cx.assert_editor_state(indoc! {"
11423 fn func(abc def: i32) -> ˇu32 {
11424 }
11425 "});
11426}
11427
11428#[gpui::test]
11429async fn cycle_through_same_place_diagnostics(
11430 executor: BackgroundExecutor,
11431 cx: &mut TestAppContext,
11432) {
11433 init_test(cx, |_| {});
11434
11435 let mut cx = EditorTestContext::new(cx).await;
11436 let lsp_store =
11437 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11438
11439 cx.set_state(indoc! {"
11440 ˇfn func(abc def: i32) -> u32 {
11441 }
11442 "});
11443
11444 cx.update(|_, cx| {
11445 lsp_store.update(cx, |lsp_store, cx| {
11446 lsp_store
11447 .update_diagnostics(
11448 LanguageServerId(0),
11449 lsp::PublishDiagnosticsParams {
11450 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11451 version: None,
11452 diagnostics: vec![
11453 lsp::Diagnostic {
11454 range: lsp::Range::new(
11455 lsp::Position::new(0, 11),
11456 lsp::Position::new(0, 12),
11457 ),
11458 severity: Some(lsp::DiagnosticSeverity::ERROR),
11459 ..Default::default()
11460 },
11461 lsp::Diagnostic {
11462 range: lsp::Range::new(
11463 lsp::Position::new(0, 12),
11464 lsp::Position::new(0, 15),
11465 ),
11466 severity: Some(lsp::DiagnosticSeverity::ERROR),
11467 ..Default::default()
11468 },
11469 lsp::Diagnostic {
11470 range: lsp::Range::new(
11471 lsp::Position::new(0, 12),
11472 lsp::Position::new(0, 15),
11473 ),
11474 severity: Some(lsp::DiagnosticSeverity::ERROR),
11475 ..Default::default()
11476 },
11477 lsp::Diagnostic {
11478 range: lsp::Range::new(
11479 lsp::Position::new(0, 25),
11480 lsp::Position::new(0, 28),
11481 ),
11482 severity: Some(lsp::DiagnosticSeverity::ERROR),
11483 ..Default::default()
11484 },
11485 ],
11486 },
11487 &[],
11488 cx,
11489 )
11490 .unwrap()
11491 });
11492 });
11493 executor.run_until_parked();
11494
11495 //// Backward
11496
11497 // Fourth diagnostic
11498 cx.update_editor(|editor, window, cx| {
11499 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11500 });
11501 cx.assert_editor_state(indoc! {"
11502 fn func(abc def: i32) -> ˇu32 {
11503 }
11504 "});
11505
11506 // Third diagnostic
11507 cx.update_editor(|editor, window, cx| {
11508 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11509 });
11510 cx.assert_editor_state(indoc! {"
11511 fn func(abc ˇdef: i32) -> u32 {
11512 }
11513 "});
11514
11515 // Second diagnostic, same place
11516 cx.update_editor(|editor, window, cx| {
11517 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11518 });
11519 cx.assert_editor_state(indoc! {"
11520 fn func(abc ˇdef: i32) -> u32 {
11521 }
11522 "});
11523
11524 // First diagnostic
11525 cx.update_editor(|editor, window, cx| {
11526 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11527 });
11528 cx.assert_editor_state(indoc! {"
11529 fn func(abcˇ def: i32) -> u32 {
11530 }
11531 "});
11532
11533 // Wrapped over, fourth diagnostic
11534 cx.update_editor(|editor, window, cx| {
11535 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11536 });
11537 cx.assert_editor_state(indoc! {"
11538 fn func(abc def: i32) -> ˇu32 {
11539 }
11540 "});
11541
11542 cx.update_editor(|editor, window, cx| {
11543 editor.move_to_beginning(&MoveToBeginning, window, cx);
11544 });
11545 cx.assert_editor_state(indoc! {"
11546 ˇfn func(abc def: i32) -> u32 {
11547 }
11548 "});
11549
11550 //// Forward
11551
11552 // First diagnostic
11553 cx.update_editor(|editor, window, cx| {
11554 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11555 });
11556 cx.assert_editor_state(indoc! {"
11557 fn func(abcˇ def: i32) -> u32 {
11558 }
11559 "});
11560
11561 // Second diagnostic
11562 cx.update_editor(|editor, window, cx| {
11563 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11564 });
11565 cx.assert_editor_state(indoc! {"
11566 fn func(abc ˇdef: i32) -> u32 {
11567 }
11568 "});
11569
11570 // Third diagnostic, same place
11571 cx.update_editor(|editor, window, cx| {
11572 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11573 });
11574 cx.assert_editor_state(indoc! {"
11575 fn func(abc ˇdef: i32) -> u32 {
11576 }
11577 "});
11578
11579 // Fourth diagnostic
11580 cx.update_editor(|editor, window, cx| {
11581 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11582 });
11583 cx.assert_editor_state(indoc! {"
11584 fn func(abc def: i32) -> ˇu32 {
11585 }
11586 "});
11587
11588 // Wrapped around, first diagnostic
11589 cx.update_editor(|editor, window, cx| {
11590 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11591 });
11592 cx.assert_editor_state(indoc! {"
11593 fn func(abcˇ def: i32) -> u32 {
11594 }
11595 "});
11596}
11597
11598#[gpui::test]
11599async fn active_diagnostics_dismiss_after_invalidation(
11600 executor: BackgroundExecutor,
11601 cx: &mut TestAppContext,
11602) {
11603 init_test(cx, |_| {});
11604
11605 let mut cx = EditorTestContext::new(cx).await;
11606 let lsp_store =
11607 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11608
11609 cx.set_state(indoc! {"
11610 ˇfn func(abc def: i32) -> u32 {
11611 }
11612 "});
11613
11614 let message = "Something's wrong!";
11615 cx.update(|_, cx| {
11616 lsp_store.update(cx, |lsp_store, cx| {
11617 lsp_store
11618 .update_diagnostics(
11619 LanguageServerId(0),
11620 lsp::PublishDiagnosticsParams {
11621 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11622 version: None,
11623 diagnostics: vec![lsp::Diagnostic {
11624 range: lsp::Range::new(
11625 lsp::Position::new(0, 11),
11626 lsp::Position::new(0, 12),
11627 ),
11628 severity: Some(lsp::DiagnosticSeverity::ERROR),
11629 message: message.to_string(),
11630 ..Default::default()
11631 }],
11632 },
11633 &[],
11634 cx,
11635 )
11636 .unwrap()
11637 });
11638 });
11639 executor.run_until_parked();
11640
11641 cx.update_editor(|editor, window, cx| {
11642 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11643 assert_eq!(
11644 editor
11645 .active_diagnostics
11646 .as_ref()
11647 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11648 Some(message),
11649 "Should have a diagnostics group activated"
11650 );
11651 });
11652 cx.assert_editor_state(indoc! {"
11653 fn func(abcˇ def: i32) -> u32 {
11654 }
11655 "});
11656
11657 cx.update(|_, cx| {
11658 lsp_store.update(cx, |lsp_store, cx| {
11659 lsp_store
11660 .update_diagnostics(
11661 LanguageServerId(0),
11662 lsp::PublishDiagnosticsParams {
11663 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11664 version: None,
11665 diagnostics: Vec::new(),
11666 },
11667 &[],
11668 cx,
11669 )
11670 .unwrap()
11671 });
11672 });
11673 executor.run_until_parked();
11674 cx.update_editor(|editor, _, _| {
11675 assert_eq!(
11676 editor.active_diagnostics, None,
11677 "After no diagnostics set to the editor, no diagnostics should be active"
11678 );
11679 });
11680 cx.assert_editor_state(indoc! {"
11681 fn func(abcˇ def: i32) -> u32 {
11682 }
11683 "});
11684
11685 cx.update_editor(|editor, window, cx| {
11686 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11687 assert_eq!(
11688 editor.active_diagnostics, None,
11689 "Should be no diagnostics to go to and activate"
11690 );
11691 });
11692 cx.assert_editor_state(indoc! {"
11693 fn func(abcˇ def: i32) -> u32 {
11694 }
11695 "});
11696}
11697
11698#[gpui::test]
11699async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11700 init_test(cx, |_| {});
11701
11702 let mut cx = EditorTestContext::new(cx).await;
11703
11704 cx.set_state(indoc! {"
11705 fn func(abˇc def: i32) -> u32 {
11706 }
11707 "});
11708 let lsp_store =
11709 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11710
11711 cx.update(|_, cx| {
11712 lsp_store.update(cx, |lsp_store, cx| {
11713 lsp_store.update_diagnostics(
11714 LanguageServerId(0),
11715 lsp::PublishDiagnosticsParams {
11716 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11717 version: None,
11718 diagnostics: vec![lsp::Diagnostic {
11719 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11720 severity: Some(lsp::DiagnosticSeverity::ERROR),
11721 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11722 ..Default::default()
11723 }],
11724 },
11725 &[],
11726 cx,
11727 )
11728 })
11729 }).unwrap();
11730 cx.run_until_parked();
11731 cx.update_editor(|editor, window, cx| {
11732 hover_popover::hover(editor, &Default::default(), window, cx)
11733 });
11734 cx.run_until_parked();
11735 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11736}
11737
11738#[gpui::test]
11739async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11740 init_test(cx, |_| {});
11741
11742 let mut cx = EditorTestContext::new(cx).await;
11743
11744 let diff_base = r#"
11745 use some::mod;
11746
11747 const A: u32 = 42;
11748
11749 fn main() {
11750 println!("hello");
11751
11752 println!("world");
11753 }
11754 "#
11755 .unindent();
11756
11757 // Edits are modified, removed, modified, added
11758 cx.set_state(
11759 &r#"
11760 use some::modified;
11761
11762 ˇ
11763 fn main() {
11764 println!("hello there");
11765
11766 println!("around the");
11767 println!("world");
11768 }
11769 "#
11770 .unindent(),
11771 );
11772
11773 cx.set_head_text(&diff_base);
11774 executor.run_until_parked();
11775
11776 cx.update_editor(|editor, window, cx| {
11777 //Wrap around the bottom of the buffer
11778 for _ in 0..3 {
11779 editor.go_to_next_hunk(&GoToHunk, window, cx);
11780 }
11781 });
11782
11783 cx.assert_editor_state(
11784 &r#"
11785 ˇuse some::modified;
11786
11787
11788 fn main() {
11789 println!("hello there");
11790
11791 println!("around the");
11792 println!("world");
11793 }
11794 "#
11795 .unindent(),
11796 );
11797
11798 cx.update_editor(|editor, window, cx| {
11799 //Wrap around the top of the buffer
11800 for _ in 0..2 {
11801 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11802 }
11803 });
11804
11805 cx.assert_editor_state(
11806 &r#"
11807 use some::modified;
11808
11809
11810 fn main() {
11811 ˇ println!("hello there");
11812
11813 println!("around the");
11814 println!("world");
11815 }
11816 "#
11817 .unindent(),
11818 );
11819
11820 cx.update_editor(|editor, window, cx| {
11821 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11822 });
11823
11824 cx.assert_editor_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.update_editor(|editor, window, cx| {
11840 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11841 });
11842
11843 cx.assert_editor_state(
11844 &r#"
11845 ˇuse some::modified;
11846
11847
11848 fn main() {
11849 println!("hello there");
11850
11851 println!("around the");
11852 println!("world");
11853 }
11854 "#
11855 .unindent(),
11856 );
11857
11858 cx.update_editor(|editor, window, cx| {
11859 for _ in 0..2 {
11860 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11861 }
11862 });
11863
11864 cx.assert_editor_state(
11865 &r#"
11866 use some::modified;
11867
11868
11869 fn main() {
11870 ˇ println!("hello there");
11871
11872 println!("around the");
11873 println!("world");
11874 }
11875 "#
11876 .unindent(),
11877 );
11878
11879 cx.update_editor(|editor, window, cx| {
11880 editor.fold(&Fold, window, cx);
11881 });
11882
11883 cx.update_editor(|editor, window, cx| {
11884 editor.go_to_next_hunk(&GoToHunk, window, cx);
11885 });
11886
11887 cx.assert_editor_state(
11888 &r#"
11889 ˇuse some::modified;
11890
11891
11892 fn main() {
11893 println!("hello there");
11894
11895 println!("around the");
11896 println!("world");
11897 }
11898 "#
11899 .unindent(),
11900 );
11901}
11902
11903#[test]
11904fn test_split_words() {
11905 fn split(text: &str) -> Vec<&str> {
11906 split_words(text).collect()
11907 }
11908
11909 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11910 assert_eq!(split("hello_world"), &["hello_", "world"]);
11911 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11912 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11913 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11914 assert_eq!(split("helloworld"), &["helloworld"]);
11915
11916 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11917}
11918
11919#[gpui::test]
11920async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11921 init_test(cx, |_| {});
11922
11923 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11924 let mut assert = |before, after| {
11925 let _state_context = cx.set_state(before);
11926 cx.run_until_parked();
11927 cx.update_editor(|editor, window, cx| {
11928 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11929 });
11930 cx.run_until_parked();
11931 cx.assert_editor_state(after);
11932 };
11933
11934 // Outside bracket jumps to outside of matching bracket
11935 assert("console.logˇ(var);", "console.log(var)ˇ;");
11936 assert("console.log(var)ˇ;", "console.logˇ(var);");
11937
11938 // Inside bracket jumps to inside of matching bracket
11939 assert("console.log(ˇvar);", "console.log(varˇ);");
11940 assert("console.log(varˇ);", "console.log(ˇvar);");
11941
11942 // When outside a bracket and inside, favor jumping to the inside bracket
11943 assert(
11944 "console.log('foo', [1, 2, 3]ˇ);",
11945 "console.log(ˇ'foo', [1, 2, 3]);",
11946 );
11947 assert(
11948 "console.log(ˇ'foo', [1, 2, 3]);",
11949 "console.log('foo', [1, 2, 3]ˇ);",
11950 );
11951
11952 // Bias forward if two options are equally likely
11953 assert(
11954 "let result = curried_fun()ˇ();",
11955 "let result = curried_fun()()ˇ;",
11956 );
11957
11958 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11959 assert(
11960 indoc! {"
11961 function test() {
11962 console.log('test')ˇ
11963 }"},
11964 indoc! {"
11965 function test() {
11966 console.logˇ('test')
11967 }"},
11968 );
11969}
11970
11971#[gpui::test]
11972async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11973 init_test(cx, |_| {});
11974
11975 let fs = FakeFs::new(cx.executor());
11976 fs.insert_tree(
11977 path!("/a"),
11978 json!({
11979 "main.rs": "fn main() { let a = 5; }",
11980 "other.rs": "// Test file",
11981 }),
11982 )
11983 .await;
11984 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11985
11986 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11987 language_registry.add(Arc::new(Language::new(
11988 LanguageConfig {
11989 name: "Rust".into(),
11990 matcher: LanguageMatcher {
11991 path_suffixes: vec!["rs".to_string()],
11992 ..Default::default()
11993 },
11994 brackets: BracketPairConfig {
11995 pairs: vec![BracketPair {
11996 start: "{".to_string(),
11997 end: "}".to_string(),
11998 close: true,
11999 surround: true,
12000 newline: true,
12001 }],
12002 disabled_scopes_by_bracket_ix: Vec::new(),
12003 },
12004 ..Default::default()
12005 },
12006 Some(tree_sitter_rust::LANGUAGE.into()),
12007 )));
12008 let mut fake_servers = language_registry.register_fake_lsp(
12009 "Rust",
12010 FakeLspAdapter {
12011 capabilities: lsp::ServerCapabilities {
12012 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12013 first_trigger_character: "{".to_string(),
12014 more_trigger_character: None,
12015 }),
12016 ..Default::default()
12017 },
12018 ..Default::default()
12019 },
12020 );
12021
12022 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12023
12024 let cx = &mut VisualTestContext::from_window(*workspace, cx);
12025
12026 let worktree_id = workspace
12027 .update(cx, |workspace, _, cx| {
12028 workspace.project().update(cx, |project, cx| {
12029 project.worktrees(cx).next().unwrap().read(cx).id()
12030 })
12031 })
12032 .unwrap();
12033
12034 let buffer = project
12035 .update(cx, |project, cx| {
12036 project.open_local_buffer(path!("/a/main.rs"), cx)
12037 })
12038 .await
12039 .unwrap();
12040 let editor_handle = workspace
12041 .update(cx, |workspace, window, cx| {
12042 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12043 })
12044 .unwrap()
12045 .await
12046 .unwrap()
12047 .downcast::<Editor>()
12048 .unwrap();
12049
12050 cx.executor().start_waiting();
12051 let fake_server = fake_servers.next().await.unwrap();
12052
12053 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
12054 assert_eq!(
12055 params.text_document_position.text_document.uri,
12056 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12057 );
12058 assert_eq!(
12059 params.text_document_position.position,
12060 lsp::Position::new(0, 21),
12061 );
12062
12063 Ok(Some(vec![lsp::TextEdit {
12064 new_text: "]".to_string(),
12065 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12066 }]))
12067 });
12068
12069 editor_handle.update_in(cx, |editor, window, cx| {
12070 window.focus(&editor.focus_handle(cx));
12071 editor.change_selections(None, window, cx, |s| {
12072 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12073 });
12074 editor.handle_input("{", window, cx);
12075 });
12076
12077 cx.executor().run_until_parked();
12078
12079 buffer.update(cx, |buffer, _| {
12080 assert_eq!(
12081 buffer.text(),
12082 "fn main() { let a = {5}; }",
12083 "No extra braces from on type formatting should appear in the buffer"
12084 )
12085 });
12086}
12087
12088#[gpui::test]
12089async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12090 init_test(cx, |_| {});
12091
12092 let fs = FakeFs::new(cx.executor());
12093 fs.insert_tree(
12094 path!("/a"),
12095 json!({
12096 "main.rs": "fn main() { let a = 5; }",
12097 "other.rs": "// Test file",
12098 }),
12099 )
12100 .await;
12101
12102 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12103
12104 let server_restarts = Arc::new(AtomicUsize::new(0));
12105 let closure_restarts = Arc::clone(&server_restarts);
12106 let language_server_name = "test language server";
12107 let language_name: LanguageName = "Rust".into();
12108
12109 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12110 language_registry.add(Arc::new(Language::new(
12111 LanguageConfig {
12112 name: language_name.clone(),
12113 matcher: LanguageMatcher {
12114 path_suffixes: vec!["rs".to_string()],
12115 ..Default::default()
12116 },
12117 ..Default::default()
12118 },
12119 Some(tree_sitter_rust::LANGUAGE.into()),
12120 )));
12121 let mut fake_servers = language_registry.register_fake_lsp(
12122 "Rust",
12123 FakeLspAdapter {
12124 name: language_server_name,
12125 initialization_options: Some(json!({
12126 "testOptionValue": true
12127 })),
12128 initializer: Some(Box::new(move |fake_server| {
12129 let task_restarts = Arc::clone(&closure_restarts);
12130 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
12131 task_restarts.fetch_add(1, atomic::Ordering::Release);
12132 futures::future::ready(Ok(()))
12133 });
12134 })),
12135 ..Default::default()
12136 },
12137 );
12138
12139 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12140 let _buffer = project
12141 .update(cx, |project, cx| {
12142 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12143 })
12144 .await
12145 .unwrap();
12146 let _fake_server = fake_servers.next().await.unwrap();
12147 update_test_language_settings(cx, |language_settings| {
12148 language_settings.languages.insert(
12149 language_name.clone(),
12150 LanguageSettingsContent {
12151 tab_size: NonZeroU32::new(8),
12152 ..Default::default()
12153 },
12154 );
12155 });
12156 cx.executor().run_until_parked();
12157 assert_eq!(
12158 server_restarts.load(atomic::Ordering::Acquire),
12159 0,
12160 "Should not restart LSP server on an unrelated change"
12161 );
12162
12163 update_test_project_settings(cx, |project_settings| {
12164 project_settings.lsp.insert(
12165 "Some other server name".into(),
12166 LspSettings {
12167 binary: None,
12168 settings: None,
12169 initialization_options: Some(json!({
12170 "some other init value": false
12171 })),
12172 },
12173 );
12174 });
12175 cx.executor().run_until_parked();
12176 assert_eq!(
12177 server_restarts.load(atomic::Ordering::Acquire),
12178 0,
12179 "Should not restart LSP server on an unrelated LSP settings change"
12180 );
12181
12182 update_test_project_settings(cx, |project_settings| {
12183 project_settings.lsp.insert(
12184 language_server_name.into(),
12185 LspSettings {
12186 binary: None,
12187 settings: None,
12188 initialization_options: Some(json!({
12189 "anotherInitValue": false
12190 })),
12191 },
12192 );
12193 });
12194 cx.executor().run_until_parked();
12195 assert_eq!(
12196 server_restarts.load(atomic::Ordering::Acquire),
12197 1,
12198 "Should restart LSP server on a related LSP settings change"
12199 );
12200
12201 update_test_project_settings(cx, |project_settings| {
12202 project_settings.lsp.insert(
12203 language_server_name.into(),
12204 LspSettings {
12205 binary: None,
12206 settings: None,
12207 initialization_options: Some(json!({
12208 "anotherInitValue": false
12209 })),
12210 },
12211 );
12212 });
12213 cx.executor().run_until_parked();
12214 assert_eq!(
12215 server_restarts.load(atomic::Ordering::Acquire),
12216 1,
12217 "Should not restart LSP server on a related LSP settings change that is the same"
12218 );
12219
12220 update_test_project_settings(cx, |project_settings| {
12221 project_settings.lsp.insert(
12222 language_server_name.into(),
12223 LspSettings {
12224 binary: None,
12225 settings: None,
12226 initialization_options: None,
12227 },
12228 );
12229 });
12230 cx.executor().run_until_parked();
12231 assert_eq!(
12232 server_restarts.load(atomic::Ordering::Acquire),
12233 2,
12234 "Should restart LSP server on another related LSP settings change"
12235 );
12236}
12237
12238#[gpui::test]
12239async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12240 init_test(cx, |_| {});
12241
12242 let mut cx = EditorLspTestContext::new_rust(
12243 lsp::ServerCapabilities {
12244 completion_provider: Some(lsp::CompletionOptions {
12245 trigger_characters: Some(vec![".".to_string()]),
12246 resolve_provider: Some(true),
12247 ..Default::default()
12248 }),
12249 ..Default::default()
12250 },
12251 cx,
12252 )
12253 .await;
12254
12255 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12256 cx.simulate_keystroke(".");
12257 let completion_item = lsp::CompletionItem {
12258 label: "some".into(),
12259 kind: Some(lsp::CompletionItemKind::SNIPPET),
12260 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12261 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12262 kind: lsp::MarkupKind::Markdown,
12263 value: "```rust\nSome(2)\n```".to_string(),
12264 })),
12265 deprecated: Some(false),
12266 sort_text: Some("fffffff2".to_string()),
12267 filter_text: Some("some".to_string()),
12268 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12269 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12270 range: lsp::Range {
12271 start: lsp::Position {
12272 line: 0,
12273 character: 22,
12274 },
12275 end: lsp::Position {
12276 line: 0,
12277 character: 22,
12278 },
12279 },
12280 new_text: "Some(2)".to_string(),
12281 })),
12282 additional_text_edits: Some(vec![lsp::TextEdit {
12283 range: lsp::Range {
12284 start: lsp::Position {
12285 line: 0,
12286 character: 20,
12287 },
12288 end: lsp::Position {
12289 line: 0,
12290 character: 22,
12291 },
12292 },
12293 new_text: "".to_string(),
12294 }]),
12295 ..Default::default()
12296 };
12297
12298 let closure_completion_item = completion_item.clone();
12299 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12300 let task_completion_item = closure_completion_item.clone();
12301 async move {
12302 Ok(Some(lsp::CompletionResponse::Array(vec![
12303 task_completion_item,
12304 ])))
12305 }
12306 });
12307
12308 request.next().await;
12309
12310 cx.condition(|editor, _| editor.context_menu_visible())
12311 .await;
12312 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12313 editor
12314 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12315 .unwrap()
12316 });
12317 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12318
12319 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12320 let task_completion_item = completion_item.clone();
12321 async move { Ok(task_completion_item) }
12322 })
12323 .next()
12324 .await
12325 .unwrap();
12326 apply_additional_edits.await.unwrap();
12327 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12328}
12329
12330#[gpui::test]
12331async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12332 init_test(cx, |_| {});
12333
12334 let mut cx = EditorLspTestContext::new_rust(
12335 lsp::ServerCapabilities {
12336 completion_provider: Some(lsp::CompletionOptions {
12337 trigger_characters: Some(vec![".".to_string()]),
12338 resolve_provider: Some(true),
12339 ..Default::default()
12340 }),
12341 ..Default::default()
12342 },
12343 cx,
12344 )
12345 .await;
12346
12347 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12348 cx.simulate_keystroke(".");
12349
12350 let item1 = lsp::CompletionItem {
12351 label: "method id()".to_string(),
12352 filter_text: Some("id".to_string()),
12353 detail: None,
12354 documentation: None,
12355 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12356 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12357 new_text: ".id".to_string(),
12358 })),
12359 ..lsp::CompletionItem::default()
12360 };
12361
12362 let item2 = lsp::CompletionItem {
12363 label: "other".to_string(),
12364 filter_text: Some("other".to_string()),
12365 detail: None,
12366 documentation: None,
12367 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12368 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12369 new_text: ".other".to_string(),
12370 })),
12371 ..lsp::CompletionItem::default()
12372 };
12373
12374 let item1 = item1.clone();
12375 cx.handle_request::<lsp::request::Completion, _, _>({
12376 let item1 = item1.clone();
12377 move |_, _, _| {
12378 let item1 = item1.clone();
12379 let item2 = item2.clone();
12380 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12381 }
12382 })
12383 .next()
12384 .await;
12385
12386 cx.condition(|editor, _| editor.context_menu_visible())
12387 .await;
12388 cx.update_editor(|editor, _, _| {
12389 let context_menu = editor.context_menu.borrow_mut();
12390 let context_menu = context_menu
12391 .as_ref()
12392 .expect("Should have the context menu deployed");
12393 match context_menu {
12394 CodeContextMenu::Completions(completions_menu) => {
12395 let completions = completions_menu.completions.borrow_mut();
12396 assert_eq!(
12397 completions
12398 .iter()
12399 .map(|completion| &completion.label.text)
12400 .collect::<Vec<_>>(),
12401 vec!["method id()", "other"]
12402 )
12403 }
12404 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12405 }
12406 });
12407
12408 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12409 let item1 = item1.clone();
12410 move |_, item_to_resolve, _| {
12411 let item1 = item1.clone();
12412 async move {
12413 if item1 == item_to_resolve {
12414 Ok(lsp::CompletionItem {
12415 label: "method id()".to_string(),
12416 filter_text: Some("id".to_string()),
12417 detail: Some("Now resolved!".to_string()),
12418 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12419 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12420 range: lsp::Range::new(
12421 lsp::Position::new(0, 22),
12422 lsp::Position::new(0, 22),
12423 ),
12424 new_text: ".id".to_string(),
12425 })),
12426 ..lsp::CompletionItem::default()
12427 })
12428 } else {
12429 Ok(item_to_resolve)
12430 }
12431 }
12432 }
12433 })
12434 .next()
12435 .await
12436 .unwrap();
12437 cx.run_until_parked();
12438
12439 cx.update_editor(|editor, window, cx| {
12440 editor.context_menu_next(&Default::default(), window, cx);
12441 });
12442
12443 cx.update_editor(|editor, _, _| {
12444 let context_menu = editor.context_menu.borrow_mut();
12445 let context_menu = context_menu
12446 .as_ref()
12447 .expect("Should have the context menu deployed");
12448 match context_menu {
12449 CodeContextMenu::Completions(completions_menu) => {
12450 let completions = completions_menu.completions.borrow_mut();
12451 assert_eq!(
12452 completions
12453 .iter()
12454 .map(|completion| &completion.label.text)
12455 .collect::<Vec<_>>(),
12456 vec!["method id() Now resolved!", "other"],
12457 "Should update first completion label, but not second as the filter text did not match."
12458 );
12459 }
12460 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12461 }
12462 });
12463}
12464
12465#[gpui::test]
12466async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12467 init_test(cx, |_| {});
12468
12469 let mut cx = EditorLspTestContext::new_rust(
12470 lsp::ServerCapabilities {
12471 completion_provider: Some(lsp::CompletionOptions {
12472 trigger_characters: Some(vec![".".to_string()]),
12473 resolve_provider: Some(true),
12474 ..Default::default()
12475 }),
12476 ..Default::default()
12477 },
12478 cx,
12479 )
12480 .await;
12481
12482 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12483 cx.simulate_keystroke(".");
12484
12485 let unresolved_item_1 = lsp::CompletionItem {
12486 label: "id".to_string(),
12487 filter_text: Some("id".to_string()),
12488 detail: None,
12489 documentation: None,
12490 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12491 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12492 new_text: ".id".to_string(),
12493 })),
12494 ..lsp::CompletionItem::default()
12495 };
12496 let resolved_item_1 = lsp::CompletionItem {
12497 additional_text_edits: Some(vec![lsp::TextEdit {
12498 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12499 new_text: "!!".to_string(),
12500 }]),
12501 ..unresolved_item_1.clone()
12502 };
12503 let unresolved_item_2 = lsp::CompletionItem {
12504 label: "other".to_string(),
12505 filter_text: Some("other".to_string()),
12506 detail: None,
12507 documentation: None,
12508 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12509 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12510 new_text: ".other".to_string(),
12511 })),
12512 ..lsp::CompletionItem::default()
12513 };
12514 let resolved_item_2 = lsp::CompletionItem {
12515 additional_text_edits: Some(vec![lsp::TextEdit {
12516 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12517 new_text: "??".to_string(),
12518 }]),
12519 ..unresolved_item_2.clone()
12520 };
12521
12522 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12523 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12524 cx.lsp
12525 .server
12526 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12527 let unresolved_item_1 = unresolved_item_1.clone();
12528 let resolved_item_1 = resolved_item_1.clone();
12529 let unresolved_item_2 = unresolved_item_2.clone();
12530 let resolved_item_2 = resolved_item_2.clone();
12531 let resolve_requests_1 = resolve_requests_1.clone();
12532 let resolve_requests_2 = resolve_requests_2.clone();
12533 move |unresolved_request, _| {
12534 let unresolved_item_1 = unresolved_item_1.clone();
12535 let resolved_item_1 = resolved_item_1.clone();
12536 let unresolved_item_2 = unresolved_item_2.clone();
12537 let resolved_item_2 = resolved_item_2.clone();
12538 let resolve_requests_1 = resolve_requests_1.clone();
12539 let resolve_requests_2 = resolve_requests_2.clone();
12540 async move {
12541 if unresolved_request == unresolved_item_1 {
12542 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12543 Ok(resolved_item_1.clone())
12544 } else if unresolved_request == unresolved_item_2 {
12545 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12546 Ok(resolved_item_2.clone())
12547 } else {
12548 panic!("Unexpected completion item {unresolved_request:?}")
12549 }
12550 }
12551 }
12552 })
12553 .detach();
12554
12555 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12556 let unresolved_item_1 = unresolved_item_1.clone();
12557 let unresolved_item_2 = unresolved_item_2.clone();
12558 async move {
12559 Ok(Some(lsp::CompletionResponse::Array(vec![
12560 unresolved_item_1,
12561 unresolved_item_2,
12562 ])))
12563 }
12564 })
12565 .next()
12566 .await;
12567
12568 cx.condition(|editor, _| editor.context_menu_visible())
12569 .await;
12570 cx.update_editor(|editor, _, _| {
12571 let context_menu = editor.context_menu.borrow_mut();
12572 let context_menu = context_menu
12573 .as_ref()
12574 .expect("Should have the context menu deployed");
12575 match context_menu {
12576 CodeContextMenu::Completions(completions_menu) => {
12577 let completions = completions_menu.completions.borrow_mut();
12578 assert_eq!(
12579 completions
12580 .iter()
12581 .map(|completion| &completion.label.text)
12582 .collect::<Vec<_>>(),
12583 vec!["id", "other"]
12584 )
12585 }
12586 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12587 }
12588 });
12589 cx.run_until_parked();
12590
12591 cx.update_editor(|editor, window, cx| {
12592 editor.context_menu_next(&ContextMenuNext, window, cx);
12593 });
12594 cx.run_until_parked();
12595 cx.update_editor(|editor, window, cx| {
12596 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12597 });
12598 cx.run_until_parked();
12599 cx.update_editor(|editor, window, cx| {
12600 editor.context_menu_next(&ContextMenuNext, window, cx);
12601 });
12602 cx.run_until_parked();
12603 cx.update_editor(|editor, window, cx| {
12604 editor
12605 .compose_completion(&ComposeCompletion::default(), window, cx)
12606 .expect("No task returned")
12607 })
12608 .await
12609 .expect("Completion failed");
12610 cx.run_until_parked();
12611
12612 cx.update_editor(|editor, _, cx| {
12613 assert_eq!(
12614 resolve_requests_1.load(atomic::Ordering::Acquire),
12615 1,
12616 "Should always resolve once despite multiple selections"
12617 );
12618 assert_eq!(
12619 resolve_requests_2.load(atomic::Ordering::Acquire),
12620 1,
12621 "Should always resolve once after multiple selections and applying the completion"
12622 );
12623 assert_eq!(
12624 editor.text(cx),
12625 "fn main() { let a = ??.other; }",
12626 "Should use resolved data when applying the completion"
12627 );
12628 });
12629}
12630
12631#[gpui::test]
12632async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12633 init_test(cx, |_| {});
12634
12635 let item_0 = lsp::CompletionItem {
12636 label: "abs".into(),
12637 insert_text: Some("abs".into()),
12638 data: Some(json!({ "very": "special"})),
12639 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12640 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12641 lsp::InsertReplaceEdit {
12642 new_text: "abs".to_string(),
12643 insert: lsp::Range::default(),
12644 replace: lsp::Range::default(),
12645 },
12646 )),
12647 ..lsp::CompletionItem::default()
12648 };
12649 let items = iter::once(item_0.clone())
12650 .chain((11..51).map(|i| lsp::CompletionItem {
12651 label: format!("item_{}", i),
12652 insert_text: Some(format!("item_{}", i)),
12653 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12654 ..lsp::CompletionItem::default()
12655 }))
12656 .collect::<Vec<_>>();
12657
12658 let default_commit_characters = vec!["?".to_string()];
12659 let default_data = json!({ "default": "data"});
12660 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12661 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12662 let default_edit_range = lsp::Range {
12663 start: lsp::Position {
12664 line: 0,
12665 character: 5,
12666 },
12667 end: lsp::Position {
12668 line: 0,
12669 character: 5,
12670 },
12671 };
12672
12673 let mut cx = EditorLspTestContext::new_rust(
12674 lsp::ServerCapabilities {
12675 completion_provider: Some(lsp::CompletionOptions {
12676 trigger_characters: Some(vec![".".to_string()]),
12677 resolve_provider: Some(true),
12678 ..Default::default()
12679 }),
12680 ..Default::default()
12681 },
12682 cx,
12683 )
12684 .await;
12685
12686 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12687 cx.simulate_keystroke(".");
12688
12689 let completion_data = default_data.clone();
12690 let completion_characters = default_commit_characters.clone();
12691 let completion_items = items.clone();
12692 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12693 let default_data = completion_data.clone();
12694 let default_commit_characters = completion_characters.clone();
12695 let items = completion_items.clone();
12696 async move {
12697 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12698 items,
12699 item_defaults: Some(lsp::CompletionListItemDefaults {
12700 data: Some(default_data.clone()),
12701 commit_characters: Some(default_commit_characters.clone()),
12702 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12703 default_edit_range,
12704 )),
12705 insert_text_format: Some(default_insert_text_format),
12706 insert_text_mode: Some(default_insert_text_mode),
12707 }),
12708 ..lsp::CompletionList::default()
12709 })))
12710 }
12711 })
12712 .next()
12713 .await;
12714
12715 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12716 cx.lsp
12717 .server
12718 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12719 let closure_resolved_items = resolved_items.clone();
12720 move |item_to_resolve, _| {
12721 let closure_resolved_items = closure_resolved_items.clone();
12722 async move {
12723 closure_resolved_items.lock().push(item_to_resolve.clone());
12724 Ok(item_to_resolve)
12725 }
12726 }
12727 })
12728 .detach();
12729
12730 cx.condition(|editor, _| editor.context_menu_visible())
12731 .await;
12732 cx.run_until_parked();
12733 cx.update_editor(|editor, _, _| {
12734 let menu = editor.context_menu.borrow_mut();
12735 match menu.as_ref().expect("should have the completions menu") {
12736 CodeContextMenu::Completions(completions_menu) => {
12737 assert_eq!(
12738 completions_menu
12739 .entries
12740 .borrow()
12741 .iter()
12742 .map(|mat| mat.string.clone())
12743 .collect::<Vec<String>>(),
12744 items
12745 .iter()
12746 .map(|completion| completion.label.clone())
12747 .collect::<Vec<String>>()
12748 );
12749 }
12750 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12751 }
12752 });
12753 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12754 // with 4 from the end.
12755 assert_eq!(
12756 *resolved_items.lock(),
12757 [&items[0..16], &items[items.len() - 4..items.len()]]
12758 .concat()
12759 .iter()
12760 .cloned()
12761 .map(|mut item| {
12762 if item.data.is_none() {
12763 item.data = Some(default_data.clone());
12764 }
12765 item
12766 })
12767 .collect::<Vec<lsp::CompletionItem>>(),
12768 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12769 );
12770 resolved_items.lock().clear();
12771
12772 cx.update_editor(|editor, window, cx| {
12773 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12774 });
12775 cx.run_until_parked();
12776 // Completions that have already been resolved are skipped.
12777 assert_eq!(
12778 *resolved_items.lock(),
12779 items[items.len() - 16..items.len() - 4]
12780 .iter()
12781 .cloned()
12782 .map(|mut item| {
12783 if item.data.is_none() {
12784 item.data = Some(default_data.clone());
12785 }
12786 item
12787 })
12788 .collect::<Vec<lsp::CompletionItem>>()
12789 );
12790 resolved_items.lock().clear();
12791}
12792
12793#[gpui::test]
12794async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12795 init_test(cx, |_| {});
12796
12797 let mut cx = EditorLspTestContext::new(
12798 Language::new(
12799 LanguageConfig {
12800 matcher: LanguageMatcher {
12801 path_suffixes: vec!["jsx".into()],
12802 ..Default::default()
12803 },
12804 overrides: [(
12805 "element".into(),
12806 LanguageConfigOverride {
12807 word_characters: Override::Set(['-'].into_iter().collect()),
12808 ..Default::default()
12809 },
12810 )]
12811 .into_iter()
12812 .collect(),
12813 ..Default::default()
12814 },
12815 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12816 )
12817 .with_override_query("(jsx_self_closing_element) @element")
12818 .unwrap(),
12819 lsp::ServerCapabilities {
12820 completion_provider: Some(lsp::CompletionOptions {
12821 trigger_characters: Some(vec![":".to_string()]),
12822 ..Default::default()
12823 }),
12824 ..Default::default()
12825 },
12826 cx,
12827 )
12828 .await;
12829
12830 cx.lsp
12831 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12832 Ok(Some(lsp::CompletionResponse::Array(vec![
12833 lsp::CompletionItem {
12834 label: "bg-blue".into(),
12835 ..Default::default()
12836 },
12837 lsp::CompletionItem {
12838 label: "bg-red".into(),
12839 ..Default::default()
12840 },
12841 lsp::CompletionItem {
12842 label: "bg-yellow".into(),
12843 ..Default::default()
12844 },
12845 ])))
12846 });
12847
12848 cx.set_state(r#"<p class="bgˇ" />"#);
12849
12850 // Trigger completion when typing a dash, because the dash is an extra
12851 // word character in the 'element' scope, which contains the cursor.
12852 cx.simulate_keystroke("-");
12853 cx.executor().run_until_parked();
12854 cx.update_editor(|editor, _, _| {
12855 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12856 {
12857 assert_eq!(
12858 completion_menu_entries(&menu),
12859 &["bg-red", "bg-blue", "bg-yellow"]
12860 );
12861 } else {
12862 panic!("expected completion menu to be open");
12863 }
12864 });
12865
12866 cx.simulate_keystroke("l");
12867 cx.executor().run_until_parked();
12868 cx.update_editor(|editor, _, _| {
12869 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12870 {
12871 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12872 } else {
12873 panic!("expected completion menu to be open");
12874 }
12875 });
12876
12877 // When filtering completions, consider the character after the '-' to
12878 // be the start of a subword.
12879 cx.set_state(r#"<p class="yelˇ" />"#);
12880 cx.simulate_keystroke("l");
12881 cx.executor().run_until_parked();
12882 cx.update_editor(|editor, _, _| {
12883 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12884 {
12885 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12886 } else {
12887 panic!("expected completion menu to be open");
12888 }
12889 });
12890}
12891
12892fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12893 let entries = menu.entries.borrow();
12894 entries.iter().map(|mat| mat.string.clone()).collect()
12895}
12896
12897#[gpui::test]
12898async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12899 init_test(cx, |settings| {
12900 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12901 FormatterList(vec![Formatter::Prettier].into()),
12902 ))
12903 });
12904
12905 let fs = FakeFs::new(cx.executor());
12906 fs.insert_file(path!("/file.ts"), Default::default()).await;
12907
12908 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12909 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12910
12911 language_registry.add(Arc::new(Language::new(
12912 LanguageConfig {
12913 name: "TypeScript".into(),
12914 matcher: LanguageMatcher {
12915 path_suffixes: vec!["ts".to_string()],
12916 ..Default::default()
12917 },
12918 ..Default::default()
12919 },
12920 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12921 )));
12922 update_test_language_settings(cx, |settings| {
12923 settings.defaults.prettier = Some(PrettierSettings {
12924 allowed: true,
12925 ..PrettierSettings::default()
12926 });
12927 });
12928
12929 let test_plugin = "test_plugin";
12930 let _ = language_registry.register_fake_lsp(
12931 "TypeScript",
12932 FakeLspAdapter {
12933 prettier_plugins: vec![test_plugin],
12934 ..Default::default()
12935 },
12936 );
12937
12938 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12939 let buffer = project
12940 .update(cx, |project, cx| {
12941 project.open_local_buffer(path!("/file.ts"), cx)
12942 })
12943 .await
12944 .unwrap();
12945
12946 let buffer_text = "one\ntwo\nthree\n";
12947 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12948 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12949 editor.update_in(cx, |editor, window, cx| {
12950 editor.set_text(buffer_text, window, cx)
12951 });
12952
12953 editor
12954 .update_in(cx, |editor, window, cx| {
12955 editor.perform_format(
12956 project.clone(),
12957 FormatTrigger::Manual,
12958 FormatTarget::Buffers,
12959 window,
12960 cx,
12961 )
12962 })
12963 .unwrap()
12964 .await;
12965 assert_eq!(
12966 editor.update(cx, |editor, cx| editor.text(cx)),
12967 buffer_text.to_string() + prettier_format_suffix,
12968 "Test prettier formatting was not applied to the original buffer text",
12969 );
12970
12971 update_test_language_settings(cx, |settings| {
12972 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12973 });
12974 let format = editor.update_in(cx, |editor, window, cx| {
12975 editor.perform_format(
12976 project.clone(),
12977 FormatTrigger::Manual,
12978 FormatTarget::Buffers,
12979 window,
12980 cx,
12981 )
12982 });
12983 format.await.unwrap();
12984 assert_eq!(
12985 editor.update(cx, |editor, cx| editor.text(cx)),
12986 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12987 "Autoformatting (via test prettier) was not applied to the original buffer text",
12988 );
12989}
12990
12991#[gpui::test]
12992async fn test_addition_reverts(cx: &mut TestAppContext) {
12993 init_test(cx, |_| {});
12994 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12995 let base_text = indoc! {r#"
12996 struct Row;
12997 struct Row1;
12998 struct Row2;
12999
13000 struct Row4;
13001 struct Row5;
13002 struct Row6;
13003
13004 struct Row8;
13005 struct Row9;
13006 struct Row10;"#};
13007
13008 // When addition hunks are not adjacent to carets, no hunk revert is performed
13009 assert_hunk_revert(
13010 indoc! {r#"struct Row;
13011 struct Row1;
13012 struct Row1.1;
13013 struct Row1.2;
13014 struct Row2;ˇ
13015
13016 struct Row4;
13017 struct Row5;
13018 struct Row6;
13019
13020 struct Row8;
13021 ˇstruct Row9;
13022 struct Row9.1;
13023 struct Row9.2;
13024 struct Row9.3;
13025 struct Row10;"#},
13026 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13027 indoc! {r#"struct Row;
13028 struct Row1;
13029 struct Row1.1;
13030 struct Row1.2;
13031 struct Row2;ˇ
13032
13033 struct Row4;
13034 struct Row5;
13035 struct Row6;
13036
13037 struct Row8;
13038 ˇstruct Row9;
13039 struct Row9.1;
13040 struct Row9.2;
13041 struct Row9.3;
13042 struct Row10;"#},
13043 base_text,
13044 &mut cx,
13045 );
13046 // Same for selections
13047 assert_hunk_revert(
13048 indoc! {r#"struct Row;
13049 struct Row1;
13050 struct Row2;
13051 struct Row2.1;
13052 struct Row2.2;
13053 «ˇ
13054 struct Row4;
13055 struct» Row5;
13056 «struct Row6;
13057 ˇ»
13058 struct Row9.1;
13059 struct Row9.2;
13060 struct Row9.3;
13061 struct Row8;
13062 struct Row9;
13063 struct Row10;"#},
13064 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13065 indoc! {r#"struct Row;
13066 struct Row1;
13067 struct Row2;
13068 struct Row2.1;
13069 struct Row2.2;
13070 «ˇ
13071 struct Row4;
13072 struct» Row5;
13073 «struct Row6;
13074 ˇ»
13075 struct Row9.1;
13076 struct Row9.2;
13077 struct Row9.3;
13078 struct Row8;
13079 struct Row9;
13080 struct Row10;"#},
13081 base_text,
13082 &mut cx,
13083 );
13084
13085 // When carets and selections intersect the addition hunks, those are reverted.
13086 // Adjacent carets got merged.
13087 assert_hunk_revert(
13088 indoc! {r#"struct Row;
13089 ˇ// something on the top
13090 struct Row1;
13091 struct Row2;
13092 struct Roˇw3.1;
13093 struct Row2.2;
13094 struct Row2.3;ˇ
13095
13096 struct Row4;
13097 struct ˇRow5.1;
13098 struct Row5.2;
13099 struct «Rowˇ»5.3;
13100 struct Row5;
13101 struct Row6;
13102 ˇ
13103 struct Row9.1;
13104 struct «Rowˇ»9.2;
13105 struct «ˇRow»9.3;
13106 struct Row8;
13107 struct Row9;
13108 «ˇ// something on bottom»
13109 struct Row10;"#},
13110 vec![
13111 DiffHunkStatusKind::Added,
13112 DiffHunkStatusKind::Added,
13113 DiffHunkStatusKind::Added,
13114 DiffHunkStatusKind::Added,
13115 DiffHunkStatusKind::Added,
13116 ],
13117 indoc! {r#"struct Row;
13118 ˇstruct Row1;
13119 struct Row2;
13120 ˇ
13121 struct Row4;
13122 ˇstruct Row5;
13123 struct Row6;
13124 ˇ
13125 ˇstruct Row8;
13126 struct Row9;
13127 ˇstruct Row10;"#},
13128 base_text,
13129 &mut cx,
13130 );
13131}
13132
13133#[gpui::test]
13134async fn test_modification_reverts(cx: &mut TestAppContext) {
13135 init_test(cx, |_| {});
13136 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13137 let base_text = indoc! {r#"
13138 struct Row;
13139 struct Row1;
13140 struct Row2;
13141
13142 struct Row4;
13143 struct Row5;
13144 struct Row6;
13145
13146 struct Row8;
13147 struct Row9;
13148 struct Row10;"#};
13149
13150 // Modification hunks behave the same as the addition ones.
13151 assert_hunk_revert(
13152 indoc! {r#"struct Row;
13153 struct Row1;
13154 struct Row33;
13155 ˇ
13156 struct Row4;
13157 struct Row5;
13158 struct Row6;
13159 ˇ
13160 struct Row99;
13161 struct Row9;
13162 struct Row10;"#},
13163 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13164 indoc! {r#"struct Row;
13165 struct Row1;
13166 struct Row33;
13167 ˇ
13168 struct Row4;
13169 struct Row5;
13170 struct Row6;
13171 ˇ
13172 struct Row99;
13173 struct Row9;
13174 struct Row10;"#},
13175 base_text,
13176 &mut cx,
13177 );
13178 assert_hunk_revert(
13179 indoc! {r#"struct Row;
13180 struct Row1;
13181 struct Row33;
13182 «ˇ
13183 struct Row4;
13184 struct» Row5;
13185 «struct Row6;
13186 ˇ»
13187 struct Row99;
13188 struct Row9;
13189 struct Row10;"#},
13190 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13191 indoc! {r#"struct Row;
13192 struct Row1;
13193 struct Row33;
13194 «ˇ
13195 struct Row4;
13196 struct» Row5;
13197 «struct Row6;
13198 ˇ»
13199 struct Row99;
13200 struct Row9;
13201 struct Row10;"#},
13202 base_text,
13203 &mut cx,
13204 );
13205
13206 assert_hunk_revert(
13207 indoc! {r#"ˇstruct Row1.1;
13208 struct Row1;
13209 «ˇstr»uct Row22;
13210
13211 struct ˇRow44;
13212 struct Row5;
13213 struct «Rˇ»ow66;ˇ
13214
13215 «struˇ»ct Row88;
13216 struct Row9;
13217 struct Row1011;ˇ"#},
13218 vec![
13219 DiffHunkStatusKind::Modified,
13220 DiffHunkStatusKind::Modified,
13221 DiffHunkStatusKind::Modified,
13222 DiffHunkStatusKind::Modified,
13223 DiffHunkStatusKind::Modified,
13224 DiffHunkStatusKind::Modified,
13225 ],
13226 indoc! {r#"struct Row;
13227 ˇstruct Row1;
13228 struct Row2;
13229 ˇ
13230 struct Row4;
13231 ˇstruct Row5;
13232 struct Row6;
13233 ˇ
13234 struct Row8;
13235 ˇstruct Row9;
13236 struct Row10;ˇ"#},
13237 base_text,
13238 &mut cx,
13239 );
13240}
13241
13242#[gpui::test]
13243async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13244 init_test(cx, |_| {});
13245 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13246 let base_text = indoc! {r#"
13247 one
13248
13249 two
13250 three
13251 "#};
13252
13253 cx.set_head_text(base_text);
13254 cx.set_state("\nˇ\n");
13255 cx.executor().run_until_parked();
13256 cx.update_editor(|editor, _window, cx| {
13257 editor.expand_selected_diff_hunks(cx);
13258 });
13259 cx.executor().run_until_parked();
13260 cx.update_editor(|editor, window, cx| {
13261 editor.backspace(&Default::default(), window, cx);
13262 });
13263 cx.run_until_parked();
13264 cx.assert_state_with_diff(
13265 indoc! {r#"
13266
13267 - two
13268 - threeˇ
13269 +
13270 "#}
13271 .to_string(),
13272 );
13273}
13274
13275#[gpui::test]
13276async fn test_deletion_reverts(cx: &mut TestAppContext) {
13277 init_test(cx, |_| {});
13278 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13279 let base_text = indoc! {r#"struct Row;
13280struct Row1;
13281struct Row2;
13282
13283struct Row4;
13284struct Row5;
13285struct Row6;
13286
13287struct Row8;
13288struct Row9;
13289struct Row10;"#};
13290
13291 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13292 assert_hunk_revert(
13293 indoc! {r#"struct Row;
13294 struct Row2;
13295
13296 ˇstruct Row4;
13297 struct Row5;
13298 struct Row6;
13299 ˇ
13300 struct Row8;
13301 struct Row10;"#},
13302 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13303 indoc! {r#"struct Row;
13304 struct Row2;
13305
13306 ˇstruct Row4;
13307 struct Row5;
13308 struct Row6;
13309 ˇ
13310 struct Row8;
13311 struct Row10;"#},
13312 base_text,
13313 &mut cx,
13314 );
13315 assert_hunk_revert(
13316 indoc! {r#"struct Row;
13317 struct Row2;
13318
13319 «ˇstruct Row4;
13320 struct» Row5;
13321 «struct Row6;
13322 ˇ»
13323 struct Row8;
13324 struct Row10;"#},
13325 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13326 indoc! {r#"struct Row;
13327 struct Row2;
13328
13329 «ˇstruct Row4;
13330 struct» Row5;
13331 «struct Row6;
13332 ˇ»
13333 struct Row8;
13334 struct Row10;"#},
13335 base_text,
13336 &mut cx,
13337 );
13338
13339 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13340 assert_hunk_revert(
13341 indoc! {r#"struct Row;
13342 ˇstruct Row2;
13343
13344 struct Row4;
13345 struct Row5;
13346 struct Row6;
13347
13348 struct Row8;ˇ
13349 struct Row10;"#},
13350 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13351 indoc! {r#"struct Row;
13352 struct Row1;
13353 ˇstruct Row2;
13354
13355 struct Row4;
13356 struct Row5;
13357 struct Row6;
13358
13359 struct Row8;ˇ
13360 struct Row9;
13361 struct Row10;"#},
13362 base_text,
13363 &mut cx,
13364 );
13365 assert_hunk_revert(
13366 indoc! {r#"struct Row;
13367 struct Row2«ˇ;
13368 struct Row4;
13369 struct» Row5;
13370 «struct Row6;
13371
13372 struct Row8;ˇ»
13373 struct Row10;"#},
13374 vec![
13375 DiffHunkStatusKind::Deleted,
13376 DiffHunkStatusKind::Deleted,
13377 DiffHunkStatusKind::Deleted,
13378 ],
13379 indoc! {r#"struct Row;
13380 struct Row1;
13381 struct Row2«ˇ;
13382
13383 struct Row4;
13384 struct» Row5;
13385 «struct Row6;
13386
13387 struct Row8;ˇ»
13388 struct Row9;
13389 struct Row10;"#},
13390 base_text,
13391 &mut cx,
13392 );
13393}
13394
13395#[gpui::test]
13396async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13397 init_test(cx, |_| {});
13398
13399 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13400 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13401 let base_text_3 =
13402 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13403
13404 let text_1 = edit_first_char_of_every_line(base_text_1);
13405 let text_2 = edit_first_char_of_every_line(base_text_2);
13406 let text_3 = edit_first_char_of_every_line(base_text_3);
13407
13408 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13409 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13410 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13411
13412 let multibuffer = cx.new(|cx| {
13413 let mut multibuffer = MultiBuffer::new(ReadWrite);
13414 multibuffer.push_excerpts(
13415 buffer_1.clone(),
13416 [
13417 ExcerptRange {
13418 context: Point::new(0, 0)..Point::new(3, 0),
13419 primary: None,
13420 },
13421 ExcerptRange {
13422 context: Point::new(5, 0)..Point::new(7, 0),
13423 primary: None,
13424 },
13425 ExcerptRange {
13426 context: Point::new(9, 0)..Point::new(10, 4),
13427 primary: None,
13428 },
13429 ],
13430 cx,
13431 );
13432 multibuffer.push_excerpts(
13433 buffer_2.clone(),
13434 [
13435 ExcerptRange {
13436 context: Point::new(0, 0)..Point::new(3, 0),
13437 primary: None,
13438 },
13439 ExcerptRange {
13440 context: Point::new(5, 0)..Point::new(7, 0),
13441 primary: None,
13442 },
13443 ExcerptRange {
13444 context: Point::new(9, 0)..Point::new(10, 4),
13445 primary: None,
13446 },
13447 ],
13448 cx,
13449 );
13450 multibuffer.push_excerpts(
13451 buffer_3.clone(),
13452 [
13453 ExcerptRange {
13454 context: Point::new(0, 0)..Point::new(3, 0),
13455 primary: None,
13456 },
13457 ExcerptRange {
13458 context: Point::new(5, 0)..Point::new(7, 0),
13459 primary: None,
13460 },
13461 ExcerptRange {
13462 context: Point::new(9, 0)..Point::new(10, 4),
13463 primary: None,
13464 },
13465 ],
13466 cx,
13467 );
13468 multibuffer
13469 });
13470
13471 let fs = FakeFs::new(cx.executor());
13472 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13473 let (editor, cx) = cx
13474 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13475 editor.update_in(cx, |editor, _window, cx| {
13476 for (buffer, diff_base) in [
13477 (buffer_1.clone(), base_text_1),
13478 (buffer_2.clone(), base_text_2),
13479 (buffer_3.clone(), base_text_3),
13480 ] {
13481 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13482 editor
13483 .buffer
13484 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13485 }
13486 });
13487 cx.executor().run_until_parked();
13488
13489 editor.update_in(cx, |editor, window, cx| {
13490 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}");
13491 editor.select_all(&SelectAll, window, cx);
13492 editor.git_restore(&Default::default(), window, cx);
13493 });
13494 cx.executor().run_until_parked();
13495
13496 // When all ranges are selected, all buffer hunks are reverted.
13497 editor.update(cx, |editor, cx| {
13498 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");
13499 });
13500 buffer_1.update(cx, |buffer, _| {
13501 assert_eq!(buffer.text(), base_text_1);
13502 });
13503 buffer_2.update(cx, |buffer, _| {
13504 assert_eq!(buffer.text(), base_text_2);
13505 });
13506 buffer_3.update(cx, |buffer, _| {
13507 assert_eq!(buffer.text(), base_text_3);
13508 });
13509
13510 editor.update_in(cx, |editor, window, cx| {
13511 editor.undo(&Default::default(), window, cx);
13512 });
13513
13514 editor.update_in(cx, |editor, window, cx| {
13515 editor.change_selections(None, window, cx, |s| {
13516 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13517 });
13518 editor.git_restore(&Default::default(), window, cx);
13519 });
13520
13521 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13522 // but not affect buffer_2 and its related excerpts.
13523 editor.update(cx, |editor, cx| {
13524 assert_eq!(
13525 editor.text(cx),
13526 "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}"
13527 );
13528 });
13529 buffer_1.update(cx, |buffer, _| {
13530 assert_eq!(buffer.text(), base_text_1);
13531 });
13532 buffer_2.update(cx, |buffer, _| {
13533 assert_eq!(
13534 buffer.text(),
13535 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13536 );
13537 });
13538 buffer_3.update(cx, |buffer, _| {
13539 assert_eq!(
13540 buffer.text(),
13541 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13542 );
13543 });
13544
13545 fn edit_first_char_of_every_line(text: &str) -> String {
13546 text.split('\n')
13547 .map(|line| format!("X{}", &line[1..]))
13548 .collect::<Vec<_>>()
13549 .join("\n")
13550 }
13551}
13552
13553#[gpui::test]
13554async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13555 init_test(cx, |_| {});
13556
13557 let cols = 4;
13558 let rows = 10;
13559 let sample_text_1 = sample_text(rows, cols, 'a');
13560 assert_eq!(
13561 sample_text_1,
13562 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13563 );
13564 let sample_text_2 = sample_text(rows, cols, 'l');
13565 assert_eq!(
13566 sample_text_2,
13567 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13568 );
13569 let sample_text_3 = sample_text(rows, cols, 'v');
13570 assert_eq!(
13571 sample_text_3,
13572 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13573 );
13574
13575 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13576 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13577 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13578
13579 let multi_buffer = cx.new(|cx| {
13580 let mut multibuffer = MultiBuffer::new(ReadWrite);
13581 multibuffer.push_excerpts(
13582 buffer_1.clone(),
13583 [
13584 ExcerptRange {
13585 context: Point::new(0, 0)..Point::new(3, 0),
13586 primary: None,
13587 },
13588 ExcerptRange {
13589 context: Point::new(5, 0)..Point::new(7, 0),
13590 primary: None,
13591 },
13592 ExcerptRange {
13593 context: Point::new(9, 0)..Point::new(10, 4),
13594 primary: None,
13595 },
13596 ],
13597 cx,
13598 );
13599 multibuffer.push_excerpts(
13600 buffer_2.clone(),
13601 [
13602 ExcerptRange {
13603 context: Point::new(0, 0)..Point::new(3, 0),
13604 primary: None,
13605 },
13606 ExcerptRange {
13607 context: Point::new(5, 0)..Point::new(7, 0),
13608 primary: None,
13609 },
13610 ExcerptRange {
13611 context: Point::new(9, 0)..Point::new(10, 4),
13612 primary: None,
13613 },
13614 ],
13615 cx,
13616 );
13617 multibuffer.push_excerpts(
13618 buffer_3.clone(),
13619 [
13620 ExcerptRange {
13621 context: Point::new(0, 0)..Point::new(3, 0),
13622 primary: None,
13623 },
13624 ExcerptRange {
13625 context: Point::new(5, 0)..Point::new(7, 0),
13626 primary: None,
13627 },
13628 ExcerptRange {
13629 context: Point::new(9, 0)..Point::new(10, 4),
13630 primary: None,
13631 },
13632 ],
13633 cx,
13634 );
13635 multibuffer
13636 });
13637
13638 let fs = FakeFs::new(cx.executor());
13639 fs.insert_tree(
13640 "/a",
13641 json!({
13642 "main.rs": sample_text_1,
13643 "other.rs": sample_text_2,
13644 "lib.rs": sample_text_3,
13645 }),
13646 )
13647 .await;
13648 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13649 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13650 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13651 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13652 Editor::new(
13653 EditorMode::Full,
13654 multi_buffer,
13655 Some(project.clone()),
13656 window,
13657 cx,
13658 )
13659 });
13660 let multibuffer_item_id = workspace
13661 .update(cx, |workspace, window, cx| {
13662 assert!(
13663 workspace.active_item(cx).is_none(),
13664 "active item should be None before the first item is added"
13665 );
13666 workspace.add_item_to_active_pane(
13667 Box::new(multi_buffer_editor.clone()),
13668 None,
13669 true,
13670 window,
13671 cx,
13672 );
13673 let active_item = workspace
13674 .active_item(cx)
13675 .expect("should have an active item after adding the multi buffer");
13676 assert!(
13677 !active_item.is_singleton(cx),
13678 "A multi buffer was expected to active after adding"
13679 );
13680 active_item.item_id()
13681 })
13682 .unwrap();
13683 cx.executor().run_until_parked();
13684
13685 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13686 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13687 s.select_ranges(Some(1..2))
13688 });
13689 editor.open_excerpts(&OpenExcerpts, window, cx);
13690 });
13691 cx.executor().run_until_parked();
13692 let first_item_id = workspace
13693 .update(cx, |workspace, window, cx| {
13694 let active_item = workspace
13695 .active_item(cx)
13696 .expect("should have an active item after navigating into the 1st buffer");
13697 let first_item_id = active_item.item_id();
13698 assert_ne!(
13699 first_item_id, multibuffer_item_id,
13700 "Should navigate into the 1st buffer and activate it"
13701 );
13702 assert!(
13703 active_item.is_singleton(cx),
13704 "New active item should be a singleton buffer"
13705 );
13706 assert_eq!(
13707 active_item
13708 .act_as::<Editor>(cx)
13709 .expect("should have navigated into an editor for the 1st buffer")
13710 .read(cx)
13711 .text(cx),
13712 sample_text_1
13713 );
13714
13715 workspace
13716 .go_back(workspace.active_pane().downgrade(), window, cx)
13717 .detach_and_log_err(cx);
13718
13719 first_item_id
13720 })
13721 .unwrap();
13722 cx.executor().run_until_parked();
13723 workspace
13724 .update(cx, |workspace, _, cx| {
13725 let active_item = workspace
13726 .active_item(cx)
13727 .expect("should have an active item after navigating back");
13728 assert_eq!(
13729 active_item.item_id(),
13730 multibuffer_item_id,
13731 "Should navigate back to the multi buffer"
13732 );
13733 assert!(!active_item.is_singleton(cx));
13734 })
13735 .unwrap();
13736
13737 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13738 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13739 s.select_ranges(Some(39..40))
13740 });
13741 editor.open_excerpts(&OpenExcerpts, window, cx);
13742 });
13743 cx.executor().run_until_parked();
13744 let second_item_id = workspace
13745 .update(cx, |workspace, window, cx| {
13746 let active_item = workspace
13747 .active_item(cx)
13748 .expect("should have an active item after navigating into the 2nd buffer");
13749 let second_item_id = active_item.item_id();
13750 assert_ne!(
13751 second_item_id, multibuffer_item_id,
13752 "Should navigate away from the multibuffer"
13753 );
13754 assert_ne!(
13755 second_item_id, first_item_id,
13756 "Should navigate into the 2nd buffer and activate it"
13757 );
13758 assert!(
13759 active_item.is_singleton(cx),
13760 "New active item should be a singleton buffer"
13761 );
13762 assert_eq!(
13763 active_item
13764 .act_as::<Editor>(cx)
13765 .expect("should have navigated into an editor")
13766 .read(cx)
13767 .text(cx),
13768 sample_text_2
13769 );
13770
13771 workspace
13772 .go_back(workspace.active_pane().downgrade(), window, cx)
13773 .detach_and_log_err(cx);
13774
13775 second_item_id
13776 })
13777 .unwrap();
13778 cx.executor().run_until_parked();
13779 workspace
13780 .update(cx, |workspace, _, cx| {
13781 let active_item = workspace
13782 .active_item(cx)
13783 .expect("should have an active item after navigating back from the 2nd buffer");
13784 assert_eq!(
13785 active_item.item_id(),
13786 multibuffer_item_id,
13787 "Should navigate back from the 2nd buffer to the multi buffer"
13788 );
13789 assert!(!active_item.is_singleton(cx));
13790 })
13791 .unwrap();
13792
13793 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13794 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13795 s.select_ranges(Some(70..70))
13796 });
13797 editor.open_excerpts(&OpenExcerpts, window, cx);
13798 });
13799 cx.executor().run_until_parked();
13800 workspace
13801 .update(cx, |workspace, window, cx| {
13802 let active_item = workspace
13803 .active_item(cx)
13804 .expect("should have an active item after navigating into the 3rd buffer");
13805 let third_item_id = active_item.item_id();
13806 assert_ne!(
13807 third_item_id, multibuffer_item_id,
13808 "Should navigate into the 3rd buffer and activate it"
13809 );
13810 assert_ne!(third_item_id, first_item_id);
13811 assert_ne!(third_item_id, second_item_id);
13812 assert!(
13813 active_item.is_singleton(cx),
13814 "New active item should be a singleton buffer"
13815 );
13816 assert_eq!(
13817 active_item
13818 .act_as::<Editor>(cx)
13819 .expect("should have navigated into an editor")
13820 .read(cx)
13821 .text(cx),
13822 sample_text_3
13823 );
13824
13825 workspace
13826 .go_back(workspace.active_pane().downgrade(), window, cx)
13827 .detach_and_log_err(cx);
13828 })
13829 .unwrap();
13830 cx.executor().run_until_parked();
13831 workspace
13832 .update(cx, |workspace, _, cx| {
13833 let active_item = workspace
13834 .active_item(cx)
13835 .expect("should have an active item after navigating back from the 3rd buffer");
13836 assert_eq!(
13837 active_item.item_id(),
13838 multibuffer_item_id,
13839 "Should navigate back from the 3rd buffer to the multi buffer"
13840 );
13841 assert!(!active_item.is_singleton(cx));
13842 })
13843 .unwrap();
13844}
13845
13846#[gpui::test]
13847async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13848 init_test(cx, |_| {});
13849
13850 let mut cx = EditorTestContext::new(cx).await;
13851
13852 let diff_base = r#"
13853 use some::mod;
13854
13855 const A: u32 = 42;
13856
13857 fn main() {
13858 println!("hello");
13859
13860 println!("world");
13861 }
13862 "#
13863 .unindent();
13864
13865 cx.set_state(
13866 &r#"
13867 use some::modified;
13868
13869 ˇ
13870 fn main() {
13871 println!("hello there");
13872
13873 println!("around the");
13874 println!("world");
13875 }
13876 "#
13877 .unindent(),
13878 );
13879
13880 cx.set_head_text(&diff_base);
13881 executor.run_until_parked();
13882
13883 cx.update_editor(|editor, window, cx| {
13884 editor.go_to_next_hunk(&GoToHunk, window, cx);
13885 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13886 });
13887 executor.run_until_parked();
13888 cx.assert_state_with_diff(
13889 r#"
13890 use some::modified;
13891
13892
13893 fn main() {
13894 - println!("hello");
13895 + ˇ println!("hello there");
13896
13897 println!("around the");
13898 println!("world");
13899 }
13900 "#
13901 .unindent(),
13902 );
13903
13904 cx.update_editor(|editor, window, cx| {
13905 for _ in 0..2 {
13906 editor.go_to_next_hunk(&GoToHunk, window, cx);
13907 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13908 }
13909 });
13910 executor.run_until_parked();
13911 cx.assert_state_with_diff(
13912 r#"
13913 - use some::mod;
13914 + ˇuse some::modified;
13915
13916
13917 fn main() {
13918 - println!("hello");
13919 + println!("hello there");
13920
13921 + println!("around the");
13922 println!("world");
13923 }
13924 "#
13925 .unindent(),
13926 );
13927
13928 cx.update_editor(|editor, window, cx| {
13929 editor.go_to_next_hunk(&GoToHunk, window, cx);
13930 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13931 });
13932 executor.run_until_parked();
13933 cx.assert_state_with_diff(
13934 r#"
13935 - use some::mod;
13936 + use some::modified;
13937
13938 - const A: u32 = 42;
13939 ˇ
13940 fn main() {
13941 - println!("hello");
13942 + println!("hello there");
13943
13944 + println!("around the");
13945 println!("world");
13946 }
13947 "#
13948 .unindent(),
13949 );
13950
13951 cx.update_editor(|editor, window, cx| {
13952 editor.cancel(&Cancel, window, cx);
13953 });
13954
13955 cx.assert_state_with_diff(
13956 r#"
13957 use some::modified;
13958
13959 ˇ
13960 fn main() {
13961 println!("hello there");
13962
13963 println!("around the");
13964 println!("world");
13965 }
13966 "#
13967 .unindent(),
13968 );
13969}
13970
13971#[gpui::test]
13972async fn test_diff_base_change_with_expanded_diff_hunks(
13973 executor: BackgroundExecutor,
13974 cx: &mut TestAppContext,
13975) {
13976 init_test(cx, |_| {});
13977
13978 let mut cx = EditorTestContext::new(cx).await;
13979
13980 let diff_base = r#"
13981 use some::mod1;
13982 use some::mod2;
13983
13984 const A: u32 = 42;
13985 const B: u32 = 42;
13986 const C: u32 = 42;
13987
13988 fn main() {
13989 println!("hello");
13990
13991 println!("world");
13992 }
13993 "#
13994 .unindent();
13995
13996 cx.set_state(
13997 &r#"
13998 use some::mod2;
13999
14000 const A: u32 = 42;
14001 const C: u32 = 42;
14002
14003 fn main(ˇ) {
14004 //println!("hello");
14005
14006 println!("world");
14007 //
14008 //
14009 }
14010 "#
14011 .unindent(),
14012 );
14013
14014 cx.set_head_text(&diff_base);
14015 executor.run_until_parked();
14016
14017 cx.update_editor(|editor, window, cx| {
14018 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14019 });
14020 executor.run_until_parked();
14021 cx.assert_state_with_diff(
14022 r#"
14023 - use some::mod1;
14024 use some::mod2;
14025
14026 const A: u32 = 42;
14027 - const B: u32 = 42;
14028 const C: u32 = 42;
14029
14030 fn main(ˇ) {
14031 - println!("hello");
14032 + //println!("hello");
14033
14034 println!("world");
14035 + //
14036 + //
14037 }
14038 "#
14039 .unindent(),
14040 );
14041
14042 cx.set_head_text("new diff base!");
14043 executor.run_until_parked();
14044 cx.assert_state_with_diff(
14045 r#"
14046 - new diff base!
14047 + use some::mod2;
14048 +
14049 + const A: u32 = 42;
14050 + const C: u32 = 42;
14051 +
14052 + fn main(ˇ) {
14053 + //println!("hello");
14054 +
14055 + println!("world");
14056 + //
14057 + //
14058 + }
14059 "#
14060 .unindent(),
14061 );
14062}
14063
14064#[gpui::test]
14065async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14066 init_test(cx, |_| {});
14067
14068 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14069 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14070 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14071 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14072 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14073 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14074
14075 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14076 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14077 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14078
14079 let multi_buffer = cx.new(|cx| {
14080 let mut multibuffer = MultiBuffer::new(ReadWrite);
14081 multibuffer.push_excerpts(
14082 buffer_1.clone(),
14083 [
14084 ExcerptRange {
14085 context: Point::new(0, 0)..Point::new(3, 0),
14086 primary: None,
14087 },
14088 ExcerptRange {
14089 context: Point::new(5, 0)..Point::new(7, 0),
14090 primary: None,
14091 },
14092 ExcerptRange {
14093 context: Point::new(9, 0)..Point::new(10, 3),
14094 primary: None,
14095 },
14096 ],
14097 cx,
14098 );
14099 multibuffer.push_excerpts(
14100 buffer_2.clone(),
14101 [
14102 ExcerptRange {
14103 context: Point::new(0, 0)..Point::new(3, 0),
14104 primary: None,
14105 },
14106 ExcerptRange {
14107 context: Point::new(5, 0)..Point::new(7, 0),
14108 primary: None,
14109 },
14110 ExcerptRange {
14111 context: Point::new(9, 0)..Point::new(10, 3),
14112 primary: None,
14113 },
14114 ],
14115 cx,
14116 );
14117 multibuffer.push_excerpts(
14118 buffer_3.clone(),
14119 [
14120 ExcerptRange {
14121 context: Point::new(0, 0)..Point::new(3, 0),
14122 primary: None,
14123 },
14124 ExcerptRange {
14125 context: Point::new(5, 0)..Point::new(7, 0),
14126 primary: None,
14127 },
14128 ExcerptRange {
14129 context: Point::new(9, 0)..Point::new(10, 3),
14130 primary: None,
14131 },
14132 ],
14133 cx,
14134 );
14135 multibuffer
14136 });
14137
14138 let editor =
14139 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14140 editor
14141 .update(cx, |editor, _window, cx| {
14142 for (buffer, diff_base) in [
14143 (buffer_1.clone(), file_1_old),
14144 (buffer_2.clone(), file_2_old),
14145 (buffer_3.clone(), file_3_old),
14146 ] {
14147 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14148 editor
14149 .buffer
14150 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14151 }
14152 })
14153 .unwrap();
14154
14155 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14156 cx.run_until_parked();
14157
14158 cx.assert_editor_state(
14159 &"
14160 ˇaaa
14161 ccc
14162 ddd
14163
14164 ggg
14165 hhh
14166
14167
14168 lll
14169 mmm
14170 NNN
14171
14172 qqq
14173 rrr
14174
14175 uuu
14176 111
14177 222
14178 333
14179
14180 666
14181 777
14182
14183 000
14184 !!!"
14185 .unindent(),
14186 );
14187
14188 cx.update_editor(|editor, window, cx| {
14189 editor.select_all(&SelectAll, window, cx);
14190 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14191 });
14192 cx.executor().run_until_parked();
14193
14194 cx.assert_state_with_diff(
14195 "
14196 «aaa
14197 - bbb
14198 ccc
14199 ddd
14200
14201 ggg
14202 hhh
14203
14204
14205 lll
14206 mmm
14207 - nnn
14208 + NNN
14209
14210 qqq
14211 rrr
14212
14213 uuu
14214 111
14215 222
14216 333
14217
14218 + 666
14219 777
14220
14221 000
14222 !!!ˇ»"
14223 .unindent(),
14224 );
14225}
14226
14227#[gpui::test]
14228async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14229 init_test(cx, |_| {});
14230
14231 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14232 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14233
14234 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14235 let multi_buffer = cx.new(|cx| {
14236 let mut multibuffer = MultiBuffer::new(ReadWrite);
14237 multibuffer.push_excerpts(
14238 buffer.clone(),
14239 [
14240 ExcerptRange {
14241 context: Point::new(0, 0)..Point::new(2, 0),
14242 primary: None,
14243 },
14244 ExcerptRange {
14245 context: Point::new(4, 0)..Point::new(7, 0),
14246 primary: None,
14247 },
14248 ExcerptRange {
14249 context: Point::new(9, 0)..Point::new(10, 0),
14250 primary: None,
14251 },
14252 ],
14253 cx,
14254 );
14255 multibuffer
14256 });
14257
14258 let editor =
14259 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14260 editor
14261 .update(cx, |editor, _window, cx| {
14262 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14263 editor
14264 .buffer
14265 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14266 })
14267 .unwrap();
14268
14269 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14270 cx.run_until_parked();
14271
14272 cx.update_editor(|editor, window, cx| {
14273 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14274 });
14275 cx.executor().run_until_parked();
14276
14277 // When the start of a hunk coincides with the start of its excerpt,
14278 // the hunk is expanded. When the start of a a hunk is earlier than
14279 // the start of its excerpt, the hunk is not expanded.
14280 cx.assert_state_with_diff(
14281 "
14282 ˇaaa
14283 - bbb
14284 + BBB
14285
14286 - ddd
14287 - eee
14288 + DDD
14289 + EEE
14290 fff
14291
14292 iii
14293 "
14294 .unindent(),
14295 );
14296}
14297
14298#[gpui::test]
14299async fn test_edits_around_expanded_insertion_hunks(
14300 executor: BackgroundExecutor,
14301 cx: &mut TestAppContext,
14302) {
14303 init_test(cx, |_| {});
14304
14305 let mut cx = EditorTestContext::new(cx).await;
14306
14307 let diff_base = r#"
14308 use some::mod1;
14309 use some::mod2;
14310
14311 const A: u32 = 42;
14312
14313 fn main() {
14314 println!("hello");
14315
14316 println!("world");
14317 }
14318 "#
14319 .unindent();
14320 executor.run_until_parked();
14321 cx.set_state(
14322 &r#"
14323 use some::mod1;
14324 use some::mod2;
14325
14326 const A: u32 = 42;
14327 const B: u32 = 42;
14328 const C: u32 = 42;
14329 ˇ
14330
14331 fn main() {
14332 println!("hello");
14333
14334 println!("world");
14335 }
14336 "#
14337 .unindent(),
14338 );
14339
14340 cx.set_head_text(&diff_base);
14341 executor.run_until_parked();
14342
14343 cx.update_editor(|editor, window, cx| {
14344 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14345 });
14346 executor.run_until_parked();
14347
14348 cx.assert_state_with_diff(
14349 r#"
14350 use some::mod1;
14351 use some::mod2;
14352
14353 const A: u32 = 42;
14354 + const B: u32 = 42;
14355 + const C: u32 = 42;
14356 + ˇ
14357
14358 fn main() {
14359 println!("hello");
14360
14361 println!("world");
14362 }
14363 "#
14364 .unindent(),
14365 );
14366
14367 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14368 executor.run_until_parked();
14369
14370 cx.assert_state_with_diff(
14371 r#"
14372 use some::mod1;
14373 use some::mod2;
14374
14375 const A: u32 = 42;
14376 + const B: u32 = 42;
14377 + const C: u32 = 42;
14378 + const D: u32 = 42;
14379 + ˇ
14380
14381 fn main() {
14382 println!("hello");
14383
14384 println!("world");
14385 }
14386 "#
14387 .unindent(),
14388 );
14389
14390 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14391 executor.run_until_parked();
14392
14393 cx.assert_state_with_diff(
14394 r#"
14395 use some::mod1;
14396 use some::mod2;
14397
14398 const A: u32 = 42;
14399 + const B: u32 = 42;
14400 + const C: u32 = 42;
14401 + const D: u32 = 42;
14402 + const E: u32 = 42;
14403 + ˇ
14404
14405 fn main() {
14406 println!("hello");
14407
14408 println!("world");
14409 }
14410 "#
14411 .unindent(),
14412 );
14413
14414 cx.update_editor(|editor, window, cx| {
14415 editor.delete_line(&DeleteLine, window, cx);
14416 });
14417 executor.run_until_parked();
14418
14419 cx.assert_state_with_diff(
14420 r#"
14421 use some::mod1;
14422 use some::mod2;
14423
14424 const A: u32 = 42;
14425 + const B: u32 = 42;
14426 + const C: u32 = 42;
14427 + const D: u32 = 42;
14428 + const E: u32 = 42;
14429 ˇ
14430 fn main() {
14431 println!("hello");
14432
14433 println!("world");
14434 }
14435 "#
14436 .unindent(),
14437 );
14438
14439 cx.update_editor(|editor, window, cx| {
14440 editor.move_up(&MoveUp, window, cx);
14441 editor.delete_line(&DeleteLine, window, cx);
14442 editor.move_up(&MoveUp, window, cx);
14443 editor.delete_line(&DeleteLine, window, cx);
14444 editor.move_up(&MoveUp, window, cx);
14445 editor.delete_line(&DeleteLine, window, cx);
14446 });
14447 executor.run_until_parked();
14448 cx.assert_state_with_diff(
14449 r#"
14450 use some::mod1;
14451 use some::mod2;
14452
14453 const A: u32 = 42;
14454 + const B: u32 = 42;
14455 ˇ
14456 fn main() {
14457 println!("hello");
14458
14459 println!("world");
14460 }
14461 "#
14462 .unindent(),
14463 );
14464
14465 cx.update_editor(|editor, window, cx| {
14466 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14467 editor.delete_line(&DeleteLine, window, cx);
14468 });
14469 executor.run_until_parked();
14470 cx.assert_state_with_diff(
14471 r#"
14472 ˇ
14473 fn main() {
14474 println!("hello");
14475
14476 println!("world");
14477 }
14478 "#
14479 .unindent(),
14480 );
14481}
14482
14483#[gpui::test]
14484async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14485 init_test(cx, |_| {});
14486
14487 let mut cx = EditorTestContext::new(cx).await;
14488 cx.set_head_text(indoc! { "
14489 one
14490 two
14491 three
14492 four
14493 five
14494 "
14495 });
14496 cx.set_state(indoc! { "
14497 one
14498 ˇthree
14499 five
14500 "});
14501 cx.run_until_parked();
14502 cx.update_editor(|editor, window, cx| {
14503 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14504 });
14505 cx.assert_state_with_diff(
14506 indoc! { "
14507 one
14508 - two
14509 ˇthree
14510 - four
14511 five
14512 "}
14513 .to_string(),
14514 );
14515 cx.update_editor(|editor, window, cx| {
14516 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14517 });
14518
14519 cx.assert_state_with_diff(
14520 indoc! { "
14521 one
14522 ˇthree
14523 five
14524 "}
14525 .to_string(),
14526 );
14527
14528 cx.set_state(indoc! { "
14529 one
14530 ˇTWO
14531 three
14532 four
14533 five
14534 "});
14535 cx.run_until_parked();
14536 cx.update_editor(|editor, window, cx| {
14537 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14538 });
14539
14540 cx.assert_state_with_diff(
14541 indoc! { "
14542 one
14543 - two
14544 + ˇTWO
14545 three
14546 four
14547 five
14548 "}
14549 .to_string(),
14550 );
14551 cx.update_editor(|editor, window, cx| {
14552 editor.move_up(&Default::default(), window, cx);
14553 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14554 });
14555 cx.assert_state_with_diff(
14556 indoc! { "
14557 one
14558 ˇTWO
14559 three
14560 four
14561 five
14562 "}
14563 .to_string(),
14564 );
14565}
14566
14567#[gpui::test]
14568async fn test_edits_around_expanded_deletion_hunks(
14569 executor: BackgroundExecutor,
14570 cx: &mut TestAppContext,
14571) {
14572 init_test(cx, |_| {});
14573
14574 let mut cx = EditorTestContext::new(cx).await;
14575
14576 let diff_base = r#"
14577 use some::mod1;
14578 use some::mod2;
14579
14580 const A: u32 = 42;
14581 const B: u32 = 42;
14582 const C: u32 = 42;
14583
14584
14585 fn main() {
14586 println!("hello");
14587
14588 println!("world");
14589 }
14590 "#
14591 .unindent();
14592 executor.run_until_parked();
14593 cx.set_state(
14594 &r#"
14595 use some::mod1;
14596 use some::mod2;
14597
14598 ˇconst B: u32 = 42;
14599 const C: u32 = 42;
14600
14601
14602 fn main() {
14603 println!("hello");
14604
14605 println!("world");
14606 }
14607 "#
14608 .unindent(),
14609 );
14610
14611 cx.set_head_text(&diff_base);
14612 executor.run_until_parked();
14613
14614 cx.update_editor(|editor, window, cx| {
14615 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14616 });
14617 executor.run_until_parked();
14618
14619 cx.assert_state_with_diff(
14620 r#"
14621 use some::mod1;
14622 use some::mod2;
14623
14624 - const A: u32 = 42;
14625 ˇconst B: u32 = 42;
14626 const C: u32 = 42;
14627
14628
14629 fn main() {
14630 println!("hello");
14631
14632 println!("world");
14633 }
14634 "#
14635 .unindent(),
14636 );
14637
14638 cx.update_editor(|editor, window, cx| {
14639 editor.delete_line(&DeleteLine, window, cx);
14640 });
14641 executor.run_until_parked();
14642 cx.assert_state_with_diff(
14643 r#"
14644 use some::mod1;
14645 use some::mod2;
14646
14647 - const A: u32 = 42;
14648 - const B: u32 = 42;
14649 ˇconst C: u32 = 42;
14650
14651
14652 fn main() {
14653 println!("hello");
14654
14655 println!("world");
14656 }
14657 "#
14658 .unindent(),
14659 );
14660
14661 cx.update_editor(|editor, window, cx| {
14662 editor.delete_line(&DeleteLine, window, cx);
14663 });
14664 executor.run_until_parked();
14665 cx.assert_state_with_diff(
14666 r#"
14667 use some::mod1;
14668 use some::mod2;
14669
14670 - const A: u32 = 42;
14671 - const B: u32 = 42;
14672 - const C: u32 = 42;
14673 ˇ
14674
14675 fn main() {
14676 println!("hello");
14677
14678 println!("world");
14679 }
14680 "#
14681 .unindent(),
14682 );
14683
14684 cx.update_editor(|editor, window, cx| {
14685 editor.handle_input("replacement", window, cx);
14686 });
14687 executor.run_until_parked();
14688 cx.assert_state_with_diff(
14689 r#"
14690 use some::mod1;
14691 use some::mod2;
14692
14693 - const A: u32 = 42;
14694 - const B: u32 = 42;
14695 - const C: u32 = 42;
14696 -
14697 + replacementˇ
14698
14699 fn main() {
14700 println!("hello");
14701
14702 println!("world");
14703 }
14704 "#
14705 .unindent(),
14706 );
14707}
14708
14709#[gpui::test]
14710async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14711 init_test(cx, |_| {});
14712
14713 let mut cx = EditorTestContext::new(cx).await;
14714
14715 let base_text = r#"
14716 one
14717 two
14718 three
14719 four
14720 five
14721 "#
14722 .unindent();
14723 executor.run_until_parked();
14724 cx.set_state(
14725 &r#"
14726 one
14727 two
14728 fˇour
14729 five
14730 "#
14731 .unindent(),
14732 );
14733
14734 cx.set_head_text(&base_text);
14735 executor.run_until_parked();
14736
14737 cx.update_editor(|editor, window, cx| {
14738 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14739 });
14740 executor.run_until_parked();
14741
14742 cx.assert_state_with_diff(
14743 r#"
14744 one
14745 two
14746 - three
14747 fˇour
14748 five
14749 "#
14750 .unindent(),
14751 );
14752
14753 cx.update_editor(|editor, window, cx| {
14754 editor.backspace(&Backspace, window, cx);
14755 editor.backspace(&Backspace, window, cx);
14756 });
14757 executor.run_until_parked();
14758 cx.assert_state_with_diff(
14759 r#"
14760 one
14761 two
14762 - threeˇ
14763 - four
14764 + our
14765 five
14766 "#
14767 .unindent(),
14768 );
14769}
14770
14771#[gpui::test]
14772async fn test_edit_after_expanded_modification_hunk(
14773 executor: BackgroundExecutor,
14774 cx: &mut TestAppContext,
14775) {
14776 init_test(cx, |_| {});
14777
14778 let mut cx = EditorTestContext::new(cx).await;
14779
14780 let diff_base = r#"
14781 use some::mod1;
14782 use some::mod2;
14783
14784 const A: u32 = 42;
14785 const B: u32 = 42;
14786 const C: u32 = 42;
14787 const D: u32 = 42;
14788
14789
14790 fn main() {
14791 println!("hello");
14792
14793 println!("world");
14794 }"#
14795 .unindent();
14796
14797 cx.set_state(
14798 &r#"
14799 use some::mod1;
14800 use some::mod2;
14801
14802 const A: u32 = 42;
14803 const B: u32 = 42;
14804 const C: u32 = 43ˇ
14805 const D: u32 = 42;
14806
14807
14808 fn main() {
14809 println!("hello");
14810
14811 println!("world");
14812 }"#
14813 .unindent(),
14814 );
14815
14816 cx.set_head_text(&diff_base);
14817 executor.run_until_parked();
14818 cx.update_editor(|editor, window, cx| {
14819 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14820 });
14821 executor.run_until_parked();
14822
14823 cx.assert_state_with_diff(
14824 r#"
14825 use some::mod1;
14826 use some::mod2;
14827
14828 const A: u32 = 42;
14829 const B: u32 = 42;
14830 - const C: u32 = 42;
14831 + const C: u32 = 43ˇ
14832 const D: u32 = 42;
14833
14834
14835 fn main() {
14836 println!("hello");
14837
14838 println!("world");
14839 }"#
14840 .unindent(),
14841 );
14842
14843 cx.update_editor(|editor, window, cx| {
14844 editor.handle_input("\nnew_line\n", window, cx);
14845 });
14846 executor.run_until_parked();
14847
14848 cx.assert_state_with_diff(
14849 r#"
14850 use some::mod1;
14851 use some::mod2;
14852
14853 const A: u32 = 42;
14854 const B: u32 = 42;
14855 - const C: u32 = 42;
14856 + const C: u32 = 43
14857 + new_line
14858 + ˇ
14859 const D: u32 = 42;
14860
14861
14862 fn main() {
14863 println!("hello");
14864
14865 println!("world");
14866 }"#
14867 .unindent(),
14868 );
14869}
14870
14871#[gpui::test]
14872async fn test_stage_and_unstage_added_file_hunk(
14873 executor: BackgroundExecutor,
14874 cx: &mut TestAppContext,
14875) {
14876 init_test(cx, |_| {});
14877
14878 let mut cx = EditorTestContext::new(cx).await;
14879 cx.update_editor(|editor, _, cx| {
14880 editor.set_expand_all_diff_hunks(cx);
14881 });
14882
14883 let working_copy = r#"
14884 ˇfn main() {
14885 println!("hello, world!");
14886 }
14887 "#
14888 .unindent();
14889
14890 cx.set_state(&working_copy);
14891 executor.run_until_parked();
14892
14893 cx.assert_state_with_diff(
14894 r#"
14895 + ˇfn main() {
14896 + println!("hello, world!");
14897 + }
14898 "#
14899 .unindent(),
14900 );
14901 cx.assert_index_text(None);
14902
14903 cx.update_editor(|editor, window, cx| {
14904 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14905 });
14906 executor.run_until_parked();
14907 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14908 cx.assert_state_with_diff(
14909 r#"
14910 + ˇfn main() {
14911 + println!("hello, world!");
14912 + }
14913 "#
14914 .unindent(),
14915 );
14916
14917 cx.update_editor(|editor, window, cx| {
14918 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14919 });
14920 executor.run_until_parked();
14921 cx.assert_index_text(None);
14922}
14923
14924async fn setup_indent_guides_editor(
14925 text: &str,
14926 cx: &mut TestAppContext,
14927) -> (BufferId, EditorTestContext) {
14928 init_test(cx, |_| {});
14929
14930 let mut cx = EditorTestContext::new(cx).await;
14931
14932 let buffer_id = cx.update_editor(|editor, window, cx| {
14933 editor.set_text(text, window, cx);
14934 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14935
14936 buffer_ids[0]
14937 });
14938
14939 (buffer_id, cx)
14940}
14941
14942fn assert_indent_guides(
14943 range: Range<u32>,
14944 expected: Vec<IndentGuide>,
14945 active_indices: Option<Vec<usize>>,
14946 cx: &mut EditorTestContext,
14947) {
14948 let indent_guides = cx.update_editor(|editor, window, cx| {
14949 let snapshot = editor.snapshot(window, cx).display_snapshot;
14950 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14951 editor,
14952 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14953 true,
14954 &snapshot,
14955 cx,
14956 );
14957
14958 indent_guides.sort_by(|a, b| {
14959 a.depth.cmp(&b.depth).then(
14960 a.start_row
14961 .cmp(&b.start_row)
14962 .then(a.end_row.cmp(&b.end_row)),
14963 )
14964 });
14965 indent_guides
14966 });
14967
14968 if let Some(expected) = active_indices {
14969 let active_indices = cx.update_editor(|editor, window, cx| {
14970 let snapshot = editor.snapshot(window, cx).display_snapshot;
14971 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14972 });
14973
14974 assert_eq!(
14975 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14976 expected,
14977 "Active indent guide indices do not match"
14978 );
14979 }
14980
14981 assert_eq!(indent_guides, expected, "Indent guides do not match");
14982}
14983
14984fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14985 IndentGuide {
14986 buffer_id,
14987 start_row: MultiBufferRow(start_row),
14988 end_row: MultiBufferRow(end_row),
14989 depth,
14990 tab_size: 4,
14991 settings: IndentGuideSettings {
14992 enabled: true,
14993 line_width: 1,
14994 active_line_width: 1,
14995 ..Default::default()
14996 },
14997 }
14998}
14999
15000#[gpui::test]
15001async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15002 let (buffer_id, mut cx) = setup_indent_guides_editor(
15003 &"
15004 fn main() {
15005 let a = 1;
15006 }"
15007 .unindent(),
15008 cx,
15009 )
15010 .await;
15011
15012 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15013}
15014
15015#[gpui::test]
15016async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15017 let (buffer_id, mut cx) = setup_indent_guides_editor(
15018 &"
15019 fn main() {
15020 let a = 1;
15021 let b = 2;
15022 }"
15023 .unindent(),
15024 cx,
15025 )
15026 .await;
15027
15028 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15029}
15030
15031#[gpui::test]
15032async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15033 let (buffer_id, mut cx) = setup_indent_guides_editor(
15034 &"
15035 fn main() {
15036 let a = 1;
15037 if a == 3 {
15038 let b = 2;
15039 } else {
15040 let c = 3;
15041 }
15042 }"
15043 .unindent(),
15044 cx,
15045 )
15046 .await;
15047
15048 assert_indent_guides(
15049 0..8,
15050 vec![
15051 indent_guide(buffer_id, 1, 6, 0),
15052 indent_guide(buffer_id, 3, 3, 1),
15053 indent_guide(buffer_id, 5, 5, 1),
15054 ],
15055 None,
15056 &mut cx,
15057 );
15058}
15059
15060#[gpui::test]
15061async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15062 let (buffer_id, mut cx) = setup_indent_guides_editor(
15063 &"
15064 fn main() {
15065 let a = 1;
15066 let b = 2;
15067 let c = 3;
15068 }"
15069 .unindent(),
15070 cx,
15071 )
15072 .await;
15073
15074 assert_indent_guides(
15075 0..5,
15076 vec![
15077 indent_guide(buffer_id, 1, 3, 0),
15078 indent_guide(buffer_id, 2, 2, 1),
15079 ],
15080 None,
15081 &mut cx,
15082 );
15083}
15084
15085#[gpui::test]
15086async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15087 let (buffer_id, mut cx) = setup_indent_guides_editor(
15088 &"
15089 fn main() {
15090 let a = 1;
15091
15092 let c = 3;
15093 }"
15094 .unindent(),
15095 cx,
15096 )
15097 .await;
15098
15099 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15100}
15101
15102#[gpui::test]
15103async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15104 let (buffer_id, mut cx) = setup_indent_guides_editor(
15105 &"
15106 fn main() {
15107 let a = 1;
15108
15109 let c = 3;
15110
15111 if a == 3 {
15112 let b = 2;
15113 } else {
15114 let c = 3;
15115 }
15116 }"
15117 .unindent(),
15118 cx,
15119 )
15120 .await;
15121
15122 assert_indent_guides(
15123 0..11,
15124 vec![
15125 indent_guide(buffer_id, 1, 9, 0),
15126 indent_guide(buffer_id, 6, 6, 1),
15127 indent_guide(buffer_id, 8, 8, 1),
15128 ],
15129 None,
15130 &mut cx,
15131 );
15132}
15133
15134#[gpui::test]
15135async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15136 let (buffer_id, mut cx) = setup_indent_guides_editor(
15137 &"
15138 fn main() {
15139 let a = 1;
15140
15141 let c = 3;
15142
15143 if a == 3 {
15144 let b = 2;
15145 } else {
15146 let c = 3;
15147 }
15148 }"
15149 .unindent(),
15150 cx,
15151 )
15152 .await;
15153
15154 assert_indent_guides(
15155 1..11,
15156 vec![
15157 indent_guide(buffer_id, 1, 9, 0),
15158 indent_guide(buffer_id, 6, 6, 1),
15159 indent_guide(buffer_id, 8, 8, 1),
15160 ],
15161 None,
15162 &mut cx,
15163 );
15164}
15165
15166#[gpui::test]
15167async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15168 let (buffer_id, mut cx) = setup_indent_guides_editor(
15169 &"
15170 fn main() {
15171 let a = 1;
15172
15173 let c = 3;
15174
15175 if a == 3 {
15176 let b = 2;
15177 } else {
15178 let c = 3;
15179 }
15180 }"
15181 .unindent(),
15182 cx,
15183 )
15184 .await;
15185
15186 assert_indent_guides(
15187 1..10,
15188 vec![
15189 indent_guide(buffer_id, 1, 9, 0),
15190 indent_guide(buffer_id, 6, 6, 1),
15191 indent_guide(buffer_id, 8, 8, 1),
15192 ],
15193 None,
15194 &mut cx,
15195 );
15196}
15197
15198#[gpui::test]
15199async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15200 let (buffer_id, mut cx) = setup_indent_guides_editor(
15201 &"
15202 block1
15203 block2
15204 block3
15205 block4
15206 block2
15207 block1
15208 block1"
15209 .unindent(),
15210 cx,
15211 )
15212 .await;
15213
15214 assert_indent_guides(
15215 1..10,
15216 vec![
15217 indent_guide(buffer_id, 1, 4, 0),
15218 indent_guide(buffer_id, 2, 3, 1),
15219 indent_guide(buffer_id, 3, 3, 2),
15220 ],
15221 None,
15222 &mut cx,
15223 );
15224}
15225
15226#[gpui::test]
15227async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15228 let (buffer_id, mut cx) = setup_indent_guides_editor(
15229 &"
15230 block1
15231 block2
15232 block3
15233
15234 block1
15235 block1"
15236 .unindent(),
15237 cx,
15238 )
15239 .await;
15240
15241 assert_indent_guides(
15242 0..6,
15243 vec![
15244 indent_guide(buffer_id, 1, 2, 0),
15245 indent_guide(buffer_id, 2, 2, 1),
15246 ],
15247 None,
15248 &mut cx,
15249 );
15250}
15251
15252#[gpui::test]
15253async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15254 let (buffer_id, mut cx) = setup_indent_guides_editor(
15255 &"
15256 block1
15257
15258
15259
15260 block2
15261 "
15262 .unindent(),
15263 cx,
15264 )
15265 .await;
15266
15267 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15268}
15269
15270#[gpui::test]
15271async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15272 let (buffer_id, mut cx) = setup_indent_guides_editor(
15273 &"
15274 def a:
15275 \tb = 3
15276 \tif True:
15277 \t\tc = 4
15278 \t\td = 5
15279 \tprint(b)
15280 "
15281 .unindent(),
15282 cx,
15283 )
15284 .await;
15285
15286 assert_indent_guides(
15287 0..6,
15288 vec![
15289 indent_guide(buffer_id, 1, 6, 0),
15290 indent_guide(buffer_id, 3, 4, 1),
15291 ],
15292 None,
15293 &mut cx,
15294 );
15295}
15296
15297#[gpui::test]
15298async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15299 let (buffer_id, mut cx) = setup_indent_guides_editor(
15300 &"
15301 fn main() {
15302 let a = 1;
15303 }"
15304 .unindent(),
15305 cx,
15306 )
15307 .await;
15308
15309 cx.update_editor(|editor, window, cx| {
15310 editor.change_selections(None, window, cx, |s| {
15311 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15312 });
15313 });
15314
15315 assert_indent_guides(
15316 0..3,
15317 vec![indent_guide(buffer_id, 1, 1, 0)],
15318 Some(vec![0]),
15319 &mut cx,
15320 );
15321}
15322
15323#[gpui::test]
15324async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15325 let (buffer_id, mut cx) = setup_indent_guides_editor(
15326 &"
15327 fn main() {
15328 if 1 == 2 {
15329 let a = 1;
15330 }
15331 }"
15332 .unindent(),
15333 cx,
15334 )
15335 .await;
15336
15337 cx.update_editor(|editor, window, cx| {
15338 editor.change_selections(None, window, cx, |s| {
15339 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15340 });
15341 });
15342
15343 assert_indent_guides(
15344 0..4,
15345 vec![
15346 indent_guide(buffer_id, 1, 3, 0),
15347 indent_guide(buffer_id, 2, 2, 1),
15348 ],
15349 Some(vec![1]),
15350 &mut cx,
15351 );
15352
15353 cx.update_editor(|editor, window, cx| {
15354 editor.change_selections(None, window, cx, |s| {
15355 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15356 });
15357 });
15358
15359 assert_indent_guides(
15360 0..4,
15361 vec![
15362 indent_guide(buffer_id, 1, 3, 0),
15363 indent_guide(buffer_id, 2, 2, 1),
15364 ],
15365 Some(vec![1]),
15366 &mut cx,
15367 );
15368
15369 cx.update_editor(|editor, window, cx| {
15370 editor.change_selections(None, window, cx, |s| {
15371 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15372 });
15373 });
15374
15375 assert_indent_guides(
15376 0..4,
15377 vec![
15378 indent_guide(buffer_id, 1, 3, 0),
15379 indent_guide(buffer_id, 2, 2, 1),
15380 ],
15381 Some(vec![0]),
15382 &mut cx,
15383 );
15384}
15385
15386#[gpui::test]
15387async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15388 let (buffer_id, mut cx) = setup_indent_guides_editor(
15389 &"
15390 fn main() {
15391 let a = 1;
15392
15393 let b = 2;
15394 }"
15395 .unindent(),
15396 cx,
15397 )
15398 .await;
15399
15400 cx.update_editor(|editor, window, cx| {
15401 editor.change_selections(None, window, cx, |s| {
15402 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15403 });
15404 });
15405
15406 assert_indent_guides(
15407 0..5,
15408 vec![indent_guide(buffer_id, 1, 3, 0)],
15409 Some(vec![0]),
15410 &mut cx,
15411 );
15412}
15413
15414#[gpui::test]
15415async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15416 let (buffer_id, mut cx) = setup_indent_guides_editor(
15417 &"
15418 def m:
15419 a = 1
15420 pass"
15421 .unindent(),
15422 cx,
15423 )
15424 .await;
15425
15426 cx.update_editor(|editor, window, cx| {
15427 editor.change_selections(None, window, cx, |s| {
15428 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15429 });
15430 });
15431
15432 assert_indent_guides(
15433 0..3,
15434 vec![indent_guide(buffer_id, 1, 2, 0)],
15435 Some(vec![0]),
15436 &mut cx,
15437 );
15438}
15439
15440#[gpui::test]
15441async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15442 init_test(cx, |_| {});
15443 let mut cx = EditorTestContext::new(cx).await;
15444 let text = indoc! {
15445 "
15446 impl A {
15447 fn b() {
15448 0;
15449 3;
15450 5;
15451 6;
15452 7;
15453 }
15454 }
15455 "
15456 };
15457 let base_text = indoc! {
15458 "
15459 impl A {
15460 fn b() {
15461 0;
15462 1;
15463 2;
15464 3;
15465 4;
15466 }
15467 fn c() {
15468 5;
15469 6;
15470 7;
15471 }
15472 }
15473 "
15474 };
15475
15476 cx.update_editor(|editor, window, cx| {
15477 editor.set_text(text, window, cx);
15478
15479 editor.buffer().update(cx, |multibuffer, cx| {
15480 let buffer = multibuffer.as_singleton().unwrap();
15481 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15482
15483 multibuffer.set_all_diff_hunks_expanded(cx);
15484 multibuffer.add_diff(diff, cx);
15485
15486 buffer.read(cx).remote_id()
15487 })
15488 });
15489 cx.run_until_parked();
15490
15491 cx.assert_state_with_diff(
15492 indoc! { "
15493 impl A {
15494 fn b() {
15495 0;
15496 - 1;
15497 - 2;
15498 3;
15499 - 4;
15500 - }
15501 - fn c() {
15502 5;
15503 6;
15504 7;
15505 }
15506 }
15507 ˇ"
15508 }
15509 .to_string(),
15510 );
15511
15512 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15513 editor
15514 .snapshot(window, cx)
15515 .buffer_snapshot
15516 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15517 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15518 .collect::<Vec<_>>()
15519 });
15520 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15521 assert_eq!(
15522 actual_guides,
15523 vec![
15524 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15525 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15526 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15527 ]
15528 );
15529}
15530
15531#[gpui::test]
15532async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15533 init_test(cx, |_| {});
15534 let mut cx = EditorTestContext::new(cx).await;
15535
15536 let diff_base = r#"
15537 a
15538 b
15539 c
15540 "#
15541 .unindent();
15542
15543 cx.set_state(
15544 &r#"
15545 ˇA
15546 b
15547 C
15548 "#
15549 .unindent(),
15550 );
15551 cx.set_head_text(&diff_base);
15552 cx.update_editor(|editor, window, cx| {
15553 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15554 });
15555 executor.run_until_parked();
15556
15557 let both_hunks_expanded = r#"
15558 - a
15559 + ˇA
15560 b
15561 - c
15562 + C
15563 "#
15564 .unindent();
15565
15566 cx.assert_state_with_diff(both_hunks_expanded.clone());
15567
15568 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15569 let snapshot = editor.snapshot(window, cx);
15570 let hunks = editor
15571 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15572 .collect::<Vec<_>>();
15573 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15574 let buffer_id = hunks[0].buffer_id;
15575 hunks
15576 .into_iter()
15577 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15578 .collect::<Vec<_>>()
15579 });
15580 assert_eq!(hunk_ranges.len(), 2);
15581
15582 cx.update_editor(|editor, _, cx| {
15583 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15584 });
15585 executor.run_until_parked();
15586
15587 let second_hunk_expanded = r#"
15588 ˇA
15589 b
15590 - c
15591 + C
15592 "#
15593 .unindent();
15594
15595 cx.assert_state_with_diff(second_hunk_expanded);
15596
15597 cx.update_editor(|editor, _, cx| {
15598 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15599 });
15600 executor.run_until_parked();
15601
15602 cx.assert_state_with_diff(both_hunks_expanded.clone());
15603
15604 cx.update_editor(|editor, _, cx| {
15605 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15606 });
15607 executor.run_until_parked();
15608
15609 let first_hunk_expanded = r#"
15610 - a
15611 + ˇA
15612 b
15613 C
15614 "#
15615 .unindent();
15616
15617 cx.assert_state_with_diff(first_hunk_expanded);
15618
15619 cx.update_editor(|editor, _, cx| {
15620 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15621 });
15622 executor.run_until_parked();
15623
15624 cx.assert_state_with_diff(both_hunks_expanded);
15625
15626 cx.set_state(
15627 &r#"
15628 ˇA
15629 b
15630 "#
15631 .unindent(),
15632 );
15633 cx.run_until_parked();
15634
15635 // TODO this cursor position seems bad
15636 cx.assert_state_with_diff(
15637 r#"
15638 - ˇa
15639 + A
15640 b
15641 "#
15642 .unindent(),
15643 );
15644
15645 cx.update_editor(|editor, window, cx| {
15646 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15647 });
15648
15649 cx.assert_state_with_diff(
15650 r#"
15651 - ˇa
15652 + A
15653 b
15654 - c
15655 "#
15656 .unindent(),
15657 );
15658
15659 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15660 let snapshot = editor.snapshot(window, cx);
15661 let hunks = editor
15662 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15663 .collect::<Vec<_>>();
15664 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15665 let buffer_id = hunks[0].buffer_id;
15666 hunks
15667 .into_iter()
15668 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15669 .collect::<Vec<_>>()
15670 });
15671 assert_eq!(hunk_ranges.len(), 2);
15672
15673 cx.update_editor(|editor, _, cx| {
15674 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15675 });
15676 executor.run_until_parked();
15677
15678 cx.assert_state_with_diff(
15679 r#"
15680 - ˇa
15681 + A
15682 b
15683 "#
15684 .unindent(),
15685 );
15686}
15687
15688#[gpui::test]
15689async fn test_toggle_deletion_hunk_at_start_of_file(
15690 executor: BackgroundExecutor,
15691 cx: &mut TestAppContext,
15692) {
15693 init_test(cx, |_| {});
15694 let mut cx = EditorTestContext::new(cx).await;
15695
15696 let diff_base = r#"
15697 a
15698 b
15699 c
15700 "#
15701 .unindent();
15702
15703 cx.set_state(
15704 &r#"
15705 ˇb
15706 c
15707 "#
15708 .unindent(),
15709 );
15710 cx.set_head_text(&diff_base);
15711 cx.update_editor(|editor, window, cx| {
15712 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15713 });
15714 executor.run_until_parked();
15715
15716 let hunk_expanded = r#"
15717 - a
15718 ˇb
15719 c
15720 "#
15721 .unindent();
15722
15723 cx.assert_state_with_diff(hunk_expanded.clone());
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(), 1);
15738
15739 cx.update_editor(|editor, _, cx| {
15740 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15741 });
15742 executor.run_until_parked();
15743
15744 let hunk_collapsed = r#"
15745 ˇb
15746 c
15747 "#
15748 .unindent();
15749
15750 cx.assert_state_with_diff(hunk_collapsed);
15751
15752 cx.update_editor(|editor, _, cx| {
15753 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15754 });
15755 executor.run_until_parked();
15756
15757 cx.assert_state_with_diff(hunk_expanded.clone());
15758}
15759
15760#[gpui::test]
15761async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15762 init_test(cx, |_| {});
15763
15764 let fs = FakeFs::new(cx.executor());
15765 fs.insert_tree(
15766 path!("/test"),
15767 json!({
15768 ".git": {},
15769 "file-1": "ONE\n",
15770 "file-2": "TWO\n",
15771 "file-3": "THREE\n",
15772 }),
15773 )
15774 .await;
15775
15776 fs.set_head_for_repo(
15777 path!("/test/.git").as_ref(),
15778 &[
15779 ("file-1".into(), "one\n".into()),
15780 ("file-2".into(), "two\n".into()),
15781 ("file-3".into(), "three\n".into()),
15782 ],
15783 );
15784
15785 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15786 let mut buffers = vec![];
15787 for i in 1..=3 {
15788 let buffer = project
15789 .update(cx, |project, cx| {
15790 let path = format!(path!("/test/file-{}"), i);
15791 project.open_local_buffer(path, cx)
15792 })
15793 .await
15794 .unwrap();
15795 buffers.push(buffer);
15796 }
15797
15798 let multibuffer = cx.new(|cx| {
15799 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15800 multibuffer.set_all_diff_hunks_expanded(cx);
15801 for buffer in &buffers {
15802 let snapshot = buffer.read(cx).snapshot();
15803 multibuffer.set_excerpts_for_path(
15804 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15805 buffer.clone(),
15806 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15807 DEFAULT_MULTIBUFFER_CONTEXT,
15808 cx,
15809 );
15810 }
15811 multibuffer
15812 });
15813
15814 let editor = cx.add_window(|window, cx| {
15815 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
15816 });
15817 cx.run_until_parked();
15818
15819 let snapshot = editor
15820 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15821 .unwrap();
15822 let hunks = snapshot
15823 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15824 .map(|hunk| match hunk {
15825 DisplayDiffHunk::Unfolded {
15826 display_row_range, ..
15827 } => display_row_range,
15828 DisplayDiffHunk::Folded { .. } => unreachable!(),
15829 })
15830 .collect::<Vec<_>>();
15831 assert_eq!(
15832 hunks,
15833 [
15834 DisplayRow(2)..DisplayRow(4),
15835 DisplayRow(7)..DisplayRow(9),
15836 DisplayRow(12)..DisplayRow(14),
15837 ]
15838 );
15839}
15840
15841#[gpui::test]
15842async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15843 init_test(cx, |_| {});
15844
15845 let mut cx = EditorTestContext::new(cx).await;
15846 cx.set_head_text(indoc! { "
15847 one
15848 two
15849 three
15850 four
15851 five
15852 "
15853 });
15854 cx.set_index_text(indoc! { "
15855 one
15856 two
15857 three
15858 four
15859 five
15860 "
15861 });
15862 cx.set_state(indoc! {"
15863 one
15864 TWO
15865 ˇTHREE
15866 FOUR
15867 five
15868 "});
15869 cx.run_until_parked();
15870 cx.update_editor(|editor, window, cx| {
15871 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15872 });
15873 cx.run_until_parked();
15874 cx.assert_index_text(Some(indoc! {"
15875 one
15876 TWO
15877 THREE
15878 FOUR
15879 five
15880 "}));
15881 cx.set_state(indoc! { "
15882 one
15883 TWO
15884 ˇTHREE-HUNDRED
15885 FOUR
15886 five
15887 "});
15888 cx.run_until_parked();
15889 cx.update_editor(|editor, window, cx| {
15890 let snapshot = editor.snapshot(window, cx);
15891 let hunks = editor
15892 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15893 .collect::<Vec<_>>();
15894 assert_eq!(hunks.len(), 1);
15895 assert_eq!(
15896 hunks[0].status(),
15897 DiffHunkStatus {
15898 kind: DiffHunkStatusKind::Modified,
15899 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15900 }
15901 );
15902
15903 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15904 });
15905 cx.run_until_parked();
15906 cx.assert_index_text(Some(indoc! {"
15907 one
15908 TWO
15909 THREE-HUNDRED
15910 FOUR
15911 five
15912 "}));
15913}
15914
15915#[gpui::test]
15916fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15917 init_test(cx, |_| {});
15918
15919 let editor = cx.add_window(|window, cx| {
15920 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15921 build_editor(buffer, window, cx)
15922 });
15923
15924 let render_args = Arc::new(Mutex::new(None));
15925 let snapshot = editor
15926 .update(cx, |editor, window, cx| {
15927 let snapshot = editor.buffer().read(cx).snapshot(cx);
15928 let range =
15929 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15930
15931 struct RenderArgs {
15932 row: MultiBufferRow,
15933 folded: bool,
15934 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15935 }
15936
15937 let crease = Crease::inline(
15938 range,
15939 FoldPlaceholder::test(),
15940 {
15941 let toggle_callback = render_args.clone();
15942 move |row, folded, callback, _window, _cx| {
15943 *toggle_callback.lock() = Some(RenderArgs {
15944 row,
15945 folded,
15946 callback,
15947 });
15948 div()
15949 }
15950 },
15951 |_row, _folded, _window, _cx| div(),
15952 );
15953
15954 editor.insert_creases(Some(crease), cx);
15955 let snapshot = editor.snapshot(window, cx);
15956 let _div = snapshot.render_crease_toggle(
15957 MultiBufferRow(1),
15958 false,
15959 cx.entity().clone(),
15960 window,
15961 cx,
15962 );
15963 snapshot
15964 })
15965 .unwrap();
15966
15967 let render_args = render_args.lock().take().unwrap();
15968 assert_eq!(render_args.row, MultiBufferRow(1));
15969 assert!(!render_args.folded);
15970 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15971
15972 cx.update_window(*editor, |_, window, cx| {
15973 (render_args.callback)(true, window, cx)
15974 })
15975 .unwrap();
15976 let snapshot = editor
15977 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15978 .unwrap();
15979 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15980
15981 cx.update_window(*editor, |_, window, cx| {
15982 (render_args.callback)(false, window, cx)
15983 })
15984 .unwrap();
15985 let snapshot = editor
15986 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15987 .unwrap();
15988 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15989}
15990
15991#[gpui::test]
15992async fn test_input_text(cx: &mut TestAppContext) {
15993 init_test(cx, |_| {});
15994 let mut cx = EditorTestContext::new(cx).await;
15995
15996 cx.set_state(
15997 &r#"ˇone
15998 two
15999
16000 three
16001 fourˇ
16002 five
16003
16004 siˇx"#
16005 .unindent(),
16006 );
16007
16008 cx.dispatch_action(HandleInput(String::new()));
16009 cx.assert_editor_state(
16010 &r#"ˇone
16011 two
16012
16013 three
16014 fourˇ
16015 five
16016
16017 siˇx"#
16018 .unindent(),
16019 );
16020
16021 cx.dispatch_action(HandleInput("AAAA".to_string()));
16022 cx.assert_editor_state(
16023 &r#"AAAAˇone
16024 two
16025
16026 three
16027 fourAAAAˇ
16028 five
16029
16030 siAAAAˇx"#
16031 .unindent(),
16032 );
16033}
16034
16035#[gpui::test]
16036async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16037 init_test(cx, |_| {});
16038
16039 let mut cx = EditorTestContext::new(cx).await;
16040 cx.set_state(
16041 r#"let foo = 1;
16042let foo = 2;
16043let foo = 3;
16044let fooˇ = 4;
16045let foo = 5;
16046let foo = 6;
16047let foo = 7;
16048let foo = 8;
16049let foo = 9;
16050let foo = 10;
16051let foo = 11;
16052let foo = 12;
16053let foo = 13;
16054let foo = 14;
16055let foo = 15;"#,
16056 );
16057
16058 cx.update_editor(|e, window, cx| {
16059 assert_eq!(
16060 e.next_scroll_position,
16061 NextScrollCursorCenterTopBottom::Center,
16062 "Default next scroll direction is center",
16063 );
16064
16065 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16066 assert_eq!(
16067 e.next_scroll_position,
16068 NextScrollCursorCenterTopBottom::Top,
16069 "After center, next scroll direction should be top",
16070 );
16071
16072 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16073 assert_eq!(
16074 e.next_scroll_position,
16075 NextScrollCursorCenterTopBottom::Bottom,
16076 "After top, next scroll direction should be bottom",
16077 );
16078
16079 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16080 assert_eq!(
16081 e.next_scroll_position,
16082 NextScrollCursorCenterTopBottom::Center,
16083 "After bottom, scrolling should start over",
16084 );
16085
16086 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16087 assert_eq!(
16088 e.next_scroll_position,
16089 NextScrollCursorCenterTopBottom::Top,
16090 "Scrolling continues if retriggered fast enough"
16091 );
16092 });
16093
16094 cx.executor()
16095 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16096 cx.executor().run_until_parked();
16097 cx.update_editor(|e, _, _| {
16098 assert_eq!(
16099 e.next_scroll_position,
16100 NextScrollCursorCenterTopBottom::Center,
16101 "If scrolling is not triggered fast enough, it should reset"
16102 );
16103 });
16104}
16105
16106#[gpui::test]
16107async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16108 init_test(cx, |_| {});
16109 let mut cx = EditorLspTestContext::new_rust(
16110 lsp::ServerCapabilities {
16111 definition_provider: Some(lsp::OneOf::Left(true)),
16112 references_provider: Some(lsp::OneOf::Left(true)),
16113 ..lsp::ServerCapabilities::default()
16114 },
16115 cx,
16116 )
16117 .await;
16118
16119 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16120 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
16121 move |params, _| async move {
16122 if empty_go_to_definition {
16123 Ok(None)
16124 } else {
16125 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16126 uri: params.text_document_position_params.text_document.uri,
16127 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
16128 })))
16129 }
16130 },
16131 );
16132 let references =
16133 cx.lsp
16134 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
16135 Ok(Some(vec![lsp::Location {
16136 uri: params.text_document_position.text_document.uri,
16137 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16138 }]))
16139 });
16140 (go_to_definition, references)
16141 };
16142
16143 cx.set_state(
16144 &r#"fn one() {
16145 let mut a = ˇtwo();
16146 }
16147
16148 fn two() {}"#
16149 .unindent(),
16150 );
16151 set_up_lsp_handlers(false, &mut cx);
16152 let navigated = cx
16153 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16154 .await
16155 .expect("Failed to navigate to definition");
16156 assert_eq!(
16157 navigated,
16158 Navigated::Yes,
16159 "Should have navigated to definition from the GetDefinition response"
16160 );
16161 cx.assert_editor_state(
16162 &r#"fn one() {
16163 let mut a = two();
16164 }
16165
16166 fn «twoˇ»() {}"#
16167 .unindent(),
16168 );
16169
16170 let editors = cx.update_workspace(|workspace, _, cx| {
16171 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16172 });
16173 cx.update_editor(|_, _, test_editor_cx| {
16174 assert_eq!(
16175 editors.len(),
16176 1,
16177 "Initially, only one, test, editor should be open in the workspace"
16178 );
16179 assert_eq!(
16180 test_editor_cx.entity(),
16181 editors.last().expect("Asserted len is 1").clone()
16182 );
16183 });
16184
16185 set_up_lsp_handlers(true, &mut cx);
16186 let navigated = cx
16187 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16188 .await
16189 .expect("Failed to navigate to lookup references");
16190 assert_eq!(
16191 navigated,
16192 Navigated::Yes,
16193 "Should have navigated to references as a fallback after empty GoToDefinition response"
16194 );
16195 // We should not change the selections in the existing file,
16196 // if opening another milti buffer with the references
16197 cx.assert_editor_state(
16198 &r#"fn one() {
16199 let mut a = two();
16200 }
16201
16202 fn «twoˇ»() {}"#
16203 .unindent(),
16204 );
16205 let editors = cx.update_workspace(|workspace, _, cx| {
16206 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16207 });
16208 cx.update_editor(|_, _, test_editor_cx| {
16209 assert_eq!(
16210 editors.len(),
16211 2,
16212 "After falling back to references search, we open a new editor with the results"
16213 );
16214 let references_fallback_text = editors
16215 .into_iter()
16216 .find(|new_editor| *new_editor != test_editor_cx.entity())
16217 .expect("Should have one non-test editor now")
16218 .read(test_editor_cx)
16219 .text(test_editor_cx);
16220 assert_eq!(
16221 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16222 "Should use the range from the references response and not the GoToDefinition one"
16223 );
16224 });
16225}
16226
16227#[gpui::test]
16228async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16229 init_test(cx, |_| {});
16230
16231 let language = Arc::new(Language::new(
16232 LanguageConfig::default(),
16233 Some(tree_sitter_rust::LANGUAGE.into()),
16234 ));
16235
16236 let text = r#"
16237 #[cfg(test)]
16238 mod tests() {
16239 #[test]
16240 fn runnable_1() {
16241 let a = 1;
16242 }
16243
16244 #[test]
16245 fn runnable_2() {
16246 let a = 1;
16247 let b = 2;
16248 }
16249 }
16250 "#
16251 .unindent();
16252
16253 let fs = FakeFs::new(cx.executor());
16254 fs.insert_file("/file.rs", Default::default()).await;
16255
16256 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16257 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16258 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16259 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16260 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16261
16262 let editor = cx.new_window_entity(|window, cx| {
16263 Editor::new(
16264 EditorMode::Full,
16265 multi_buffer,
16266 Some(project.clone()),
16267 window,
16268 cx,
16269 )
16270 });
16271
16272 editor.update_in(cx, |editor, window, cx| {
16273 let snapshot = editor.buffer().read(cx).snapshot(cx);
16274 editor.tasks.insert(
16275 (buffer.read(cx).remote_id(), 3),
16276 RunnableTasks {
16277 templates: vec![],
16278 offset: snapshot.anchor_before(43),
16279 column: 0,
16280 extra_variables: HashMap::default(),
16281 context_range: BufferOffset(43)..BufferOffset(85),
16282 },
16283 );
16284 editor.tasks.insert(
16285 (buffer.read(cx).remote_id(), 8),
16286 RunnableTasks {
16287 templates: vec![],
16288 offset: snapshot.anchor_before(86),
16289 column: 0,
16290 extra_variables: HashMap::default(),
16291 context_range: BufferOffset(86)..BufferOffset(191),
16292 },
16293 );
16294
16295 // Test finding task when cursor is inside function body
16296 editor.change_selections(None, window, cx, |s| {
16297 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16298 });
16299 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16300 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16301
16302 // Test finding task when cursor is on function name
16303 editor.change_selections(None, window, cx, |s| {
16304 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16305 });
16306 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16307 assert_eq!(row, 8, "Should find task when cursor is on function name");
16308 });
16309}
16310
16311#[gpui::test]
16312async fn test_folding_buffers(cx: &mut TestAppContext) {
16313 init_test(cx, |_| {});
16314
16315 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16316 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16317 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16318
16319 let fs = FakeFs::new(cx.executor());
16320 fs.insert_tree(
16321 path!("/a"),
16322 json!({
16323 "first.rs": sample_text_1,
16324 "second.rs": sample_text_2,
16325 "third.rs": sample_text_3,
16326 }),
16327 )
16328 .await;
16329 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16330 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16331 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16332 let worktree = project.update(cx, |project, cx| {
16333 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16334 assert_eq!(worktrees.len(), 1);
16335 worktrees.pop().unwrap()
16336 });
16337 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16338
16339 let buffer_1 = project
16340 .update(cx, |project, cx| {
16341 project.open_buffer((worktree_id, "first.rs"), cx)
16342 })
16343 .await
16344 .unwrap();
16345 let buffer_2 = project
16346 .update(cx, |project, cx| {
16347 project.open_buffer((worktree_id, "second.rs"), cx)
16348 })
16349 .await
16350 .unwrap();
16351 let buffer_3 = project
16352 .update(cx, |project, cx| {
16353 project.open_buffer((worktree_id, "third.rs"), cx)
16354 })
16355 .await
16356 .unwrap();
16357
16358 let multi_buffer = cx.new(|cx| {
16359 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16360 multi_buffer.push_excerpts(
16361 buffer_1.clone(),
16362 [
16363 ExcerptRange {
16364 context: Point::new(0, 0)..Point::new(3, 0),
16365 primary: None,
16366 },
16367 ExcerptRange {
16368 context: Point::new(5, 0)..Point::new(7, 0),
16369 primary: None,
16370 },
16371 ExcerptRange {
16372 context: Point::new(9, 0)..Point::new(10, 4),
16373 primary: None,
16374 },
16375 ],
16376 cx,
16377 );
16378 multi_buffer.push_excerpts(
16379 buffer_2.clone(),
16380 [
16381 ExcerptRange {
16382 context: Point::new(0, 0)..Point::new(3, 0),
16383 primary: None,
16384 },
16385 ExcerptRange {
16386 context: Point::new(5, 0)..Point::new(7, 0),
16387 primary: None,
16388 },
16389 ExcerptRange {
16390 context: Point::new(9, 0)..Point::new(10, 4),
16391 primary: None,
16392 },
16393 ],
16394 cx,
16395 );
16396 multi_buffer.push_excerpts(
16397 buffer_3.clone(),
16398 [
16399 ExcerptRange {
16400 context: Point::new(0, 0)..Point::new(3, 0),
16401 primary: None,
16402 },
16403 ExcerptRange {
16404 context: Point::new(5, 0)..Point::new(7, 0),
16405 primary: None,
16406 },
16407 ExcerptRange {
16408 context: Point::new(9, 0)..Point::new(10, 4),
16409 primary: None,
16410 },
16411 ],
16412 cx,
16413 );
16414 multi_buffer
16415 });
16416 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16417 Editor::new(
16418 EditorMode::Full,
16419 multi_buffer.clone(),
16420 Some(project.clone()),
16421 window,
16422 cx,
16423 )
16424 });
16425
16426 assert_eq!(
16427 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16428 "\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",
16429 );
16430
16431 multi_buffer_editor.update(cx, |editor, cx| {
16432 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16433 });
16434 assert_eq!(
16435 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16436 "\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",
16437 "After folding the first buffer, its text should not be displayed"
16438 );
16439
16440 multi_buffer_editor.update(cx, |editor, cx| {
16441 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16442 });
16443 assert_eq!(
16444 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16445 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16446 "After folding the second buffer, its text should not be displayed"
16447 );
16448
16449 multi_buffer_editor.update(cx, |editor, cx| {
16450 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16451 });
16452 assert_eq!(
16453 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16454 "\n\n\n\n\n",
16455 "After folding the third buffer, its text should not be displayed"
16456 );
16457
16458 // Emulate selection inside the fold logic, that should work
16459 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16460 editor
16461 .snapshot(window, cx)
16462 .next_line_boundary(Point::new(0, 4));
16463 });
16464
16465 multi_buffer_editor.update(cx, |editor, cx| {
16466 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16467 });
16468 assert_eq!(
16469 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16470 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16471 "After unfolding the second buffer, its text should be displayed"
16472 );
16473
16474 // Typing inside of buffer 1 causes that buffer to be unfolded.
16475 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16476 assert_eq!(
16477 multi_buffer
16478 .read(cx)
16479 .snapshot(cx)
16480 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16481 .collect::<String>(),
16482 "bbbb"
16483 );
16484 editor.change_selections(None, window, cx, |selections| {
16485 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16486 });
16487 editor.handle_input("B", window, cx);
16488 });
16489
16490 assert_eq!(
16491 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16492 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16493 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16494 );
16495
16496 multi_buffer_editor.update(cx, |editor, cx| {
16497 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16498 });
16499 assert_eq!(
16500 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16501 "\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",
16502 "After unfolding the all buffers, all original text should be displayed"
16503 );
16504}
16505
16506#[gpui::test]
16507async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16508 init_test(cx, |_| {});
16509
16510 let sample_text_1 = "1111\n2222\n3333".to_string();
16511 let sample_text_2 = "4444\n5555\n6666".to_string();
16512 let sample_text_3 = "7777\n8888\n9999".to_string();
16513
16514 let fs = FakeFs::new(cx.executor());
16515 fs.insert_tree(
16516 path!("/a"),
16517 json!({
16518 "first.rs": sample_text_1,
16519 "second.rs": sample_text_2,
16520 "third.rs": sample_text_3,
16521 }),
16522 )
16523 .await;
16524 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16525 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16526 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16527 let worktree = project.update(cx, |project, cx| {
16528 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16529 assert_eq!(worktrees.len(), 1);
16530 worktrees.pop().unwrap()
16531 });
16532 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16533
16534 let buffer_1 = project
16535 .update(cx, |project, cx| {
16536 project.open_buffer((worktree_id, "first.rs"), cx)
16537 })
16538 .await
16539 .unwrap();
16540 let buffer_2 = project
16541 .update(cx, |project, cx| {
16542 project.open_buffer((worktree_id, "second.rs"), cx)
16543 })
16544 .await
16545 .unwrap();
16546 let buffer_3 = project
16547 .update(cx, |project, cx| {
16548 project.open_buffer((worktree_id, "third.rs"), cx)
16549 })
16550 .await
16551 .unwrap();
16552
16553 let multi_buffer = cx.new(|cx| {
16554 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16555 multi_buffer.push_excerpts(
16556 buffer_1.clone(),
16557 [ExcerptRange {
16558 context: Point::new(0, 0)..Point::new(3, 0),
16559 primary: None,
16560 }],
16561 cx,
16562 );
16563 multi_buffer.push_excerpts(
16564 buffer_2.clone(),
16565 [ExcerptRange {
16566 context: Point::new(0, 0)..Point::new(3, 0),
16567 primary: None,
16568 }],
16569 cx,
16570 );
16571 multi_buffer.push_excerpts(
16572 buffer_3.clone(),
16573 [ExcerptRange {
16574 context: Point::new(0, 0)..Point::new(3, 0),
16575 primary: None,
16576 }],
16577 cx,
16578 );
16579 multi_buffer
16580 });
16581
16582 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16583 Editor::new(
16584 EditorMode::Full,
16585 multi_buffer,
16586 Some(project.clone()),
16587 window,
16588 cx,
16589 )
16590 });
16591
16592 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16593 assert_eq!(
16594 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16595 full_text,
16596 );
16597
16598 multi_buffer_editor.update(cx, |editor, cx| {
16599 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16600 });
16601 assert_eq!(
16602 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16603 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16604 "After folding the first buffer, its text should not be displayed"
16605 );
16606
16607 multi_buffer_editor.update(cx, |editor, cx| {
16608 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16609 });
16610
16611 assert_eq!(
16612 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16613 "\n\n\n\n\n\n7777\n8888\n9999",
16614 "After folding the second buffer, its text should not be displayed"
16615 );
16616
16617 multi_buffer_editor.update(cx, |editor, cx| {
16618 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16619 });
16620 assert_eq!(
16621 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16622 "\n\n\n\n\n",
16623 "After folding the third buffer, its text should not be displayed"
16624 );
16625
16626 multi_buffer_editor.update(cx, |editor, cx| {
16627 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16628 });
16629 assert_eq!(
16630 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16631 "\n\n\n\n4444\n5555\n6666\n\n",
16632 "After unfolding the second buffer, its text should be displayed"
16633 );
16634
16635 multi_buffer_editor.update(cx, |editor, cx| {
16636 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16637 });
16638 assert_eq!(
16639 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16640 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16641 "After unfolding the first buffer, its text should be displayed"
16642 );
16643
16644 multi_buffer_editor.update(cx, |editor, cx| {
16645 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16646 });
16647 assert_eq!(
16648 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16649 full_text,
16650 "After unfolding all buffers, all original text should be displayed"
16651 );
16652}
16653
16654#[gpui::test]
16655async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16656 init_test(cx, |_| {});
16657
16658 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16659
16660 let fs = FakeFs::new(cx.executor());
16661 fs.insert_tree(
16662 path!("/a"),
16663 json!({
16664 "main.rs": sample_text,
16665 }),
16666 )
16667 .await;
16668 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16669 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16670 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16671 let worktree = project.update(cx, |project, cx| {
16672 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16673 assert_eq!(worktrees.len(), 1);
16674 worktrees.pop().unwrap()
16675 });
16676 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16677
16678 let buffer_1 = project
16679 .update(cx, |project, cx| {
16680 project.open_buffer((worktree_id, "main.rs"), cx)
16681 })
16682 .await
16683 .unwrap();
16684
16685 let multi_buffer = cx.new(|cx| {
16686 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16687 multi_buffer.push_excerpts(
16688 buffer_1.clone(),
16689 [ExcerptRange {
16690 context: Point::new(0, 0)
16691 ..Point::new(
16692 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16693 0,
16694 ),
16695 primary: None,
16696 }],
16697 cx,
16698 );
16699 multi_buffer
16700 });
16701 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16702 Editor::new(
16703 EditorMode::Full,
16704 multi_buffer,
16705 Some(project.clone()),
16706 window,
16707 cx,
16708 )
16709 });
16710
16711 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16712 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16713 enum TestHighlight {}
16714 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16715 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16716 editor.highlight_text::<TestHighlight>(
16717 vec![highlight_range.clone()],
16718 HighlightStyle::color(Hsla::green()),
16719 cx,
16720 );
16721 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16722 });
16723
16724 let full_text = format!("\n\n{sample_text}");
16725 assert_eq!(
16726 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16727 full_text,
16728 );
16729}
16730
16731#[gpui::test]
16732async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16733 init_test(cx, |_| {});
16734 cx.update(|cx| {
16735 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16736 "keymaps/default-linux.json",
16737 cx,
16738 )
16739 .unwrap();
16740 cx.bind_keys(default_key_bindings);
16741 });
16742
16743 let (editor, cx) = cx.add_window_view(|window, cx| {
16744 let multi_buffer = MultiBuffer::build_multi(
16745 [
16746 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16747 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16748 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16749 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16750 ],
16751 cx,
16752 );
16753 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16754
16755 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16756 // fold all but the second buffer, so that we test navigating between two
16757 // adjacent folded buffers, as well as folded buffers at the start and
16758 // end the multibuffer
16759 editor.fold_buffer(buffer_ids[0], cx);
16760 editor.fold_buffer(buffer_ids[2], cx);
16761 editor.fold_buffer(buffer_ids[3], cx);
16762
16763 editor
16764 });
16765 cx.simulate_resize(size(px(1000.), px(1000.)));
16766
16767 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16768 cx.assert_excerpts_with_selections(indoc! {"
16769 [EXCERPT]
16770 ˇ[FOLDED]
16771 [EXCERPT]
16772 a1
16773 b1
16774 [EXCERPT]
16775 [FOLDED]
16776 [EXCERPT]
16777 [FOLDED]
16778 "
16779 });
16780 cx.simulate_keystroke("down");
16781 cx.assert_excerpts_with_selections(indoc! {"
16782 [EXCERPT]
16783 [FOLDED]
16784 [EXCERPT]
16785 ˇa1
16786 b1
16787 [EXCERPT]
16788 [FOLDED]
16789 [EXCERPT]
16790 [FOLDED]
16791 "
16792 });
16793 cx.simulate_keystroke("down");
16794 cx.assert_excerpts_with_selections(indoc! {"
16795 [EXCERPT]
16796 [FOLDED]
16797 [EXCERPT]
16798 a1
16799 ˇb1
16800 [EXCERPT]
16801 [FOLDED]
16802 [EXCERPT]
16803 [FOLDED]
16804 "
16805 });
16806 cx.simulate_keystroke("down");
16807 cx.assert_excerpts_with_selections(indoc! {"
16808 [EXCERPT]
16809 [FOLDED]
16810 [EXCERPT]
16811 a1
16812 b1
16813 ˇ[EXCERPT]
16814 [FOLDED]
16815 [EXCERPT]
16816 [FOLDED]
16817 "
16818 });
16819 cx.simulate_keystroke("down");
16820 cx.assert_excerpts_with_selections(indoc! {"
16821 [EXCERPT]
16822 [FOLDED]
16823 [EXCERPT]
16824 a1
16825 b1
16826 [EXCERPT]
16827 ˇ[FOLDED]
16828 [EXCERPT]
16829 [FOLDED]
16830 "
16831 });
16832 for _ in 0..5 {
16833 cx.simulate_keystroke("down");
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 }
16847
16848 cx.simulate_keystroke("up");
16849 cx.assert_excerpts_with_selections(indoc! {"
16850 [EXCERPT]
16851 [FOLDED]
16852 [EXCERPT]
16853 a1
16854 b1
16855 [EXCERPT]
16856 ˇ[FOLDED]
16857 [EXCERPT]
16858 [FOLDED]
16859 "
16860 });
16861 cx.simulate_keystroke("up");
16862 cx.assert_excerpts_with_selections(indoc! {"
16863 [EXCERPT]
16864 [FOLDED]
16865 [EXCERPT]
16866 a1
16867 b1
16868 ˇ[EXCERPT]
16869 [FOLDED]
16870 [EXCERPT]
16871 [FOLDED]
16872 "
16873 });
16874 cx.simulate_keystroke("up");
16875 cx.assert_excerpts_with_selections(indoc! {"
16876 [EXCERPT]
16877 [FOLDED]
16878 [EXCERPT]
16879 a1
16880 ˇb1
16881 [EXCERPT]
16882 [FOLDED]
16883 [EXCERPT]
16884 [FOLDED]
16885 "
16886 });
16887 cx.simulate_keystroke("up");
16888 cx.assert_excerpts_with_selections(indoc! {"
16889 [EXCERPT]
16890 [FOLDED]
16891 [EXCERPT]
16892 ˇa1
16893 b1
16894 [EXCERPT]
16895 [FOLDED]
16896 [EXCERPT]
16897 [FOLDED]
16898 "
16899 });
16900 for _ in 0..5 {
16901 cx.simulate_keystroke("up");
16902 cx.assert_excerpts_with_selections(indoc! {"
16903 [EXCERPT]
16904 ˇ[FOLDED]
16905 [EXCERPT]
16906 a1
16907 b1
16908 [EXCERPT]
16909 [FOLDED]
16910 [EXCERPT]
16911 [FOLDED]
16912 "
16913 });
16914 }
16915}
16916
16917#[gpui::test]
16918async fn test_inline_completion_text(cx: &mut TestAppContext) {
16919 init_test(cx, |_| {});
16920
16921 // Simple insertion
16922 assert_highlighted_edits(
16923 "Hello, world!",
16924 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16925 true,
16926 cx,
16927 |highlighted_edits, cx| {
16928 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16929 assert_eq!(highlighted_edits.highlights.len(), 1);
16930 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16931 assert_eq!(
16932 highlighted_edits.highlights[0].1.background_color,
16933 Some(cx.theme().status().created_background)
16934 );
16935 },
16936 )
16937 .await;
16938
16939 // Replacement
16940 assert_highlighted_edits(
16941 "This is a test.",
16942 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16943 false,
16944 cx,
16945 |highlighted_edits, cx| {
16946 assert_eq!(highlighted_edits.text, "That is a test.");
16947 assert_eq!(highlighted_edits.highlights.len(), 1);
16948 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16949 assert_eq!(
16950 highlighted_edits.highlights[0].1.background_color,
16951 Some(cx.theme().status().created_background)
16952 );
16953 },
16954 )
16955 .await;
16956
16957 // Multiple edits
16958 assert_highlighted_edits(
16959 "Hello, world!",
16960 vec![
16961 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16962 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16963 ],
16964 false,
16965 cx,
16966 |highlighted_edits, cx| {
16967 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16968 assert_eq!(highlighted_edits.highlights.len(), 2);
16969 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16970 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16971 assert_eq!(
16972 highlighted_edits.highlights[0].1.background_color,
16973 Some(cx.theme().status().created_background)
16974 );
16975 assert_eq!(
16976 highlighted_edits.highlights[1].1.background_color,
16977 Some(cx.theme().status().created_background)
16978 );
16979 },
16980 )
16981 .await;
16982
16983 // Multiple lines with edits
16984 assert_highlighted_edits(
16985 "First line\nSecond line\nThird line\nFourth line",
16986 vec![
16987 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16988 (
16989 Point::new(2, 0)..Point::new(2, 10),
16990 "New third line".to_string(),
16991 ),
16992 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16993 ],
16994 false,
16995 cx,
16996 |highlighted_edits, cx| {
16997 assert_eq!(
16998 highlighted_edits.text,
16999 "Second modified\nNew third line\nFourth updated line"
17000 );
17001 assert_eq!(highlighted_edits.highlights.len(), 3);
17002 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17003 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17004 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17005 for highlight in &highlighted_edits.highlights {
17006 assert_eq!(
17007 highlight.1.background_color,
17008 Some(cx.theme().status().created_background)
17009 );
17010 }
17011 },
17012 )
17013 .await;
17014}
17015
17016#[gpui::test]
17017async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17018 init_test(cx, |_| {});
17019
17020 // Deletion
17021 assert_highlighted_edits(
17022 "Hello, world!",
17023 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17024 true,
17025 cx,
17026 |highlighted_edits, cx| {
17027 assert_eq!(highlighted_edits.text, "Hello, world!");
17028 assert_eq!(highlighted_edits.highlights.len(), 1);
17029 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17030 assert_eq!(
17031 highlighted_edits.highlights[0].1.background_color,
17032 Some(cx.theme().status().deleted_background)
17033 );
17034 },
17035 )
17036 .await;
17037
17038 // Insertion
17039 assert_highlighted_edits(
17040 "Hello, world!",
17041 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17042 true,
17043 cx,
17044 |highlighted_edits, cx| {
17045 assert_eq!(highlighted_edits.highlights.len(), 1);
17046 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17047 assert_eq!(
17048 highlighted_edits.highlights[0].1.background_color,
17049 Some(cx.theme().status().created_background)
17050 );
17051 },
17052 )
17053 .await;
17054}
17055
17056async fn assert_highlighted_edits(
17057 text: &str,
17058 edits: Vec<(Range<Point>, String)>,
17059 include_deletions: bool,
17060 cx: &mut TestAppContext,
17061 assertion_fn: impl Fn(HighlightedText, &App),
17062) {
17063 let window = cx.add_window(|window, cx| {
17064 let buffer = MultiBuffer::build_simple(text, cx);
17065 Editor::new(EditorMode::Full, buffer, None, window, cx)
17066 });
17067 let cx = &mut VisualTestContext::from_window(*window, cx);
17068
17069 let (buffer, snapshot) = window
17070 .update(cx, |editor, _window, cx| {
17071 (
17072 editor.buffer().clone(),
17073 editor.buffer().read(cx).snapshot(cx),
17074 )
17075 })
17076 .unwrap();
17077
17078 let edits = edits
17079 .into_iter()
17080 .map(|(range, edit)| {
17081 (
17082 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17083 edit,
17084 )
17085 })
17086 .collect::<Vec<_>>();
17087
17088 let text_anchor_edits = edits
17089 .clone()
17090 .into_iter()
17091 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17092 .collect::<Vec<_>>();
17093
17094 let edit_preview = window
17095 .update(cx, |_, _window, cx| {
17096 buffer
17097 .read(cx)
17098 .as_singleton()
17099 .unwrap()
17100 .read(cx)
17101 .preview_edits(text_anchor_edits.into(), cx)
17102 })
17103 .unwrap()
17104 .await;
17105
17106 cx.update(|_window, cx| {
17107 let highlighted_edits = inline_completion_edit_text(
17108 &snapshot.as_singleton().unwrap().2,
17109 &edits,
17110 &edit_preview,
17111 include_deletions,
17112 cx,
17113 );
17114 assertion_fn(highlighted_edits, cx)
17115 });
17116}
17117
17118#[track_caller]
17119fn assert_breakpoint(
17120 breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
17121 path: &Arc<Path>,
17122 expected: Vec<(u32, BreakpointKind)>,
17123) {
17124 if expected.len() == 0usize {
17125 assert!(!breakpoints.contains_key(path));
17126 } else {
17127 let mut breakpoint = breakpoints
17128 .get(path)
17129 .unwrap()
17130 .into_iter()
17131 .map(|breakpoint| (breakpoint.position, breakpoint.kind.clone()))
17132 .collect::<Vec<_>>();
17133
17134 breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17135
17136 assert_eq!(expected, breakpoint);
17137 }
17138}
17139
17140fn add_log_breakpoint_at_cursor(
17141 editor: &mut Editor,
17142 log_message: &str,
17143 window: &mut Window,
17144 cx: &mut Context<Editor>,
17145) {
17146 let (anchor, bp) = editor
17147 .breakpoint_at_cursor_head(window, cx)
17148 .unwrap_or_else(|| {
17149 let cursor_position: Point = editor.selections.newest(cx).head();
17150
17151 let breakpoint_position = editor
17152 .snapshot(window, cx)
17153 .display_snapshot
17154 .buffer_snapshot
17155 .anchor_before(Point::new(cursor_position.row, 0));
17156
17157 let kind = BreakpointKind::Log(Arc::from(log_message));
17158
17159 (breakpoint_position, Breakpoint { kind })
17160 });
17161
17162 editor.edit_breakpoint_at_anchor(
17163 anchor,
17164 bp.kind,
17165 BreakpointEditAction::EditLogMessage(log_message.into()),
17166 cx,
17167 );
17168}
17169
17170#[gpui::test]
17171async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
17172 init_test(cx, |_| {});
17173
17174 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17175 let fs = FakeFs::new(cx.executor());
17176 fs.insert_tree(
17177 path!("/a"),
17178 json!({
17179 "main.rs": sample_text,
17180 }),
17181 )
17182 .await;
17183 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17184 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17185 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17186
17187 let fs = FakeFs::new(cx.executor());
17188 fs.insert_tree(
17189 path!("/a"),
17190 json!({
17191 "main.rs": sample_text,
17192 }),
17193 )
17194 .await;
17195 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17196 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17197 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17198 let worktree_id = workspace
17199 .update(cx, |workspace, _window, cx| {
17200 workspace.project().update(cx, |project, cx| {
17201 project.worktrees(cx).next().unwrap().read(cx).id()
17202 })
17203 })
17204 .unwrap();
17205
17206 let buffer = project
17207 .update(cx, |project, cx| {
17208 project.open_buffer((worktree_id, "main.rs"), cx)
17209 })
17210 .await
17211 .unwrap();
17212
17213 let (editor, cx) = cx.add_window_view(|window, cx| {
17214 Editor::new(
17215 EditorMode::Full,
17216 MultiBuffer::build_from_buffer(buffer, cx),
17217 Some(project.clone()),
17218 window,
17219 cx,
17220 )
17221 });
17222
17223 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17224 let abs_path = project.read_with(cx, |project, cx| {
17225 project
17226 .absolute_path(&project_path, cx)
17227 .map(|path_buf| Arc::from(path_buf.to_owned()))
17228 .unwrap()
17229 });
17230
17231 // assert we can add breakpoint on the first line
17232 editor.update_in(cx, |editor, window, cx| {
17233 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17234 editor.move_to_end(&MoveToEnd, window, cx);
17235 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17236 });
17237
17238 let breakpoints = editor.update(cx, |editor, cx| {
17239 editor
17240 .breakpoint_store()
17241 .as_ref()
17242 .unwrap()
17243 .read(cx)
17244 .all_breakpoints(cx)
17245 .clone()
17246 });
17247
17248 assert_eq!(1, breakpoints.len());
17249 assert_breakpoint(
17250 &breakpoints,
17251 &abs_path,
17252 vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17253 );
17254
17255 editor.update_in(cx, |editor, window, cx| {
17256 editor.move_to_beginning(&MoveToBeginning, window, cx);
17257 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17258 });
17259
17260 let breakpoints = editor.update(cx, |editor, cx| {
17261 editor
17262 .breakpoint_store()
17263 .as_ref()
17264 .unwrap()
17265 .read(cx)
17266 .all_breakpoints(cx)
17267 .clone()
17268 });
17269
17270 assert_eq!(1, breakpoints.len());
17271 assert_breakpoint(&breakpoints, &abs_path, vec![(3, BreakpointKind::Standard)]);
17272
17273 editor.update_in(cx, |editor, window, cx| {
17274 editor.move_to_end(&MoveToEnd, window, cx);
17275 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17276 });
17277
17278 let breakpoints = editor.update(cx, |editor, cx| {
17279 editor
17280 .breakpoint_store()
17281 .as_ref()
17282 .unwrap()
17283 .read(cx)
17284 .all_breakpoints(cx)
17285 .clone()
17286 });
17287
17288 assert_eq!(0, breakpoints.len());
17289 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17290}
17291
17292#[gpui::test]
17293async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
17294 init_test(cx, |_| {});
17295
17296 let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17297
17298 let fs = FakeFs::new(cx.executor());
17299 fs.insert_tree(
17300 path!("/a"),
17301 json!({
17302 "main.rs": sample_text,
17303 }),
17304 )
17305 .await;
17306 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17307 let (workspace, cx) =
17308 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
17309
17310 let worktree_id = workspace.update(cx, |workspace, cx| {
17311 workspace.project().update(cx, |project, cx| {
17312 project.worktrees(cx).next().unwrap().read(cx).id()
17313 })
17314 });
17315
17316 let buffer = project
17317 .update(cx, |project, cx| {
17318 project.open_buffer((worktree_id, "main.rs"), cx)
17319 })
17320 .await
17321 .unwrap();
17322
17323 let (editor, cx) = cx.add_window_view(|window, cx| {
17324 Editor::new(
17325 EditorMode::Full,
17326 MultiBuffer::build_from_buffer(buffer, cx),
17327 Some(project.clone()),
17328 window,
17329 cx,
17330 )
17331 });
17332
17333 let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17334 let abs_path = project.read_with(cx, |project, cx| {
17335 project
17336 .absolute_path(&project_path, cx)
17337 .map(|path_buf| Arc::from(path_buf.to_owned()))
17338 .unwrap()
17339 });
17340
17341 editor.update_in(cx, |editor, window, cx| {
17342 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17343 });
17344
17345 let breakpoints = editor.update(cx, |editor, cx| {
17346 editor
17347 .breakpoint_store()
17348 .as_ref()
17349 .unwrap()
17350 .read(cx)
17351 .all_breakpoints(cx)
17352 .clone()
17353 });
17354
17355 assert_breakpoint(
17356 &breakpoints,
17357 &abs_path,
17358 vec![(0, BreakpointKind::Log("hello world".into()))],
17359 );
17360
17361 // Removing a log message from a log breakpoint should remove it
17362 editor.update_in(cx, |editor, window, cx| {
17363 add_log_breakpoint_at_cursor(editor, "", window, cx);
17364 });
17365
17366 let breakpoints = editor.update(cx, |editor, cx| {
17367 editor
17368 .breakpoint_store()
17369 .as_ref()
17370 .unwrap()
17371 .read(cx)
17372 .all_breakpoints(cx)
17373 .clone()
17374 });
17375
17376 assert_breakpoint(&breakpoints, &abs_path, vec![]);
17377
17378 editor.update_in(cx, |editor, window, cx| {
17379 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17380 editor.move_to_end(&MoveToEnd, window, cx);
17381 editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17382 // Not adding a log message to a standard breakpoint shouldn't remove it
17383 add_log_breakpoint_at_cursor(editor, "", window, cx);
17384 });
17385
17386 let breakpoints = editor.update(cx, |editor, cx| {
17387 editor
17388 .breakpoint_store()
17389 .as_ref()
17390 .unwrap()
17391 .read(cx)
17392 .all_breakpoints(cx)
17393 .clone()
17394 });
17395
17396 assert_breakpoint(
17397 &breakpoints,
17398 &abs_path,
17399 vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17400 );
17401
17402 editor.update_in(cx, |editor, window, cx| {
17403 add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17404 });
17405
17406 let breakpoints = editor.update(cx, |editor, cx| {
17407 editor
17408 .breakpoint_store()
17409 .as_ref()
17410 .unwrap()
17411 .read(cx)
17412 .all_breakpoints(cx)
17413 .clone()
17414 });
17415
17416 assert_breakpoint(
17417 &breakpoints,
17418 &abs_path,
17419 vec![
17420 (0, BreakpointKind::Standard),
17421 (3, BreakpointKind::Log("hello world".into())),
17422 ],
17423 );
17424
17425 editor.update_in(cx, |editor, window, cx| {
17426 add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
17427 });
17428
17429 let breakpoints = editor.update(cx, |editor, cx| {
17430 editor
17431 .breakpoint_store()
17432 .as_ref()
17433 .unwrap()
17434 .read(cx)
17435 .all_breakpoints(cx)
17436 .clone()
17437 });
17438
17439 assert_breakpoint(
17440 &breakpoints,
17441 &abs_path,
17442 vec![
17443 (0, BreakpointKind::Standard),
17444 (3, BreakpointKind::Log("hello Earth !!".into())),
17445 ],
17446 );
17447}
17448
17449#[gpui::test]
17450async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17451 init_test(cx, |_| {});
17452 let capabilities = lsp::ServerCapabilities {
17453 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17454 prepare_provider: Some(true),
17455 work_done_progress_options: Default::default(),
17456 })),
17457 ..Default::default()
17458 };
17459 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17460
17461 cx.set_state(indoc! {"
17462 struct Fˇoo {}
17463 "});
17464
17465 cx.update_editor(|editor, _, cx| {
17466 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17467 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17468 editor.highlight_background::<DocumentHighlightRead>(
17469 &[highlight_range],
17470 |c| c.editor_document_highlight_read_background,
17471 cx,
17472 );
17473 });
17474
17475 let mut prepare_rename_handler =
17476 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17477 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17478 start: lsp::Position {
17479 line: 0,
17480 character: 7,
17481 },
17482 end: lsp::Position {
17483 line: 0,
17484 character: 10,
17485 },
17486 })))
17487 });
17488 let prepare_rename_task = cx
17489 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17490 .expect("Prepare rename was not started");
17491 prepare_rename_handler.next().await.unwrap();
17492 prepare_rename_task.await.expect("Prepare rename failed");
17493
17494 let mut rename_handler =
17495 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17496 let edit = lsp::TextEdit {
17497 range: lsp::Range {
17498 start: lsp::Position {
17499 line: 0,
17500 character: 7,
17501 },
17502 end: lsp::Position {
17503 line: 0,
17504 character: 10,
17505 },
17506 },
17507 new_text: "FooRenamed".to_string(),
17508 };
17509 Ok(Some(lsp::WorkspaceEdit::new(
17510 // Specify the same edit twice
17511 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17512 )))
17513 });
17514 let rename_task = cx
17515 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17516 .expect("Confirm rename was not started");
17517 rename_handler.next().await.unwrap();
17518 rename_task.await.expect("Confirm rename failed");
17519 cx.run_until_parked();
17520
17521 // Despite two edits, only one is actually applied as those are identical
17522 cx.assert_editor_state(indoc! {"
17523 struct FooRenamedˇ {}
17524 "});
17525}
17526
17527#[gpui::test]
17528async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17529 init_test(cx, |_| {});
17530 // These capabilities indicate that the server does not support prepare rename.
17531 let capabilities = lsp::ServerCapabilities {
17532 rename_provider: Some(lsp::OneOf::Left(true)),
17533 ..Default::default()
17534 };
17535 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17536
17537 cx.set_state(indoc! {"
17538 struct Fˇoo {}
17539 "});
17540
17541 cx.update_editor(|editor, _window, cx| {
17542 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17543 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17544 editor.highlight_background::<DocumentHighlightRead>(
17545 &[highlight_range],
17546 |c| c.editor_document_highlight_read_background,
17547 cx,
17548 );
17549 });
17550
17551 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17552 .expect("Prepare rename was not started")
17553 .await
17554 .expect("Prepare rename failed");
17555
17556 let mut rename_handler =
17557 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17558 let edit = lsp::TextEdit {
17559 range: lsp::Range {
17560 start: lsp::Position {
17561 line: 0,
17562 character: 7,
17563 },
17564 end: lsp::Position {
17565 line: 0,
17566 character: 10,
17567 },
17568 },
17569 new_text: "FooRenamed".to_string(),
17570 };
17571 Ok(Some(lsp::WorkspaceEdit::new(
17572 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17573 )))
17574 });
17575 let rename_task = cx
17576 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17577 .expect("Confirm rename was not started");
17578 rename_handler.next().await.unwrap();
17579 rename_task.await.expect("Confirm rename failed");
17580 cx.run_until_parked();
17581
17582 // Correct range is renamed, as `surrounding_word` is used to find it.
17583 cx.assert_editor_state(indoc! {"
17584 struct FooRenamedˇ {}
17585 "});
17586}
17587
17588#[gpui::test]
17589async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17590 init_test(cx, |_| {});
17591 let mut cx = EditorTestContext::new(cx).await;
17592
17593 let language = Arc::new(
17594 Language::new(
17595 LanguageConfig::default(),
17596 Some(tree_sitter_html::LANGUAGE.into()),
17597 )
17598 .with_brackets_query(
17599 r#"
17600 ("<" @open "/>" @close)
17601 ("</" @open ">" @close)
17602 ("<" @open ">" @close)
17603 ("\"" @open "\"" @close)
17604 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17605 "#,
17606 )
17607 .unwrap(),
17608 );
17609 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17610
17611 cx.set_state(indoc! {"
17612 <span>ˇ</span>
17613 "});
17614 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17615 cx.assert_editor_state(indoc! {"
17616 <span>
17617 ˇ
17618 </span>
17619 "});
17620
17621 cx.set_state(indoc! {"
17622 <span><span></span>ˇ</span>
17623 "});
17624 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17625 cx.assert_editor_state(indoc! {"
17626 <span><span></span>
17627 ˇ</span>
17628 "});
17629
17630 cx.set_state(indoc! {"
17631 <span>ˇ
17632 </span>
17633 "});
17634 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17635 cx.assert_editor_state(indoc! {"
17636 <span>
17637 ˇ
17638 </span>
17639 "});
17640}
17641
17642#[gpui::test(iterations = 10)]
17643async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
17644 init_test(cx, |_| {});
17645
17646 let fs = FakeFs::new(cx.executor());
17647 fs.insert_tree(
17648 path!("/dir"),
17649 json!({
17650 "a.ts": "a",
17651 }),
17652 )
17653 .await;
17654
17655 let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
17656 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17657 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17658
17659 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17660 language_registry.add(Arc::new(Language::new(
17661 LanguageConfig {
17662 name: "TypeScript".into(),
17663 matcher: LanguageMatcher {
17664 path_suffixes: vec!["ts".to_string()],
17665 ..Default::default()
17666 },
17667 ..Default::default()
17668 },
17669 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17670 )));
17671 let mut fake_language_servers = language_registry.register_fake_lsp(
17672 "TypeScript",
17673 FakeLspAdapter {
17674 capabilities: lsp::ServerCapabilities {
17675 code_lens_provider: Some(lsp::CodeLensOptions {
17676 resolve_provider: Some(true),
17677 }),
17678 execute_command_provider: Some(lsp::ExecuteCommandOptions {
17679 commands: vec!["_the/command".to_string()],
17680 ..lsp::ExecuteCommandOptions::default()
17681 }),
17682 ..lsp::ServerCapabilities::default()
17683 },
17684 ..FakeLspAdapter::default()
17685 },
17686 );
17687
17688 let (buffer, _handle) = project
17689 .update(cx, |p, cx| {
17690 p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
17691 })
17692 .await
17693 .unwrap();
17694 cx.executor().run_until_parked();
17695
17696 let fake_server = fake_language_servers.next().await.unwrap();
17697
17698 let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
17699 let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
17700 drop(buffer_snapshot);
17701 let actions = cx
17702 .update_window(*workspace, |_, window, cx| {
17703 project.code_actions(&buffer, anchor..anchor, window, cx)
17704 })
17705 .unwrap();
17706
17707 fake_server
17708 .handle_request::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
17709 Ok(Some(vec![
17710 lsp::CodeLens {
17711 range: lsp::Range::default(),
17712 command: Some(lsp::Command {
17713 title: "Code lens command".to_owned(),
17714 command: "_the/command".to_owned(),
17715 arguments: None,
17716 }),
17717 data: None,
17718 },
17719 lsp::CodeLens {
17720 range: lsp::Range::default(),
17721 command: Some(lsp::Command {
17722 title: "Command not in capabilities".to_owned(),
17723 command: "not in capabilities".to_owned(),
17724 arguments: None,
17725 }),
17726 data: None,
17727 },
17728 lsp::CodeLens {
17729 range: lsp::Range {
17730 start: lsp::Position {
17731 line: 1,
17732 character: 1,
17733 },
17734 end: lsp::Position {
17735 line: 1,
17736 character: 1,
17737 },
17738 },
17739 command: Some(lsp::Command {
17740 title: "Command not in range".to_owned(),
17741 command: "_the/command".to_owned(),
17742 arguments: None,
17743 }),
17744 data: None,
17745 },
17746 ]))
17747 })
17748 .next()
17749 .await;
17750
17751 let actions = actions.await.unwrap();
17752 assert_eq!(
17753 actions.len(),
17754 1,
17755 "Should have only one valid action for the 0..0 range"
17756 );
17757 let action = actions[0].clone();
17758 let apply = project.update(cx, |project, cx| {
17759 project.apply_code_action(buffer.clone(), action, true, cx)
17760 });
17761
17762 // Resolving the code action does not populate its edits. In absence of
17763 // edits, we must execute the given command.
17764 fake_server.handle_request::<lsp::request::CodeLensResolve, _, _>(|mut lens, _| async move {
17765 let lens_command = lens.command.as_mut().expect("should have a command");
17766 assert_eq!(lens_command.title, "Code lens command");
17767 lens_command.arguments = Some(vec![json!("the-argument")]);
17768 Ok(lens)
17769 });
17770
17771 // While executing the command, the language server sends the editor
17772 // a `workspaceEdit` request.
17773 fake_server
17774 .handle_request::<lsp::request::ExecuteCommand, _, _>({
17775 let fake = fake_server.clone();
17776 move |params, _| {
17777 assert_eq!(params.command, "_the/command");
17778 let fake = fake.clone();
17779 async move {
17780 fake.server
17781 .request::<lsp::request::ApplyWorkspaceEdit>(
17782 lsp::ApplyWorkspaceEditParams {
17783 label: None,
17784 edit: lsp::WorkspaceEdit {
17785 changes: Some(
17786 [(
17787 lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
17788 vec![lsp::TextEdit {
17789 range: lsp::Range::new(
17790 lsp::Position::new(0, 0),
17791 lsp::Position::new(0, 0),
17792 ),
17793 new_text: "X".into(),
17794 }],
17795 )]
17796 .into_iter()
17797 .collect(),
17798 ),
17799 ..Default::default()
17800 },
17801 },
17802 )
17803 .await
17804 .unwrap();
17805 Ok(Some(json!(null)))
17806 }
17807 }
17808 })
17809 .next()
17810 .await;
17811
17812 // Applying the code lens command returns a project transaction containing the edits
17813 // sent by the language server in its `workspaceEdit` request.
17814 let transaction = apply.await.unwrap();
17815 assert!(transaction.0.contains_key(&buffer));
17816 buffer.update(cx, |buffer, cx| {
17817 assert_eq!(buffer.text(), "Xa");
17818 buffer.undo(cx);
17819 assert_eq!(buffer.text(), "a");
17820 });
17821}
17822
17823mod autoclose_tags {
17824 use super::*;
17825 use language::language_settings::JsxTagAutoCloseSettings;
17826 use languages::language;
17827
17828 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17829 init_test(cx, |settings| {
17830 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17831 });
17832
17833 let mut cx = EditorTestContext::new(cx).await;
17834 cx.update_buffer(|buffer, cx| {
17835 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17836
17837 buffer.set_language(Some(language), cx)
17838 });
17839
17840 cx
17841 }
17842
17843 macro_rules! check {
17844 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17845 #[gpui::test]
17846 async fn $name(cx: &mut TestAppContext) {
17847 let mut cx = test_setup(cx).await;
17848 cx.set_state($initial);
17849 cx.run_until_parked();
17850
17851 cx.update_editor(|editor, window, cx| {
17852 editor.handle_input($input, window, cx);
17853 });
17854 cx.run_until_parked();
17855 cx.assert_editor_state($expected);
17856 }
17857 };
17858 }
17859
17860 check!(
17861 test_basic,
17862 "<divˇ" + ">" => "<div>ˇ</div>"
17863 );
17864
17865 check!(
17866 test_basic_nested,
17867 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17868 );
17869
17870 check!(
17871 test_basic_ignore_already_closed,
17872 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17873 );
17874
17875 check!(
17876 test_doesnt_autoclose_closing_tag,
17877 "</divˇ" + ">" => "</div>ˇ"
17878 );
17879
17880 check!(
17881 test_jsx_attr,
17882 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17883 );
17884
17885 check!(
17886 test_ignores_closing_tags_in_expr_block,
17887 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17888 );
17889
17890 check!(
17891 test_doesnt_autoclose_on_gt_in_expr,
17892 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17893 );
17894
17895 check!(
17896 test_ignores_closing_tags_with_different_tag_names,
17897 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17898 );
17899
17900 check!(
17901 test_autocloses_in_jsx_expression,
17902 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17903 );
17904
17905 check!(
17906 test_doesnt_autoclose_already_closed_in_jsx_expression,
17907 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17908 );
17909
17910 check!(
17911 test_autocloses_fragment,
17912 "<ˇ" + ">" => "<>ˇ</>"
17913 );
17914
17915 check!(
17916 test_does_not_include_type_argument_in_autoclose_tag_name,
17917 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17918 );
17919
17920 check!(
17921 test_does_not_autoclose_doctype,
17922 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17923 );
17924
17925 check!(
17926 test_does_not_autoclose_comment,
17927 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17928 );
17929
17930 check!(
17931 test_multi_cursor_autoclose_same_tag,
17932 r#"
17933 <divˇ
17934 <divˇ
17935 "#
17936 + ">" =>
17937 r#"
17938 <div>ˇ</div>
17939 <div>ˇ</div>
17940 "#
17941 );
17942
17943 check!(
17944 test_multi_cursor_autoclose_different_tags,
17945 r#"
17946 <divˇ
17947 <spanˇ
17948 "#
17949 + ">" =>
17950 r#"
17951 <div>ˇ</div>
17952 <span>ˇ</span>
17953 "#
17954 );
17955
17956 check!(
17957 test_multi_cursor_autoclose_some_dont_autoclose_others,
17958 r#"
17959 <divˇ
17960 <div /ˇ
17961 <spanˇ</span>
17962 <!DOCTYPE htmlˇ
17963 </headˇ
17964 <Component<T>ˇ
17965 ˇ
17966 "#
17967 + ">" =>
17968 r#"
17969 <div>ˇ</div>
17970 <div />ˇ
17971 <span>ˇ</span>
17972 <!DOCTYPE html>ˇ
17973 </head>ˇ
17974 <Component<T>>ˇ</Component>
17975 >ˇ
17976 "#
17977 );
17978
17979 check!(
17980 test_doesnt_mess_up_trailing_text,
17981 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17982 );
17983
17984 #[gpui::test]
17985 async fn test_multibuffer(cx: &mut TestAppContext) {
17986 init_test(cx, |settings| {
17987 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17988 });
17989
17990 let buffer_a = cx.new(|cx| {
17991 let mut buf = language::Buffer::local("<div", cx);
17992 buf.set_language(
17993 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17994 cx,
17995 );
17996 buf
17997 });
17998 let buffer_b = cx.new(|cx| {
17999 let mut buf = language::Buffer::local("<pre", cx);
18000 buf.set_language(
18001 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18002 cx,
18003 );
18004 buf
18005 });
18006 let buffer_c = cx.new(|cx| {
18007 let buf = language::Buffer::local("<span", cx);
18008 buf
18009 });
18010 let buffer = cx.new(|cx| {
18011 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
18012 buf.push_excerpts(
18013 buffer_a,
18014 [ExcerptRange {
18015 context: text::Anchor::MIN..text::Anchor::MAX,
18016 primary: None,
18017 }],
18018 cx,
18019 );
18020 buf.push_excerpts(
18021 buffer_b,
18022 [ExcerptRange {
18023 context: text::Anchor::MIN..text::Anchor::MAX,
18024 primary: None,
18025 }],
18026 cx,
18027 );
18028 buf.push_excerpts(
18029 buffer_c,
18030 [ExcerptRange {
18031 context: text::Anchor::MIN..text::Anchor::MAX,
18032 primary: None,
18033 }],
18034 cx,
18035 );
18036 buf
18037 });
18038 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18039
18040 let mut cx = EditorTestContext::for_editor(editor, cx).await;
18041
18042 cx.update_editor(|editor, window, cx| {
18043 editor.change_selections(None, window, cx, |selections| {
18044 selections.select(vec![
18045 Selection::from_offset(4),
18046 Selection::from_offset(9),
18047 Selection::from_offset(15),
18048 ])
18049 })
18050 });
18051 cx.run_until_parked();
18052
18053 cx.update_editor(|editor, window, cx| {
18054 editor.handle_input(">", window, cx);
18055 });
18056 cx.run_until_parked();
18057
18058 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
18059 }
18060}
18061
18062fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
18063 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
18064 point..point
18065}
18066
18067fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
18068 let (text, ranges) = marked_text_ranges(marked_text, true);
18069 assert_eq!(editor.text(cx), text);
18070 assert_eq!(
18071 editor.selections.ranges(cx),
18072 ranges,
18073 "Assert selections are {}",
18074 marked_text
18075 );
18076}
18077
18078pub fn handle_signature_help_request(
18079 cx: &mut EditorLspTestContext,
18080 mocked_response: lsp::SignatureHelp,
18081) -> impl Future<Output = ()> {
18082 let mut request =
18083 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
18084 let mocked_response = mocked_response.clone();
18085 async move { Ok(Some(mocked_response)) }
18086 });
18087
18088 async move {
18089 request.next().await;
18090 }
18091}
18092
18093/// Handle completion request passing a marked string specifying where the completion
18094/// should be triggered from using '|' character, what range should be replaced, and what completions
18095/// should be returned using '<' and '>' to delimit the range
18096pub fn handle_completion_request(
18097 cx: &mut EditorLspTestContext,
18098 marked_string: &str,
18099 completions: Vec<&'static str>,
18100 counter: Arc<AtomicUsize>,
18101) -> impl Future<Output = ()> {
18102 let complete_from_marker: TextRangeMarker = '|'.into();
18103 let replace_range_marker: TextRangeMarker = ('<', '>').into();
18104 let (_, mut marked_ranges) = marked_text_ranges_by(
18105 marked_string,
18106 vec![complete_from_marker.clone(), replace_range_marker.clone()],
18107 );
18108
18109 let complete_from_position =
18110 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
18111 let replace_range =
18112 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
18113
18114 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
18115 let completions = completions.clone();
18116 counter.fetch_add(1, atomic::Ordering::Release);
18117 async move {
18118 assert_eq!(params.text_document_position.text_document.uri, url.clone());
18119 assert_eq!(
18120 params.text_document_position.position,
18121 complete_from_position
18122 );
18123 Ok(Some(lsp::CompletionResponse::Array(
18124 completions
18125 .iter()
18126 .map(|completion_text| lsp::CompletionItem {
18127 label: completion_text.to_string(),
18128 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18129 range: replace_range,
18130 new_text: completion_text.to_string(),
18131 })),
18132 ..Default::default()
18133 })
18134 .collect(),
18135 )))
18136 }
18137 });
18138
18139 async move {
18140 request.next().await;
18141 }
18142}
18143
18144fn handle_resolve_completion_request(
18145 cx: &mut EditorLspTestContext,
18146 edits: Option<Vec<(&'static str, &'static str)>>,
18147) -> impl Future<Output = ()> {
18148 let edits = edits.map(|edits| {
18149 edits
18150 .iter()
18151 .map(|(marked_string, new_text)| {
18152 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
18153 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
18154 lsp::TextEdit::new(replace_range, new_text.to_string())
18155 })
18156 .collect::<Vec<_>>()
18157 });
18158
18159 let mut request =
18160 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18161 let edits = edits.clone();
18162 async move {
18163 Ok(lsp::CompletionItem {
18164 additional_text_edits: edits,
18165 ..Default::default()
18166 })
18167 }
18168 });
18169
18170 async move {
18171 request.next().await;
18172 }
18173}
18174
18175pub(crate) fn update_test_language_settings(
18176 cx: &mut TestAppContext,
18177 f: impl Fn(&mut AllLanguageSettingsContent),
18178) {
18179 cx.update(|cx| {
18180 SettingsStore::update_global(cx, |store, cx| {
18181 store.update_user_settings::<AllLanguageSettings>(cx, f);
18182 });
18183 });
18184}
18185
18186pub(crate) fn update_test_project_settings(
18187 cx: &mut TestAppContext,
18188 f: impl Fn(&mut ProjectSettings),
18189) {
18190 cx.update(|cx| {
18191 SettingsStore::update_global(cx, |store, cx| {
18192 store.update_user_settings::<ProjectSettings>(cx, f);
18193 });
18194 });
18195}
18196
18197pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
18198 cx.update(|cx| {
18199 assets::Assets.load_test_fonts(cx);
18200 let store = SettingsStore::test(cx);
18201 cx.set_global(store);
18202 theme::init(theme::LoadThemes::JustBase, cx);
18203 release_channel::init(SemanticVersion::default(), cx);
18204 client::init_settings(cx);
18205 language::init(cx);
18206 Project::init_settings(cx);
18207 workspace::init_settings(cx);
18208 crate::init(cx);
18209 });
18210
18211 update_test_language_settings(cx, f);
18212}
18213
18214#[track_caller]
18215fn assert_hunk_revert(
18216 not_reverted_text_with_selections: &str,
18217 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
18218 expected_reverted_text_with_selections: &str,
18219 base_text: &str,
18220 cx: &mut EditorLspTestContext,
18221) {
18222 cx.set_state(not_reverted_text_with_selections);
18223 cx.set_head_text(base_text);
18224 cx.executor().run_until_parked();
18225
18226 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
18227 let snapshot = editor.snapshot(window, cx);
18228 let reverted_hunk_statuses = snapshot
18229 .buffer_snapshot
18230 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
18231 .map(|hunk| hunk.status().kind)
18232 .collect::<Vec<_>>();
18233
18234 editor.git_restore(&Default::default(), window, cx);
18235 reverted_hunk_statuses
18236 });
18237 cx.executor().run_until_parked();
18238 cx.assert_editor_state(expected_reverted_text_with_selections);
18239 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
18240}