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, 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, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
24 Override, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::{IndentGuide, PathKey};
28use parking_lot::Mutex;
29use pretty_assertions::{assert_eq, assert_ne};
30use project::project_settings::{LspSettings, ProjectSettings};
31use project::FakeFs;
32use serde_json::{self, json};
33use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
34use std::{
35 iter,
36 sync::atomic::{self, AtomicUsize},
37};
38use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
39use text::ToPoint as _;
40use unindent::Unindent;
41use util::{
42 assert_set_eq, path,
43 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
44 uri,
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |window, cx| {
65 let entity = cx.entity().clone();
66 cx.subscribe_in(
67 &entity,
68 window,
69 move |_, _, event: &EditorEvent, _, _| match event {
70 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
71 EditorEvent::BufferEdited => {
72 events.borrow_mut().push(("editor1", "buffer edited"))
73 }
74 _ => {}
75 },
76 )
77 .detach();
78 Editor::for_buffer(buffer.clone(), None, window, cx)
79 }
80 });
81
82 let editor2 = cx.add_window({
83 let events = events.clone();
84 |window, cx| {
85 cx.subscribe_in(
86 &cx.entity().clone(),
87 window,
88 move |_, _, event: &EditorEvent, _, _| match event {
89 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
90 EditorEvent::BufferEdited => {
91 events.borrow_mut().push(("editor2", "buffer edited"))
92 }
93 _ => {}
94 },
95 )
96 .detach();
97 Editor::for_buffer(buffer.clone(), None, window, cx)
98 }
99 });
100
101 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
102
103 // Mutating editor 1 will emit an `Edited` event only for that editor.
104 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor1", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Mutating editor 2 will emit an `Edited` event only for that editor.
115 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor2", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Undoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Redoing on editor 1 will emit an `Edited` event only for that editor.
137 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor1", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Undoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // Redoing on editor 2 will emit an `Edited` event only for that editor.
159 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
160 assert_eq!(
161 mem::take(&mut *events.borrow_mut()),
162 [
163 ("editor2", "edited"),
164 ("editor1", "buffer edited"),
165 ("editor2", "buffer edited"),
166 ]
167 );
168
169 // No event is emitted when the mutation is a no-op.
170 _ = editor2.update(cx, |editor, window, cx| {
171 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
172
173 editor.backspace(&Backspace, window, cx);
174 });
175 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
176}
177
178#[gpui::test]
179fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
180 init_test(cx, |_| {});
181
182 let mut now = Instant::now();
183 let group_interval = Duration::from_millis(1);
184 let buffer = cx.new(|cx| {
185 let mut buf = language::Buffer::local("123456", cx);
186 buf.set_group_interval(group_interval);
187 buf
188 });
189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
190 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
191
192 _ = editor.update(cx, |editor, window, cx| {
193 editor.start_transaction_at(now, window, cx);
194 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
195
196 editor.insert("cd", window, cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cd56");
199 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
200
201 editor.start_transaction_at(now, window, cx);
202 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
203 editor.insert("e", window, cx);
204 editor.end_transaction_at(now, cx);
205 assert_eq!(editor.text(cx), "12cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
207
208 now += group_interval + Duration::from_millis(1);
209 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
210
211 // Simulate an edit in another editor
212 buffer.update(cx, |buffer, cx| {
213 buffer.start_transaction_at(now, cx);
214 buffer.edit([(0..1, "a")], None, cx);
215 buffer.edit([(1..1, "b")], None, cx);
216 buffer.end_transaction_at(now, cx);
217 });
218
219 assert_eq!(editor.text(cx), "ab2cde6");
220 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
221
222 // Last transaction happened past the group interval in a different editor.
223 // Undo it individually and don't restore selections.
224 editor.undo(&Undo, window, cx);
225 assert_eq!(editor.text(cx), "12cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
227
228 // First two transactions happened within the group interval in this editor.
229 // Undo them together and restore selections.
230 editor.undo(&Undo, window, cx);
231 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
232 assert_eq!(editor.text(cx), "123456");
233 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
234
235 // Redo the first two transactions together.
236 editor.redo(&Redo, window, cx);
237 assert_eq!(editor.text(cx), "12cde6");
238 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
239
240 // Redo the last transaction on its own.
241 editor.redo(&Redo, window, cx);
242 assert_eq!(editor.text(cx), "ab2cde6");
243 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
244
245 // Test empty transactions.
246 editor.start_transaction_at(now, window, cx);
247 editor.end_transaction_at(now, cx);
248 editor.undo(&Undo, window, cx);
249 assert_eq!(editor.text(cx), "12cde6");
250 });
251}
252
253#[gpui::test]
254fn test_ime_composition(cx: &mut TestAppContext) {
255 init_test(cx, |_| {});
256
257 let buffer = cx.new(|cx| {
258 let mut buffer = language::Buffer::local("abcde", cx);
259 // Ensure automatic grouping doesn't occur.
260 buffer.set_group_interval(Duration::ZERO);
261 buffer
262 });
263
264 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
265 cx.add_window(|window, cx| {
266 let mut editor = build_editor(buffer.clone(), window, cx);
267
268 // Start a new IME composition.
269 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
270 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
272 assert_eq!(editor.text(cx), "äbcde");
273 assert_eq!(
274 editor.marked_text_ranges(cx),
275 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
276 );
277
278 // Finalize IME composition.
279 editor.replace_text_in_range(None, "ā", window, cx);
280 assert_eq!(editor.text(cx), "ābcde");
281 assert_eq!(editor.marked_text_ranges(cx), None);
282
283 // IME composition edits are grouped and are undone/redone at once.
284 editor.undo(&Default::default(), window, cx);
285 assert_eq!(editor.text(cx), "abcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287 editor.redo(&Default::default(), window, cx);
288 assert_eq!(editor.text(cx), "ābcde");
289 assert_eq!(editor.marked_text_ranges(cx), None);
290
291 // Start a new IME composition.
292 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
293 assert_eq!(
294 editor.marked_text_ranges(cx),
295 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
296 );
297
298 // Undoing during an IME composition cancels it.
299 editor.undo(&Default::default(), window, cx);
300 assert_eq!(editor.text(cx), "ābcde");
301 assert_eq!(editor.marked_text_ranges(cx), None);
302
303 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
304 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
305 assert_eq!(editor.text(cx), "ābcdè");
306 assert_eq!(
307 editor.marked_text_ranges(cx),
308 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
309 );
310
311 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
312 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
313 assert_eq!(editor.text(cx), "ābcdę");
314 assert_eq!(editor.marked_text_ranges(cx), None);
315
316 // Start a new IME composition with multiple cursors.
317 editor.change_selections(None, window, cx, |s| {
318 s.select_ranges([
319 OffsetUtf16(1)..OffsetUtf16(1),
320 OffsetUtf16(3)..OffsetUtf16(3),
321 OffsetUtf16(5)..OffsetUtf16(5),
322 ])
323 });
324 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
325 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
326 assert_eq!(
327 editor.marked_text_ranges(cx),
328 Some(vec![
329 OffsetUtf16(0)..OffsetUtf16(3),
330 OffsetUtf16(4)..OffsetUtf16(7),
331 OffsetUtf16(8)..OffsetUtf16(11)
332 ])
333 );
334
335 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
336 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
337 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
338 assert_eq!(
339 editor.marked_text_ranges(cx),
340 Some(vec![
341 OffsetUtf16(1)..OffsetUtf16(2),
342 OffsetUtf16(5)..OffsetUtf16(6),
343 OffsetUtf16(9)..OffsetUtf16(10)
344 ])
345 );
346
347 // Finalize IME composition with multiple cursors.
348 editor.replace_text_in_range(Some(9..10), "2", window, cx);
349 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
350 assert_eq!(editor.marked_text_ranges(cx), None);
351
352 editor
353 });
354}
355
356#[gpui::test]
357fn test_selection_with_mouse(cx: &mut TestAppContext) {
358 init_test(cx, |_| {});
359
360 let editor = cx.add_window(|window, cx| {
361 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
362 build_editor(buffer, window, cx)
363 });
364
365 _ = editor.update(cx, |editor, window, cx| {
366 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
367 });
368 assert_eq!(
369 editor
370 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
371 .unwrap(),
372 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
373 );
374
375 _ = editor.update(cx, |editor, window, cx| {
376 editor.update_selection(
377 DisplayPoint::new(DisplayRow(3), 3),
378 0,
379 gpui::Point::<f32>::default(),
380 window,
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
390 );
391
392 _ = editor.update(cx, |editor, window, cx| {
393 editor.update_selection(
394 DisplayPoint::new(DisplayRow(1), 1),
395 0,
396 gpui::Point::<f32>::default(),
397 window,
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |editor, window, cx| {
410 editor.end_selection(window, cx);
411 editor.update_selection(
412 DisplayPoint::new(DisplayRow(3), 3),
413 0,
414 gpui::Point::<f32>::default(),
415 window,
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
423 .unwrap(),
424 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
425 );
426
427 _ = editor.update(cx, |editor, window, cx| {
428 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
429 editor.update_selection(
430 DisplayPoint::new(DisplayRow(0), 0),
431 0,
432 gpui::Point::<f32>::default(),
433 window,
434 cx,
435 );
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
441 .unwrap(),
442 [
443 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
444 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
445 ]
446 );
447
448 _ = editor.update(cx, |editor, window, cx| {
449 editor.end_selection(window, cx);
450 });
451
452 assert_eq!(
453 editor
454 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
455 .unwrap(),
456 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
457 );
458}
459
460#[gpui::test]
461fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
462 init_test(cx, |_| {});
463
464 let editor = cx.add_window(|window, cx| {
465 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
466 build_editor(buffer, window, cx)
467 });
468
469 _ = editor.update(cx, |editor, window, cx| {
470 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
471 });
472
473 _ = editor.update(cx, |editor, window, cx| {
474 editor.end_selection(window, cx);
475 });
476
477 _ = editor.update(cx, |editor, window, cx| {
478 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
479 });
480
481 _ = editor.update(cx, |editor, window, cx| {
482 editor.end_selection(window, cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
488 .unwrap(),
489 [
490 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
491 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
492 ]
493 );
494
495 _ = editor.update(cx, |editor, window, cx| {
496 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
497 });
498
499 _ = editor.update(cx, |editor, window, cx| {
500 editor.end_selection(window, cx);
501 });
502
503 assert_eq!(
504 editor
505 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
506 .unwrap(),
507 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
508 );
509}
510
511#[gpui::test]
512fn test_canceling_pending_selection(cx: &mut TestAppContext) {
513 init_test(cx, |_| {});
514
515 let editor = cx.add_window(|window, cx| {
516 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
517 build_editor(buffer, window, cx)
518 });
519
520 _ = editor.update(cx, |editor, window, cx| {
521 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
522 assert_eq!(
523 editor.selections.display_ranges(cx),
524 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
525 );
526 });
527
528 _ = editor.update(cx, |editor, window, cx| {
529 editor.update_selection(
530 DisplayPoint::new(DisplayRow(3), 3),
531 0,
532 gpui::Point::<f32>::default(),
533 window,
534 cx,
535 );
536 assert_eq!(
537 editor.selections.display_ranges(cx),
538 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
539 );
540 });
541
542 _ = editor.update(cx, |editor, window, cx| {
543 editor.cancel(&Cancel, window, cx);
544 editor.update_selection(
545 DisplayPoint::new(DisplayRow(1), 1),
546 0,
547 gpui::Point::<f32>::default(),
548 window,
549 cx,
550 );
551 assert_eq!(
552 editor.selections.display_ranges(cx),
553 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
554 );
555 });
556}
557
558#[gpui::test]
559fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
560 init_test(cx, |_| {});
561
562 let editor = cx.add_window(|window, cx| {
563 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
564 build_editor(buffer, window, cx)
565 });
566
567 _ = editor.update(cx, |editor, window, cx| {
568 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
569 assert_eq!(
570 editor.selections.display_ranges(cx),
571 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
572 );
573
574 editor.move_down(&Default::default(), window, cx);
575 assert_eq!(
576 editor.selections.display_ranges(cx),
577 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
578 );
579
580 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
581 assert_eq!(
582 editor.selections.display_ranges(cx),
583 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
584 );
585
586 editor.move_up(&Default::default(), window, cx);
587 assert_eq!(
588 editor.selections.display_ranges(cx),
589 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
590 );
591 });
592}
593
594#[gpui::test]
595fn test_clone(cx: &mut TestAppContext) {
596 init_test(cx, |_| {});
597
598 let (text, selection_ranges) = marked_text_ranges(
599 indoc! {"
600 one
601 two
602 threeˇ
603 four
604 fiveˇ
605 "},
606 true,
607 );
608
609 let editor = cx.add_window(|window, cx| {
610 let buffer = MultiBuffer::build_simple(&text, cx);
611 build_editor(buffer, window, cx)
612 });
613
614 _ = editor.update(cx, |editor, window, cx| {
615 editor.change_selections(None, window, cx, |s| {
616 s.select_ranges(selection_ranges.clone())
617 });
618 editor.fold_creases(
619 vec![
620 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
621 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
622 ],
623 true,
624 window,
625 cx,
626 );
627 });
628
629 let cloned_editor = editor
630 .update(cx, |editor, _, cx| {
631 cx.open_window(Default::default(), |window, cx| {
632 cx.new(|cx| editor.clone(window, cx))
633 })
634 })
635 .unwrap()
636 .unwrap();
637
638 let snapshot = editor
639 .update(cx, |e, window, cx| e.snapshot(window, cx))
640 .unwrap();
641 let cloned_snapshot = cloned_editor
642 .update(cx, |e, window, cx| e.snapshot(window, cx))
643 .unwrap();
644
645 assert_eq!(
646 cloned_editor
647 .update(cx, |e, _, cx| e.display_text(cx))
648 .unwrap(),
649 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
650 );
651 assert_eq!(
652 cloned_snapshot
653 .folds_in_range(0..text.len())
654 .collect::<Vec<_>>(),
655 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
656 );
657 assert_set_eq!(
658 cloned_editor
659 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
660 .unwrap(),
661 editor
662 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
663 .unwrap()
664 );
665 assert_set_eq!(
666 cloned_editor
667 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
668 .unwrap(),
669 editor
670 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
671 .unwrap()
672 );
673}
674
675#[gpui::test]
676async fn test_navigation_history(cx: &mut TestAppContext) {
677 init_test(cx, |_| {});
678
679 use workspace::item::Item;
680
681 let fs = FakeFs::new(cx.executor());
682 let project = Project::test(fs, [], cx).await;
683 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
684 let pane = workspace
685 .update(cx, |workspace, _, _| workspace.active_pane().clone())
686 .unwrap();
687
688 _ = workspace.update(cx, |_v, window, cx| {
689 cx.new(|cx| {
690 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
691 let mut editor = build_editor(buffer.clone(), window, cx);
692 let handle = cx.entity();
693 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
694
695 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
696 editor.nav_history.as_mut().unwrap().pop_backward(cx)
697 }
698
699 // Move the cursor a small distance.
700 // Nothing is added to the navigation history.
701 editor.change_selections(None, window, cx, |s| {
702 s.select_display_ranges([
703 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
704 ])
705 });
706 editor.change_selections(None, window, cx, |s| {
707 s.select_display_ranges([
708 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
709 ])
710 });
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance.
714 // The history can jump back to the previous position.
715 editor.change_selections(None, window, cx, |s| {
716 s.select_display_ranges([
717 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
718 ])
719 });
720 let nav_entry = pop_history(&mut editor, cx).unwrap();
721 editor.navigate(nav_entry.data.unwrap(), window, cx);
722 assert_eq!(nav_entry.item.id(), cx.entity_id());
723 assert_eq!(
724 editor.selections.display_ranges(cx),
725 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
726 );
727 assert!(pop_history(&mut editor, cx).is_none());
728
729 // Move the cursor a small distance via the mouse.
730 // Nothing is added to the navigation history.
731 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
732 editor.end_selection(window, cx);
733 assert_eq!(
734 editor.selections.display_ranges(cx),
735 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
736 );
737 assert!(pop_history(&mut editor, cx).is_none());
738
739 // Move the cursor a large distance via the mouse.
740 // The history can jump back to the previous position.
741 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
742 editor.end_selection(window, cx);
743 assert_eq!(
744 editor.selections.display_ranges(cx),
745 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
746 );
747 let nav_entry = pop_history(&mut editor, cx).unwrap();
748 editor.navigate(nav_entry.data.unwrap(), window, cx);
749 assert_eq!(nav_entry.item.id(), cx.entity_id());
750 assert_eq!(
751 editor.selections.display_ranges(cx),
752 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
753 );
754 assert!(pop_history(&mut editor, cx).is_none());
755
756 // Set scroll position to check later
757 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
758 let original_scroll_position = editor.scroll_manager.anchor();
759
760 // Jump to the end of the document and adjust scroll
761 editor.move_to_end(&MoveToEnd, window, cx);
762 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
763 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
764
765 let nav_entry = pop_history(&mut editor, cx).unwrap();
766 editor.navigate(nav_entry.data.unwrap(), window, cx);
767 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
768
769 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
770 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
771 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
772 let invalid_point = Point::new(9999, 0);
773 editor.navigate(
774 Box::new(NavigationData {
775 cursor_anchor: invalid_anchor,
776 cursor_position: invalid_point,
777 scroll_anchor: ScrollAnchor {
778 anchor: invalid_anchor,
779 offset: Default::default(),
780 },
781 scroll_top_row: invalid_point.row,
782 }),
783 window,
784 cx,
785 );
786 assert_eq!(
787 editor.selections.display_ranges(cx),
788 &[editor.max_point(cx)..editor.max_point(cx)]
789 );
790 assert_eq!(
791 editor.scroll_position(cx),
792 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
793 );
794
795 editor
796 })
797 });
798}
799
800#[gpui::test]
801fn test_cancel(cx: &mut TestAppContext) {
802 init_test(cx, |_| {});
803
804 let editor = cx.add_window(|window, cx| {
805 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
806 build_editor(buffer, window, cx)
807 });
808
809 _ = editor.update(cx, |editor, window, cx| {
810 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
811 editor.update_selection(
812 DisplayPoint::new(DisplayRow(1), 1),
813 0,
814 gpui::Point::<f32>::default(),
815 window,
816 cx,
817 );
818 editor.end_selection(window, cx);
819
820 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
821 editor.update_selection(
822 DisplayPoint::new(DisplayRow(0), 3),
823 0,
824 gpui::Point::<f32>::default(),
825 window,
826 cx,
827 );
828 editor.end_selection(window, cx);
829 assert_eq!(
830 editor.selections.display_ranges(cx),
831 [
832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
833 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
834 ]
835 );
836 });
837
838 _ = editor.update(cx, |editor, window, cx| {
839 editor.cancel(&Cancel, window, cx);
840 assert_eq!(
841 editor.selections.display_ranges(cx),
842 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
843 );
844 });
845
846 _ = editor.update(cx, |editor, window, cx| {
847 editor.cancel(&Cancel, window, cx);
848 assert_eq!(
849 editor.selections.display_ranges(cx),
850 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
851 );
852 });
853}
854
855#[gpui::test]
856fn test_fold_action(cx: &mut TestAppContext) {
857 init_test(cx, |_| {});
858
859 let editor = cx.add_window(|window, cx| {
860 let buffer = MultiBuffer::build_simple(
861 &"
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {
870 2
871 }
872
873 fn c() {
874 3
875 }
876 }
877 "
878 .unindent(),
879 cx,
880 );
881 build_editor(buffer.clone(), window, cx)
882 });
883
884 _ = editor.update(cx, |editor, window, cx| {
885 editor.change_selections(None, window, cx, |s| {
886 s.select_display_ranges([
887 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
888 ]);
889 });
890 editor.fold(&Fold, window, cx);
891 assert_eq!(
892 editor.display_text(cx),
893 "
894 impl Foo {
895 // Hello!
896
897 fn a() {
898 1
899 }
900
901 fn b() {⋯
902 }
903
904 fn c() {⋯
905 }
906 }
907 "
908 .unindent(),
909 );
910
911 editor.fold(&Fold, window, cx);
912 assert_eq!(
913 editor.display_text(cx),
914 "
915 impl Foo {⋯
916 }
917 "
918 .unindent(),
919 );
920
921 editor.unfold_lines(&UnfoldLines, window, cx);
922 assert_eq!(
923 editor.display_text(cx),
924 "
925 impl Foo {
926 // Hello!
927
928 fn a() {
929 1
930 }
931
932 fn b() {⋯
933 }
934
935 fn c() {⋯
936 }
937 }
938 "
939 .unindent(),
940 );
941
942 editor.unfold_lines(&UnfoldLines, window, cx);
943 assert_eq!(
944 editor.display_text(cx),
945 editor.buffer.read(cx).read(cx).text()
946 );
947 });
948}
949
950#[gpui::test]
951fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
952 init_test(cx, |_| {});
953
954 let editor = cx.add_window(|window, cx| {
955 let buffer = MultiBuffer::build_simple(
956 &"
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():
964 print(2)
965
966 def c():
967 print(3)
968 "
969 .unindent(),
970 cx,
971 );
972 build_editor(buffer.clone(), window, cx)
973 });
974
975 _ = editor.update(cx, |editor, window, cx| {
976 editor.change_selections(None, window, cx, |s| {
977 s.select_display_ranges([
978 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
979 ]);
980 });
981 editor.fold(&Fold, window, cx);
982 assert_eq!(
983 editor.display_text(cx),
984 "
985 class Foo:
986 # Hello!
987
988 def a():
989 print(1)
990
991 def b():⋯
992
993 def c():⋯
994 "
995 .unindent(),
996 );
997
998 editor.fold(&Fold, window, cx);
999 assert_eq!(
1000 editor.display_text(cx),
1001 "
1002 class Foo:⋯
1003 "
1004 .unindent(),
1005 );
1006
1007 editor.unfold_lines(&UnfoldLines, window, cx);
1008 assert_eq!(
1009 editor.display_text(cx),
1010 "
1011 class Foo:
1012 # Hello!
1013
1014 def a():
1015 print(1)
1016
1017 def b():⋯
1018
1019 def c():⋯
1020 "
1021 .unindent(),
1022 );
1023
1024 editor.unfold_lines(&UnfoldLines, window, cx);
1025 assert_eq!(
1026 editor.display_text(cx),
1027 editor.buffer.read(cx).read(cx).text()
1028 );
1029 });
1030}
1031
1032#[gpui::test]
1033fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1034 init_test(cx, |_| {});
1035
1036 let editor = cx.add_window(|window, cx| {
1037 let buffer = MultiBuffer::build_simple(
1038 &"
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():
1046 print(2)
1047
1048
1049 def c():
1050 print(3)
1051
1052
1053 "
1054 .unindent(),
1055 cx,
1056 );
1057 build_editor(buffer.clone(), window, cx)
1058 });
1059
1060 _ = editor.update(cx, |editor, window, cx| {
1061 editor.change_selections(None, window, cx, |s| {
1062 s.select_display_ranges([
1063 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1064 ]);
1065 });
1066 editor.fold(&Fold, window, cx);
1067 assert_eq!(
1068 editor.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 editor.fold(&Fold, window, cx);
1087 assert_eq!(
1088 editor.display_text(cx),
1089 "
1090 class Foo:⋯
1091
1092
1093 "
1094 .unindent(),
1095 );
1096
1097 editor.unfold_lines(&UnfoldLines, window, cx);
1098 assert_eq!(
1099 editor.display_text(cx),
1100 "
1101 class Foo:
1102 # Hello!
1103
1104 def a():
1105 print(1)
1106
1107 def b():⋯
1108
1109
1110 def c():⋯
1111
1112
1113 "
1114 .unindent(),
1115 );
1116
1117 editor.unfold_lines(&UnfoldLines, window, cx);
1118 assert_eq!(
1119 editor.display_text(cx),
1120 editor.buffer.read(cx).read(cx).text()
1121 );
1122 });
1123}
1124
1125#[gpui::test]
1126fn test_fold_at_level(cx: &mut TestAppContext) {
1127 init_test(cx, |_| {});
1128
1129 let editor = cx.add_window(|window, cx| {
1130 let buffer = MultiBuffer::build_simple(
1131 &"
1132 class Foo:
1133 # Hello!
1134
1135 def a():
1136 print(1)
1137
1138 def b():
1139 print(2)
1140
1141
1142 class Bar:
1143 # World!
1144
1145 def a():
1146 print(1)
1147
1148 def b():
1149 print(2)
1150
1151
1152 "
1153 .unindent(),
1154 cx,
1155 );
1156 build_editor(buffer.clone(), window, cx)
1157 });
1158
1159 _ = editor.update(cx, |editor, window, cx| {
1160 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1161 assert_eq!(
1162 editor.display_text(cx),
1163 "
1164 class Foo:
1165 # Hello!
1166
1167 def a():⋯
1168
1169 def b():⋯
1170
1171
1172 class Bar:
1173 # World!
1174
1175 def a():⋯
1176
1177 def b():⋯
1178
1179
1180 "
1181 .unindent(),
1182 );
1183
1184 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1185 assert_eq!(
1186 editor.display_text(cx),
1187 "
1188 class Foo:⋯
1189
1190
1191 class Bar:⋯
1192
1193
1194 "
1195 .unindent(),
1196 );
1197
1198 editor.unfold_all(&UnfoldAll, window, cx);
1199 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1200 assert_eq!(
1201 editor.display_text(cx),
1202 "
1203 class Foo:
1204 # Hello!
1205
1206 def a():
1207 print(1)
1208
1209 def b():
1210 print(2)
1211
1212
1213 class Bar:
1214 # World!
1215
1216 def a():
1217 print(1)
1218
1219 def b():
1220 print(2)
1221
1222
1223 "
1224 .unindent(),
1225 );
1226
1227 assert_eq!(
1228 editor.display_text(cx),
1229 editor.buffer.read(cx).read(cx).text()
1230 );
1231 });
1232}
1233
1234#[gpui::test]
1235fn test_move_cursor(cx: &mut TestAppContext) {
1236 init_test(cx, |_| {});
1237
1238 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1239 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1240
1241 buffer.update(cx, |buffer, cx| {
1242 buffer.edit(
1243 vec![
1244 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1245 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1246 ],
1247 None,
1248 cx,
1249 );
1250 });
1251 _ = editor.update(cx, |editor, window, cx| {
1252 assert_eq!(
1253 editor.selections.display_ranges(cx),
1254 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1255 );
1256
1257 editor.move_down(&MoveDown, window, cx);
1258 assert_eq!(
1259 editor.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1261 );
1262
1263 editor.move_right(&MoveRight, window, cx);
1264 assert_eq!(
1265 editor.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1267 );
1268
1269 editor.move_left(&MoveLeft, window, cx);
1270 assert_eq!(
1271 editor.selections.display_ranges(cx),
1272 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1273 );
1274
1275 editor.move_up(&MoveUp, window, cx);
1276 assert_eq!(
1277 editor.selections.display_ranges(cx),
1278 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1279 );
1280
1281 editor.move_to_end(&MoveToEnd, window, cx);
1282 assert_eq!(
1283 editor.selections.display_ranges(cx),
1284 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1285 );
1286
1287 editor.move_to_beginning(&MoveToBeginning, window, cx);
1288 assert_eq!(
1289 editor.selections.display_ranges(cx),
1290 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1291 );
1292
1293 editor.change_selections(None, window, cx, |s| {
1294 s.select_display_ranges([
1295 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1296 ]);
1297 });
1298 editor.select_to_beginning(&SelectToBeginning, window, cx);
1299 assert_eq!(
1300 editor.selections.display_ranges(cx),
1301 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1302 );
1303
1304 editor.select_to_end(&SelectToEnd, window, cx);
1305 assert_eq!(
1306 editor.selections.display_ranges(cx),
1307 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1308 );
1309 });
1310}
1311
1312// TODO: Re-enable this test
1313#[cfg(target_os = "macos")]
1314#[gpui::test]
1315fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1316 init_test(cx, |_| {});
1317
1318 let editor = cx.add_window(|window, cx| {
1319 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1320 build_editor(buffer.clone(), window, cx)
1321 });
1322
1323 assert_eq!('🟥'.len_utf8(), 4);
1324 assert_eq!('α'.len_utf8(), 2);
1325
1326 _ = editor.update(cx, |editor, window, cx| {
1327 editor.fold_creases(
1328 vec![
1329 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1330 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1332 ],
1333 true,
1334 window,
1335 cx,
1336 );
1337 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1338
1339 editor.move_right(&MoveRight, window, cx);
1340 assert_eq!(
1341 editor.selections.display_ranges(cx),
1342 &[empty_range(0, "🟥".len())]
1343 );
1344 editor.move_right(&MoveRight, window, cx);
1345 assert_eq!(
1346 editor.selections.display_ranges(cx),
1347 &[empty_range(0, "🟥🟧".len())]
1348 );
1349 editor.move_right(&MoveRight, window, cx);
1350 assert_eq!(
1351 editor.selections.display_ranges(cx),
1352 &[empty_range(0, "🟥🟧⋯".len())]
1353 );
1354
1355 editor.move_down(&MoveDown, window, cx);
1356 assert_eq!(
1357 editor.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 editor.move_left(&MoveLeft, window, cx);
1361 assert_eq!(
1362 editor.selections.display_ranges(cx),
1363 &[empty_range(1, "ab⋯".len())]
1364 );
1365 editor.move_left(&MoveLeft, window, cx);
1366 assert_eq!(
1367 editor.selections.display_ranges(cx),
1368 &[empty_range(1, "ab".len())]
1369 );
1370 editor.move_left(&MoveLeft, window, cx);
1371 assert_eq!(
1372 editor.selections.display_ranges(cx),
1373 &[empty_range(1, "a".len())]
1374 );
1375
1376 editor.move_down(&MoveDown, window, cx);
1377 assert_eq!(
1378 editor.selections.display_ranges(cx),
1379 &[empty_range(2, "α".len())]
1380 );
1381 editor.move_right(&MoveRight, window, cx);
1382 assert_eq!(
1383 editor.selections.display_ranges(cx),
1384 &[empty_range(2, "αβ".len())]
1385 );
1386 editor.move_right(&MoveRight, window, cx);
1387 assert_eq!(
1388 editor.selections.display_ranges(cx),
1389 &[empty_range(2, "αβ⋯".len())]
1390 );
1391 editor.move_right(&MoveRight, window, cx);
1392 assert_eq!(
1393 editor.selections.display_ranges(cx),
1394 &[empty_range(2, "αβ⋯ε".len())]
1395 );
1396
1397 editor.move_up(&MoveUp, window, cx);
1398 assert_eq!(
1399 editor.selections.display_ranges(cx),
1400 &[empty_range(1, "ab⋯e".len())]
1401 );
1402 editor.move_down(&MoveDown, window, cx);
1403 assert_eq!(
1404 editor.selections.display_ranges(cx),
1405 &[empty_range(2, "αβ⋯ε".len())]
1406 );
1407 editor.move_up(&MoveUp, window, cx);
1408 assert_eq!(
1409 editor.selections.display_ranges(cx),
1410 &[empty_range(1, "ab⋯e".len())]
1411 );
1412
1413 editor.move_up(&MoveUp, window, cx);
1414 assert_eq!(
1415 editor.selections.display_ranges(cx),
1416 &[empty_range(0, "🟥🟧".len())]
1417 );
1418 editor.move_left(&MoveLeft, window, cx);
1419 assert_eq!(
1420 editor.selections.display_ranges(cx),
1421 &[empty_range(0, "🟥".len())]
1422 );
1423 editor.move_left(&MoveLeft, window, cx);
1424 assert_eq!(
1425 editor.selections.display_ranges(cx),
1426 &[empty_range(0, "".len())]
1427 );
1428 });
1429}
1430
1431#[gpui::test]
1432fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1433 init_test(cx, |_| {});
1434
1435 let editor = cx.add_window(|window, cx| {
1436 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1437 build_editor(buffer.clone(), window, cx)
1438 });
1439 _ = editor.update(cx, |editor, window, cx| {
1440 editor.change_selections(None, window, cx, |s| {
1441 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1442 });
1443
1444 // moving above start of document should move selection to start of document,
1445 // but the next move down should still be at the original goal_x
1446 editor.move_up(&MoveUp, window, cx);
1447 assert_eq!(
1448 editor.selections.display_ranges(cx),
1449 &[empty_range(0, "".len())]
1450 );
1451
1452 editor.move_down(&MoveDown, window, cx);
1453 assert_eq!(
1454 editor.selections.display_ranges(cx),
1455 &[empty_range(1, "abcd".len())]
1456 );
1457
1458 editor.move_down(&MoveDown, window, cx);
1459 assert_eq!(
1460 editor.selections.display_ranges(cx),
1461 &[empty_range(2, "αβγ".len())]
1462 );
1463
1464 editor.move_down(&MoveDown, window, cx);
1465 assert_eq!(
1466 editor.selections.display_ranges(cx),
1467 &[empty_range(3, "abcd".len())]
1468 );
1469
1470 editor.move_down(&MoveDown, window, cx);
1471 assert_eq!(
1472 editor.selections.display_ranges(cx),
1473 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1474 );
1475
1476 // moving past end of document should not change goal_x
1477 editor.move_down(&MoveDown, window, cx);
1478 assert_eq!(
1479 editor.selections.display_ranges(cx),
1480 &[empty_range(5, "".len())]
1481 );
1482
1483 editor.move_down(&MoveDown, window, cx);
1484 assert_eq!(
1485 editor.selections.display_ranges(cx),
1486 &[empty_range(5, "".len())]
1487 );
1488
1489 editor.move_up(&MoveUp, window, cx);
1490 assert_eq!(
1491 editor.selections.display_ranges(cx),
1492 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1493 );
1494
1495 editor.move_up(&MoveUp, window, cx);
1496 assert_eq!(
1497 editor.selections.display_ranges(cx),
1498 &[empty_range(3, "abcd".len())]
1499 );
1500
1501 editor.move_up(&MoveUp, window, cx);
1502 assert_eq!(
1503 editor.selections.display_ranges(cx),
1504 &[empty_range(2, "αβγ".len())]
1505 );
1506 });
1507}
1508
1509#[gpui::test]
1510fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1511 init_test(cx, |_| {});
1512 let move_to_beg = MoveToBeginningOfLine {
1513 stop_at_soft_wraps: true,
1514 stop_at_indent: true,
1515 };
1516
1517 let move_to_end = MoveToEndOfLine {
1518 stop_at_soft_wraps: true,
1519 };
1520
1521 let editor = cx.add_window(|window, cx| {
1522 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1523 build_editor(buffer, window, cx)
1524 });
1525 _ = editor.update(cx, |editor, window, cx| {
1526 editor.change_selections(None, window, cx, |s| {
1527 s.select_display_ranges([
1528 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1529 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1530 ]);
1531 });
1532 });
1533
1534 _ = editor.update(cx, |editor, window, cx| {
1535 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1536 assert_eq!(
1537 editor.selections.display_ranges(cx),
1538 &[
1539 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1540 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1541 ]
1542 );
1543 });
1544
1545 _ = editor.update(cx, |editor, window, cx| {
1546 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1547 assert_eq!(
1548 editor.selections.display_ranges(cx),
1549 &[
1550 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1551 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1552 ]
1553 );
1554 });
1555
1556 _ = editor.update(cx, |editor, window, cx| {
1557 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1558 assert_eq!(
1559 editor.selections.display_ranges(cx),
1560 &[
1561 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1562 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1563 ]
1564 );
1565 });
1566
1567 _ = editor.update(cx, |editor, window, cx| {
1568 editor.move_to_end_of_line(&move_to_end, window, cx);
1569 assert_eq!(
1570 editor.selections.display_ranges(cx),
1571 &[
1572 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1573 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1574 ]
1575 );
1576 });
1577
1578 // Moving to the end of line again is a no-op.
1579 _ = editor.update(cx, |editor, window, cx| {
1580 editor.move_to_end_of_line(&move_to_end, window, cx);
1581 assert_eq!(
1582 editor.selections.display_ranges(cx),
1583 &[
1584 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1585 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1586 ]
1587 );
1588 });
1589
1590 _ = editor.update(cx, |editor, window, cx| {
1591 editor.move_left(&MoveLeft, window, cx);
1592 editor.select_to_beginning_of_line(
1593 &SelectToBeginningOfLine {
1594 stop_at_soft_wraps: true,
1595 stop_at_indent: true,
1596 },
1597 window,
1598 cx,
1599 );
1600 assert_eq!(
1601 editor.selections.display_ranges(cx),
1602 &[
1603 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1604 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1605 ]
1606 );
1607 });
1608
1609 _ = editor.update(cx, |editor, window, cx| {
1610 editor.select_to_beginning_of_line(
1611 &SelectToBeginningOfLine {
1612 stop_at_soft_wraps: true,
1613 stop_at_indent: true,
1614 },
1615 window,
1616 cx,
1617 );
1618 assert_eq!(
1619 editor.selections.display_ranges(cx),
1620 &[
1621 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1622 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1623 ]
1624 );
1625 });
1626
1627 _ = editor.update(cx, |editor, window, cx| {
1628 editor.select_to_beginning_of_line(
1629 &SelectToBeginningOfLine {
1630 stop_at_soft_wraps: true,
1631 stop_at_indent: true,
1632 },
1633 window,
1634 cx,
1635 );
1636 assert_eq!(
1637 editor.selections.display_ranges(cx),
1638 &[
1639 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1640 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1641 ]
1642 );
1643 });
1644
1645 _ = editor.update(cx, |editor, window, cx| {
1646 editor.select_to_end_of_line(
1647 &SelectToEndOfLine {
1648 stop_at_soft_wraps: true,
1649 },
1650 window,
1651 cx,
1652 );
1653 assert_eq!(
1654 editor.selections.display_ranges(cx),
1655 &[
1656 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1657 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1658 ]
1659 );
1660 });
1661
1662 _ = editor.update(cx, |editor, window, cx| {
1663 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1664 assert_eq!(editor.display_text(cx), "ab\n de");
1665 assert_eq!(
1666 editor.selections.display_ranges(cx),
1667 &[
1668 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1669 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1670 ]
1671 );
1672 });
1673
1674 _ = editor.update(cx, |editor, window, cx| {
1675 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
1676 assert_eq!(editor.display_text(cx), "\n");
1677 assert_eq!(
1678 editor.selections.display_ranges(cx),
1679 &[
1680 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1681 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1682 ]
1683 );
1684 });
1685}
1686
1687#[gpui::test]
1688fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1689 init_test(cx, |_| {});
1690 let move_to_beg = MoveToBeginningOfLine {
1691 stop_at_soft_wraps: false,
1692 stop_at_indent: false,
1693 };
1694
1695 let move_to_end = MoveToEndOfLine {
1696 stop_at_soft_wraps: false,
1697 };
1698
1699 let editor = cx.add_window(|window, cx| {
1700 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1701 build_editor(buffer, window, cx)
1702 });
1703
1704 _ = editor.update(cx, |editor, window, cx| {
1705 editor.set_wrap_width(Some(140.0.into()), cx);
1706
1707 // We expect the following lines after wrapping
1708 // ```
1709 // thequickbrownfox
1710 // jumpedoverthelazydo
1711 // gs
1712 // ```
1713 // The final `gs` was soft-wrapped onto a new line.
1714 assert_eq!(
1715 "thequickbrownfox\njumpedoverthelaz\nydogs",
1716 editor.display_text(cx),
1717 );
1718
1719 // First, let's assert behavior on the first line, that was not soft-wrapped.
1720 // Start the cursor at the `k` on the first line
1721 editor.change_selections(None, window, cx, |s| {
1722 s.select_display_ranges([
1723 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1724 ]);
1725 });
1726
1727 // Moving to the beginning of the line should put us at the beginning of the line.
1728 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1729 assert_eq!(
1730 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1731 editor.selections.display_ranges(cx)
1732 );
1733
1734 // Moving to the end of the line should put us at the end of the line.
1735 editor.move_to_end_of_line(&move_to_end, window, cx);
1736 assert_eq!(
1737 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1738 editor.selections.display_ranges(cx)
1739 );
1740
1741 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1742 // Start the cursor at the last line (`y` that was wrapped to a new line)
1743 editor.change_selections(None, window, cx, |s| {
1744 s.select_display_ranges([
1745 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1746 ]);
1747 });
1748
1749 // Moving to the beginning of the line should put us at the start of the second line of
1750 // display text, i.e., the `j`.
1751 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1752 assert_eq!(
1753 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1754 editor.selections.display_ranges(cx)
1755 );
1756
1757 // Moving to the beginning of the line again should be a no-op.
1758 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1759 assert_eq!(
1760 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1761 editor.selections.display_ranges(cx)
1762 );
1763
1764 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1765 // next display line.
1766 editor.move_to_end_of_line(&move_to_end, window, cx);
1767 assert_eq!(
1768 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1769 editor.selections.display_ranges(cx)
1770 );
1771
1772 // Moving to the end of the line again should be a no-op.
1773 editor.move_to_end_of_line(&move_to_end, window, cx);
1774 assert_eq!(
1775 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1776 editor.selections.display_ranges(cx)
1777 );
1778 });
1779}
1780
1781#[gpui::test]
1782fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1783 init_test(cx, |_| {});
1784
1785 let editor = cx.add_window(|window, cx| {
1786 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1787 build_editor(buffer, window, cx)
1788 });
1789 _ = editor.update(cx, |editor, window, cx| {
1790 editor.change_selections(None, window, cx, |s| {
1791 s.select_display_ranges([
1792 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1793 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1794 ])
1795 });
1796
1797 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1798 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1799
1800 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1801 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1802
1803 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1804 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1805
1806 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1807 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1808
1809 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1810 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1811
1812 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1813 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1814
1815 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1816 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1817
1818 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1819 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1820
1821 editor.move_right(&MoveRight, window, cx);
1822 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1823 assert_selection_ranges(
1824 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1825 editor,
1826 cx,
1827 );
1828
1829 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1830 assert_selection_ranges(
1831 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1832 editor,
1833 cx,
1834 );
1835
1836 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1837 assert_selection_ranges(
1838 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1839 editor,
1840 cx,
1841 );
1842 });
1843}
1844
1845#[gpui::test]
1846fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1847 init_test(cx, |_| {});
1848
1849 let editor = cx.add_window(|window, cx| {
1850 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1851 build_editor(buffer, window, cx)
1852 });
1853
1854 _ = editor.update(cx, |editor, window, cx| {
1855 editor.set_wrap_width(Some(140.0.into()), cx);
1856 assert_eq!(
1857 editor.display_text(cx),
1858 "use one::{\n two::three::\n four::five\n};"
1859 );
1860
1861 editor.change_selections(None, window, cx, |s| {
1862 s.select_display_ranges([
1863 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1864 ]);
1865 });
1866
1867 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1868 assert_eq!(
1869 editor.selections.display_ranges(cx),
1870 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1871 );
1872
1873 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1874 assert_eq!(
1875 editor.selections.display_ranges(cx),
1876 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1877 );
1878
1879 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1880 assert_eq!(
1881 editor.selections.display_ranges(cx),
1882 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1883 );
1884
1885 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1886 assert_eq!(
1887 editor.selections.display_ranges(cx),
1888 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1889 );
1890
1891 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1892 assert_eq!(
1893 editor.selections.display_ranges(cx),
1894 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1895 );
1896
1897 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1898 assert_eq!(
1899 editor.selections.display_ranges(cx),
1900 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1901 );
1902 });
1903}
1904
1905#[gpui::test]
1906async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
1907 init_test(cx, |_| {});
1908 let mut cx = EditorTestContext::new(cx).await;
1909
1910 let line_height = cx.editor(|editor, window, _| {
1911 editor
1912 .style()
1913 .unwrap()
1914 .text
1915 .line_height_in_pixels(window.rem_size())
1916 });
1917 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1918
1919 cx.set_state(
1920 &r#"ˇone
1921 two
1922
1923 three
1924 fourˇ
1925 five
1926
1927 six"#
1928 .unindent(),
1929 );
1930
1931 cx.update_editor(|editor, window, cx| {
1932 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1933 });
1934 cx.assert_editor_state(
1935 &r#"one
1936 two
1937 ˇ
1938 three
1939 four
1940 five
1941 ˇ
1942 six"#
1943 .unindent(),
1944 );
1945
1946 cx.update_editor(|editor, window, cx| {
1947 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1948 });
1949 cx.assert_editor_state(
1950 &r#"one
1951 two
1952
1953 three
1954 four
1955 five
1956 ˇ
1957 sixˇ"#
1958 .unindent(),
1959 );
1960
1961 cx.update_editor(|editor, window, cx| {
1962 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1963 });
1964 cx.assert_editor_state(
1965 &r#"one
1966 two
1967
1968 three
1969 four
1970 five
1971
1972 sixˇ"#
1973 .unindent(),
1974 );
1975
1976 cx.update_editor(|editor, window, cx| {
1977 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1978 });
1979 cx.assert_editor_state(
1980 &r#"one
1981 two
1982
1983 three
1984 four
1985 five
1986 ˇ
1987 six"#
1988 .unindent(),
1989 );
1990
1991 cx.update_editor(|editor, window, cx| {
1992 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1993 });
1994 cx.assert_editor_state(
1995 &r#"one
1996 two
1997 ˇ
1998 three
1999 four
2000 five
2001
2002 six"#
2003 .unindent(),
2004 );
2005
2006 cx.update_editor(|editor, window, cx| {
2007 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2008 });
2009 cx.assert_editor_state(
2010 &r#"ˇone
2011 two
2012
2013 three
2014 four
2015 five
2016
2017 six"#
2018 .unindent(),
2019 );
2020}
2021
2022#[gpui::test]
2023async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2024 init_test(cx, |_| {});
2025 let mut cx = EditorTestContext::new(cx).await;
2026 let line_height = cx.editor(|editor, window, _| {
2027 editor
2028 .style()
2029 .unwrap()
2030 .text
2031 .line_height_in_pixels(window.rem_size())
2032 });
2033 let window = cx.window;
2034 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2035
2036 cx.set_state(
2037 r#"ˇone
2038 two
2039 three
2040 four
2041 five
2042 six
2043 seven
2044 eight
2045 nine
2046 ten
2047 "#,
2048 );
2049
2050 cx.update_editor(|editor, window, cx| {
2051 assert_eq!(
2052 editor.snapshot(window, cx).scroll_position(),
2053 gpui::Point::new(0., 0.)
2054 );
2055 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2056 assert_eq!(
2057 editor.snapshot(window, cx).scroll_position(),
2058 gpui::Point::new(0., 3.)
2059 );
2060 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2061 assert_eq!(
2062 editor.snapshot(window, cx).scroll_position(),
2063 gpui::Point::new(0., 6.)
2064 );
2065 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2066 assert_eq!(
2067 editor.snapshot(window, cx).scroll_position(),
2068 gpui::Point::new(0., 3.)
2069 );
2070
2071 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2072 assert_eq!(
2073 editor.snapshot(window, cx).scroll_position(),
2074 gpui::Point::new(0., 1.)
2075 );
2076 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2077 assert_eq!(
2078 editor.snapshot(window, cx).scroll_position(),
2079 gpui::Point::new(0., 3.)
2080 );
2081 });
2082}
2083
2084#[gpui::test]
2085async fn test_autoscroll(cx: &mut TestAppContext) {
2086 init_test(cx, |_| {});
2087 let mut cx = EditorTestContext::new(cx).await;
2088
2089 let line_height = cx.update_editor(|editor, window, cx| {
2090 editor.set_vertical_scroll_margin(2, cx);
2091 editor
2092 .style()
2093 .unwrap()
2094 .text
2095 .line_height_in_pixels(window.rem_size())
2096 });
2097 let window = cx.window;
2098 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2099
2100 cx.set_state(
2101 r#"ˇone
2102 two
2103 three
2104 four
2105 five
2106 six
2107 seven
2108 eight
2109 nine
2110 ten
2111 "#,
2112 );
2113 cx.update_editor(|editor, window, cx| {
2114 assert_eq!(
2115 editor.snapshot(window, cx).scroll_position(),
2116 gpui::Point::new(0., 0.0)
2117 );
2118 });
2119
2120 // Add a cursor below the visible area. Since both cursors cannot fit
2121 // on screen, the editor autoscrolls to reveal the newest cursor, and
2122 // allows the vertical scroll margin below that cursor.
2123 cx.update_editor(|editor, window, cx| {
2124 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2125 selections.select_ranges([
2126 Point::new(0, 0)..Point::new(0, 0),
2127 Point::new(6, 0)..Point::new(6, 0),
2128 ]);
2129 })
2130 });
2131 cx.update_editor(|editor, window, cx| {
2132 assert_eq!(
2133 editor.snapshot(window, cx).scroll_position(),
2134 gpui::Point::new(0., 3.0)
2135 );
2136 });
2137
2138 // Move down. The editor cursor scrolls down to track the newest cursor.
2139 cx.update_editor(|editor, window, cx| {
2140 editor.move_down(&Default::default(), window, cx);
2141 });
2142 cx.update_editor(|editor, window, cx| {
2143 assert_eq!(
2144 editor.snapshot(window, cx).scroll_position(),
2145 gpui::Point::new(0., 4.0)
2146 );
2147 });
2148
2149 // Add a cursor above the visible area. Since both cursors fit on screen,
2150 // the editor scrolls to show both.
2151 cx.update_editor(|editor, window, cx| {
2152 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2153 selections.select_ranges([
2154 Point::new(1, 0)..Point::new(1, 0),
2155 Point::new(6, 0)..Point::new(6, 0),
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., 1.0)
2163 );
2164 });
2165}
2166
2167#[gpui::test]
2168async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2169 init_test(cx, |_| {});
2170 let mut cx = EditorTestContext::new(cx).await;
2171
2172 let line_height = cx.editor(|editor, window, _cx| {
2173 editor
2174 .style()
2175 .unwrap()
2176 .text
2177 .line_height_in_pixels(window.rem_size())
2178 });
2179 let window = cx.window;
2180 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2181 cx.set_state(
2182 &r#"
2183 ˇone
2184 two
2185 threeˇ
2186 four
2187 five
2188 six
2189 seven
2190 eight
2191 nine
2192 ten
2193 "#
2194 .unindent(),
2195 );
2196
2197 cx.update_editor(|editor, window, cx| {
2198 editor.move_page_down(&MovePageDown::default(), window, cx)
2199 });
2200 cx.assert_editor_state(
2201 &r#"
2202 one
2203 two
2204 three
2205 ˇfour
2206 five
2207 sixˇ
2208 seven
2209 eight
2210 nine
2211 ten
2212 "#
2213 .unindent(),
2214 );
2215
2216 cx.update_editor(|editor, window, cx| {
2217 editor.move_page_down(&MovePageDown::default(), window, cx)
2218 });
2219 cx.assert_editor_state(
2220 &r#"
2221 one
2222 two
2223 three
2224 four
2225 five
2226 six
2227 ˇseven
2228 eight
2229 nineˇ
2230 ten
2231 "#
2232 .unindent(),
2233 );
2234
2235 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2236 cx.assert_editor_state(
2237 &r#"
2238 one
2239 two
2240 three
2241 ˇfour
2242 five
2243 sixˇ
2244 seven
2245 eight
2246 nine
2247 ten
2248 "#
2249 .unindent(),
2250 );
2251
2252 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2253 cx.assert_editor_state(
2254 &r#"
2255 ˇone
2256 two
2257 threeˇ
2258 four
2259 five
2260 six
2261 seven
2262 eight
2263 nine
2264 ten
2265 "#
2266 .unindent(),
2267 );
2268
2269 // Test select collapsing
2270 cx.update_editor(|editor, window, cx| {
2271 editor.move_page_down(&MovePageDown::default(), window, cx);
2272 editor.move_page_down(&MovePageDown::default(), window, cx);
2273 editor.move_page_down(&MovePageDown::default(), window, cx);
2274 });
2275 cx.assert_editor_state(
2276 &r#"
2277 one
2278 two
2279 three
2280 four
2281 five
2282 six
2283 seven
2284 eight
2285 nine
2286 ˇten
2287 ˇ"#
2288 .unindent(),
2289 );
2290}
2291
2292#[gpui::test]
2293async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2294 init_test(cx, |_| {});
2295 let mut cx = EditorTestContext::new(cx).await;
2296 cx.set_state("one «two threeˇ» four");
2297 cx.update_editor(|editor, window, cx| {
2298 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
2299 assert_eq!(editor.text(cx), " four");
2300 });
2301}
2302
2303#[gpui::test]
2304fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2305 init_test(cx, |_| {});
2306
2307 let editor = cx.add_window(|window, cx| {
2308 let buffer = MultiBuffer::build_simple("one two three four", cx);
2309 build_editor(buffer.clone(), window, cx)
2310 });
2311
2312 _ = editor.update(cx, |editor, window, cx| {
2313 editor.change_selections(None, window, cx, |s| {
2314 s.select_display_ranges([
2315 // an empty selection - the preceding word fragment is deleted
2316 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2317 // characters selected - they are deleted
2318 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2319 ])
2320 });
2321 editor.delete_to_previous_word_start(
2322 &DeleteToPreviousWordStart {
2323 ignore_newlines: false,
2324 },
2325 window,
2326 cx,
2327 );
2328 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2329 });
2330
2331 _ = editor.update(cx, |editor, window, cx| {
2332 editor.change_selections(None, window, cx, |s| {
2333 s.select_display_ranges([
2334 // an empty selection - the following word fragment is deleted
2335 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2336 // characters selected - they are deleted
2337 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2338 ])
2339 });
2340 editor.delete_to_next_word_end(
2341 &DeleteToNextWordEnd {
2342 ignore_newlines: false,
2343 },
2344 window,
2345 cx,
2346 );
2347 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2348 });
2349}
2350
2351#[gpui::test]
2352fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2353 init_test(cx, |_| {});
2354
2355 let editor = cx.add_window(|window, cx| {
2356 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2357 build_editor(buffer.clone(), window, cx)
2358 });
2359 let del_to_prev_word_start = DeleteToPreviousWordStart {
2360 ignore_newlines: false,
2361 };
2362 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2363 ignore_newlines: true,
2364 };
2365
2366 _ = editor.update(cx, |editor, window, cx| {
2367 editor.change_selections(None, window, cx, |s| {
2368 s.select_display_ranges([
2369 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2370 ])
2371 });
2372 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2373 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2374 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2375 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2376 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2377 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2378 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2379 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2380 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2381 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2382 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2383 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2384 });
2385}
2386
2387#[gpui::test]
2388fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2389 init_test(cx, |_| {});
2390
2391 let editor = cx.add_window(|window, cx| {
2392 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2393 build_editor(buffer.clone(), window, cx)
2394 });
2395 let del_to_next_word_end = DeleteToNextWordEnd {
2396 ignore_newlines: false,
2397 };
2398 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2399 ignore_newlines: true,
2400 };
2401
2402 _ = editor.update(cx, |editor, window, cx| {
2403 editor.change_selections(None, window, cx, |s| {
2404 s.select_display_ranges([
2405 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2406 ])
2407 });
2408 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2409 assert_eq!(
2410 editor.buffer.read(cx).read(cx).text(),
2411 "one\n two\nthree\n four"
2412 );
2413 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2414 assert_eq!(
2415 editor.buffer.read(cx).read(cx).text(),
2416 "\n two\nthree\n four"
2417 );
2418 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2419 assert_eq!(
2420 editor.buffer.read(cx).read(cx).text(),
2421 "two\nthree\n four"
2422 );
2423 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2424 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2425 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2426 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2427 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2428 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2429 });
2430}
2431
2432#[gpui::test]
2433fn test_newline(cx: &mut TestAppContext) {
2434 init_test(cx, |_| {});
2435
2436 let editor = cx.add_window(|window, cx| {
2437 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2438 build_editor(buffer.clone(), window, cx)
2439 });
2440
2441 _ = editor.update(cx, |editor, window, cx| {
2442 editor.change_selections(None, window, cx, |s| {
2443 s.select_display_ranges([
2444 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2445 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2446 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2447 ])
2448 });
2449
2450 editor.newline(&Newline, window, cx);
2451 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2452 });
2453}
2454
2455#[gpui::test]
2456fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2457 init_test(cx, |_| {});
2458
2459 let editor = cx.add_window(|window, cx| {
2460 let buffer = MultiBuffer::build_simple(
2461 "
2462 a
2463 b(
2464 X
2465 )
2466 c(
2467 X
2468 )
2469 "
2470 .unindent()
2471 .as_str(),
2472 cx,
2473 );
2474 let mut editor = build_editor(buffer.clone(), window, cx);
2475 editor.change_selections(None, window, cx, |s| {
2476 s.select_ranges([
2477 Point::new(2, 4)..Point::new(2, 5),
2478 Point::new(5, 4)..Point::new(5, 5),
2479 ])
2480 });
2481 editor
2482 });
2483
2484 _ = editor.update(cx, |editor, window, cx| {
2485 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2486 editor.buffer.update(cx, |buffer, cx| {
2487 buffer.edit(
2488 [
2489 (Point::new(1, 2)..Point::new(3, 0), ""),
2490 (Point::new(4, 2)..Point::new(6, 0), ""),
2491 ],
2492 None,
2493 cx,
2494 );
2495 assert_eq!(
2496 buffer.read(cx).text(),
2497 "
2498 a
2499 b()
2500 c()
2501 "
2502 .unindent()
2503 );
2504 });
2505 assert_eq!(
2506 editor.selections.ranges(cx),
2507 &[
2508 Point::new(1, 2)..Point::new(1, 2),
2509 Point::new(2, 2)..Point::new(2, 2),
2510 ],
2511 );
2512
2513 editor.newline(&Newline, window, cx);
2514 assert_eq!(
2515 editor.text(cx),
2516 "
2517 a
2518 b(
2519 )
2520 c(
2521 )
2522 "
2523 .unindent()
2524 );
2525
2526 // The selections are moved after the inserted newlines
2527 assert_eq!(
2528 editor.selections.ranges(cx),
2529 &[
2530 Point::new(2, 0)..Point::new(2, 0),
2531 Point::new(4, 0)..Point::new(4, 0),
2532 ],
2533 );
2534 });
2535}
2536
2537#[gpui::test]
2538async fn test_newline_above(cx: &mut TestAppContext) {
2539 init_test(cx, |settings| {
2540 settings.defaults.tab_size = NonZeroU32::new(4)
2541 });
2542
2543 let language = Arc::new(
2544 Language::new(
2545 LanguageConfig::default(),
2546 Some(tree_sitter_rust::LANGUAGE.into()),
2547 )
2548 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2549 .unwrap(),
2550 );
2551
2552 let mut cx = EditorTestContext::new(cx).await;
2553 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2554 cx.set_state(indoc! {"
2555 const a: ˇA = (
2556 (ˇ
2557 «const_functionˇ»(ˇ),
2558 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2559 )ˇ
2560 ˇ);ˇ
2561 "});
2562
2563 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2564 cx.assert_editor_state(indoc! {"
2565 ˇ
2566 const a: A = (
2567 ˇ
2568 (
2569 ˇ
2570 ˇ
2571 const_function(),
2572 ˇ
2573 ˇ
2574 ˇ
2575 ˇ
2576 something_else,
2577 ˇ
2578 )
2579 ˇ
2580 ˇ
2581 );
2582 "});
2583}
2584
2585#[gpui::test]
2586async fn test_newline_below(cx: &mut TestAppContext) {
2587 init_test(cx, |settings| {
2588 settings.defaults.tab_size = NonZeroU32::new(4)
2589 });
2590
2591 let language = Arc::new(
2592 Language::new(
2593 LanguageConfig::default(),
2594 Some(tree_sitter_rust::LANGUAGE.into()),
2595 )
2596 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2597 .unwrap(),
2598 );
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2602 cx.set_state(indoc! {"
2603 const a: ˇA = (
2604 (ˇ
2605 «const_functionˇ»(ˇ),
2606 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2607 )ˇ
2608 ˇ);ˇ
2609 "});
2610
2611 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2612 cx.assert_editor_state(indoc! {"
2613 const a: A = (
2614 ˇ
2615 (
2616 ˇ
2617 const_function(),
2618 ˇ
2619 ˇ
2620 something_else,
2621 ˇ
2622 ˇ
2623 ˇ
2624 ˇ
2625 )
2626 ˇ
2627 );
2628 ˇ
2629 ˇ
2630 "});
2631}
2632
2633#[gpui::test]
2634async fn test_newline_comments(cx: &mut TestAppContext) {
2635 init_test(cx, |settings| {
2636 settings.defaults.tab_size = NonZeroU32::new(4)
2637 });
2638
2639 let language = Arc::new(Language::new(
2640 LanguageConfig {
2641 line_comments: vec!["//".into()],
2642 ..LanguageConfig::default()
2643 },
2644 None,
2645 ));
2646 {
2647 let mut cx = EditorTestContext::new(cx).await;
2648 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2649 cx.set_state(indoc! {"
2650 // Fooˇ
2651 "});
2652
2653 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2654 cx.assert_editor_state(indoc! {"
2655 // Foo
2656 //ˇ
2657 "});
2658 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2659 cx.set_state(indoc! {"
2660 ˇ// Foo
2661 "});
2662 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2663 cx.assert_editor_state(indoc! {"
2664
2665 ˇ// Foo
2666 "});
2667 }
2668 // Ensure that comment continuations can be disabled.
2669 update_test_language_settings(cx, |settings| {
2670 settings.defaults.extend_comment_on_newline = Some(false);
2671 });
2672 let mut cx = EditorTestContext::new(cx).await;
2673 cx.set_state(indoc! {"
2674 // Fooˇ
2675 "});
2676 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2677 cx.assert_editor_state(indoc! {"
2678 // Foo
2679 ˇ
2680 "});
2681}
2682
2683#[gpui::test]
2684fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2685 init_test(cx, |_| {});
2686
2687 let editor = cx.add_window(|window, cx| {
2688 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2689 let mut editor = build_editor(buffer.clone(), window, cx);
2690 editor.change_selections(None, window, cx, |s| {
2691 s.select_ranges([3..4, 11..12, 19..20])
2692 });
2693 editor
2694 });
2695
2696 _ = editor.update(cx, |editor, window, cx| {
2697 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2698 editor.buffer.update(cx, |buffer, cx| {
2699 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2700 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2701 });
2702 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2703
2704 editor.insert("Z", window, cx);
2705 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2706
2707 // The selections are moved after the inserted characters
2708 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2709 });
2710}
2711
2712#[gpui::test]
2713async fn test_tab(cx: &mut TestAppContext) {
2714 init_test(cx, |settings| {
2715 settings.defaults.tab_size = NonZeroU32::new(3)
2716 });
2717
2718 let mut cx = EditorTestContext::new(cx).await;
2719 cx.set_state(indoc! {"
2720 ˇabˇc
2721 ˇ🏀ˇ🏀ˇefg
2722 dˇ
2723 "});
2724 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2725 cx.assert_editor_state(indoc! {"
2726 ˇab ˇc
2727 ˇ🏀 ˇ🏀 ˇefg
2728 d ˇ
2729 "});
2730
2731 cx.set_state(indoc! {"
2732 a
2733 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2734 "});
2735 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2736 cx.assert_editor_state(indoc! {"
2737 a
2738 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2739 "});
2740}
2741
2742#[gpui::test]
2743async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2744 init_test(cx, |_| {});
2745
2746 let mut cx = EditorTestContext::new(cx).await;
2747 let language = Arc::new(
2748 Language::new(
2749 LanguageConfig::default(),
2750 Some(tree_sitter_rust::LANGUAGE.into()),
2751 )
2752 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2753 .unwrap(),
2754 );
2755 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2756
2757 // cursors that are already at the suggested indent level insert
2758 // a soft tab. cursors that are to the left of the suggested indent
2759 // auto-indent their line.
2760 cx.set_state(indoc! {"
2761 ˇ
2762 const a: B = (
2763 c(
2764 d(
2765 ˇ
2766 )
2767 ˇ
2768 ˇ )
2769 );
2770 "});
2771 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2772 cx.assert_editor_state(indoc! {"
2773 ˇ
2774 const a: B = (
2775 c(
2776 d(
2777 ˇ
2778 )
2779 ˇ
2780 ˇ)
2781 );
2782 "});
2783
2784 // handle auto-indent when there are multiple cursors on the same line
2785 cx.set_state(indoc! {"
2786 const a: B = (
2787 c(
2788 ˇ ˇ
2789 ˇ )
2790 );
2791 "});
2792 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2793 cx.assert_editor_state(indoc! {"
2794 const a: B = (
2795 c(
2796 ˇ
2797 ˇ)
2798 );
2799 "});
2800}
2801
2802#[gpui::test]
2803async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2804 init_test(cx, |settings| {
2805 settings.defaults.tab_size = NonZeroU32::new(4)
2806 });
2807
2808 let language = Arc::new(
2809 Language::new(
2810 LanguageConfig::default(),
2811 Some(tree_sitter_rust::LANGUAGE.into()),
2812 )
2813 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2814 .unwrap(),
2815 );
2816
2817 let mut cx = EditorTestContext::new(cx).await;
2818 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2819 cx.set_state(indoc! {"
2820 fn a() {
2821 if b {
2822 \t ˇc
2823 }
2824 }
2825 "});
2826
2827 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2828 cx.assert_editor_state(indoc! {"
2829 fn a() {
2830 if b {
2831 ˇc
2832 }
2833 }
2834 "});
2835}
2836
2837#[gpui::test]
2838async fn test_indent_outdent(cx: &mut TestAppContext) {
2839 init_test(cx, |settings| {
2840 settings.defaults.tab_size = NonZeroU32::new(4);
2841 });
2842
2843 let mut cx = EditorTestContext::new(cx).await;
2844
2845 cx.set_state(indoc! {"
2846 «oneˇ» «twoˇ»
2847 three
2848 four
2849 "});
2850 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2851 cx.assert_editor_state(indoc! {"
2852 «oneˇ» «twoˇ»
2853 three
2854 four
2855 "});
2856
2857 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ» four
2869 "});
2870 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 t«hree
2874 ˇ» four
2875 "});
2876
2877 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2878 cx.assert_editor_state(indoc! {"
2879 one two
2880 t«hree
2881 ˇ» four
2882 "});
2883
2884 // Ensure that indenting/outdenting works when the cursor is at column 0.
2885 cx.set_state(indoc! {"
2886 one two
2887 ˇthree
2888 four
2889 "});
2890 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2891 cx.assert_editor_state(indoc! {"
2892 one two
2893 ˇthree
2894 four
2895 "});
2896
2897 cx.set_state(indoc! {"
2898 one two
2899 ˇ three
2900 four
2901 "});
2902 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2903 cx.assert_editor_state(indoc! {"
2904 one two
2905 ˇthree
2906 four
2907 "});
2908}
2909
2910#[gpui::test]
2911async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
2912 init_test(cx, |settings| {
2913 settings.defaults.hard_tabs = Some(true);
2914 });
2915
2916 let mut cx = EditorTestContext::new(cx).await;
2917
2918 // select two ranges on one line
2919 cx.set_state(indoc! {"
2920 «oneˇ» «twoˇ»
2921 three
2922 four
2923 "});
2924 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2925 cx.assert_editor_state(indoc! {"
2926 \t«oneˇ» «twoˇ»
2927 three
2928 four
2929 "});
2930 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2931 cx.assert_editor_state(indoc! {"
2932 \t\t«oneˇ» «twoˇ»
2933 three
2934 four
2935 "});
2936 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2937 cx.assert_editor_state(indoc! {"
2938 \t«oneˇ» «twoˇ»
2939 three
2940 four
2941 "});
2942 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2943 cx.assert_editor_state(indoc! {"
2944 «oneˇ» «twoˇ»
2945 three
2946 four
2947 "});
2948
2949 // select across a line ending
2950 cx.set_state(indoc! {"
2951 one two
2952 t«hree
2953 ˇ»four
2954 "});
2955 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2956 cx.assert_editor_state(indoc! {"
2957 one two
2958 \tt«hree
2959 ˇ»four
2960 "});
2961 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2962 cx.assert_editor_state(indoc! {"
2963 one two
2964 \t\tt«hree
2965 ˇ»four
2966 "});
2967 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2968 cx.assert_editor_state(indoc! {"
2969 one two
2970 \tt«hree
2971 ˇ»four
2972 "});
2973 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2974 cx.assert_editor_state(indoc! {"
2975 one two
2976 t«hree
2977 ˇ»four
2978 "});
2979
2980 // Ensure that indenting/outdenting works when the cursor is at column 0.
2981 cx.set_state(indoc! {"
2982 one two
2983 ˇthree
2984 four
2985 "});
2986 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2987 cx.assert_editor_state(indoc! {"
2988 one two
2989 ˇthree
2990 four
2991 "});
2992 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2993 cx.assert_editor_state(indoc! {"
2994 one two
2995 \tˇthree
2996 four
2997 "});
2998 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2999 cx.assert_editor_state(indoc! {"
3000 one two
3001 ˇthree
3002 four
3003 "});
3004}
3005
3006#[gpui::test]
3007fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3008 init_test(cx, |settings| {
3009 settings.languages.extend([
3010 (
3011 "TOML".into(),
3012 LanguageSettingsContent {
3013 tab_size: NonZeroU32::new(2),
3014 ..Default::default()
3015 },
3016 ),
3017 (
3018 "Rust".into(),
3019 LanguageSettingsContent {
3020 tab_size: NonZeroU32::new(4),
3021 ..Default::default()
3022 },
3023 ),
3024 ]);
3025 });
3026
3027 let toml_language = Arc::new(Language::new(
3028 LanguageConfig {
3029 name: "TOML".into(),
3030 ..Default::default()
3031 },
3032 None,
3033 ));
3034 let rust_language = Arc::new(Language::new(
3035 LanguageConfig {
3036 name: "Rust".into(),
3037 ..Default::default()
3038 },
3039 None,
3040 ));
3041
3042 let toml_buffer =
3043 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3044 let rust_buffer =
3045 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3046 let multibuffer = cx.new(|cx| {
3047 let mut multibuffer = MultiBuffer::new(ReadWrite);
3048 multibuffer.push_excerpts(
3049 toml_buffer.clone(),
3050 [ExcerptRange {
3051 context: Point::new(0, 0)..Point::new(2, 0),
3052 primary: None,
3053 }],
3054 cx,
3055 );
3056 multibuffer.push_excerpts(
3057 rust_buffer.clone(),
3058 [ExcerptRange {
3059 context: Point::new(0, 0)..Point::new(1, 0),
3060 primary: None,
3061 }],
3062 cx,
3063 );
3064 multibuffer
3065 });
3066
3067 cx.add_window(|window, cx| {
3068 let mut editor = build_editor(multibuffer, window, cx);
3069
3070 assert_eq!(
3071 editor.text(cx),
3072 indoc! {"
3073 a = 1
3074 b = 2
3075
3076 const c: usize = 3;
3077 "}
3078 );
3079
3080 select_ranges(
3081 &mut editor,
3082 indoc! {"
3083 «aˇ» = 1
3084 b = 2
3085
3086 «const c:ˇ» usize = 3;
3087 "},
3088 window,
3089 cx,
3090 );
3091
3092 editor.tab(&Tab, window, cx);
3093 assert_text_with_selections(
3094 &mut editor,
3095 indoc! {"
3096 «aˇ» = 1
3097 b = 2
3098
3099 «const c:ˇ» usize = 3;
3100 "},
3101 cx,
3102 );
3103 editor.tab_prev(&TabPrev, window, cx);
3104 assert_text_with_selections(
3105 &mut editor,
3106 indoc! {"
3107 «aˇ» = 1
3108 b = 2
3109
3110 «const c:ˇ» usize = 3;
3111 "},
3112 cx,
3113 );
3114
3115 editor
3116 });
3117}
3118
3119#[gpui::test]
3120async fn test_backspace(cx: &mut TestAppContext) {
3121 init_test(cx, |_| {});
3122
3123 let mut cx = EditorTestContext::new(cx).await;
3124
3125 // Basic backspace
3126 cx.set_state(indoc! {"
3127 onˇe two three
3128 fou«rˇ» five six
3129 seven «ˇeight nine
3130 »ten
3131 "});
3132 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3133 cx.assert_editor_state(indoc! {"
3134 oˇe two three
3135 fouˇ five six
3136 seven ˇten
3137 "});
3138
3139 // Test backspace inside and around indents
3140 cx.set_state(indoc! {"
3141 zero
3142 ˇone
3143 ˇtwo
3144 ˇ ˇ ˇ three
3145 ˇ ˇ four
3146 "});
3147 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3148 cx.assert_editor_state(indoc! {"
3149 zero
3150 ˇone
3151 ˇtwo
3152 ˇ threeˇ four
3153 "});
3154
3155 // Test backspace with line_mode set to true
3156 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3157 cx.set_state(indoc! {"
3158 The ˇquick ˇbrown
3159 fox jumps over
3160 the lazy dog
3161 ˇThe qu«ick bˇ»rown"});
3162 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3163 cx.assert_editor_state(indoc! {"
3164 ˇfox jumps over
3165 the lazy dogˇ"});
3166}
3167
3168#[gpui::test]
3169async fn test_delete(cx: &mut TestAppContext) {
3170 init_test(cx, |_| {});
3171
3172 let mut cx = EditorTestContext::new(cx).await;
3173 cx.set_state(indoc! {"
3174 onˇe two three
3175 fou«rˇ» five six
3176 seven «ˇeight nine
3177 »ten
3178 "});
3179 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3180 cx.assert_editor_state(indoc! {"
3181 onˇ two three
3182 fouˇ five six
3183 seven ˇten
3184 "});
3185
3186 // Test backspace with line_mode set to true
3187 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3188 cx.set_state(indoc! {"
3189 The ˇquick ˇbrown
3190 fox «ˇjum»ps over
3191 the lazy dog
3192 ˇThe qu«ick bˇ»rown"});
3193 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3194 cx.assert_editor_state("ˇthe lazy dogˇ");
3195}
3196
3197#[gpui::test]
3198fn test_delete_line(cx: &mut TestAppContext) {
3199 init_test(cx, |_| {});
3200
3201 let editor = cx.add_window(|window, cx| {
3202 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3203 build_editor(buffer, window, cx)
3204 });
3205 _ = editor.update(cx, |editor, window, cx| {
3206 editor.change_selections(None, window, cx, |s| {
3207 s.select_display_ranges([
3208 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3209 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3210 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3211 ])
3212 });
3213 editor.delete_line(&DeleteLine, window, cx);
3214 assert_eq!(editor.display_text(cx), "ghi");
3215 assert_eq!(
3216 editor.selections.display_ranges(cx),
3217 vec![
3218 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3219 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3220 ]
3221 );
3222 });
3223
3224 let editor = cx.add_window(|window, cx| {
3225 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3226 build_editor(buffer, window, cx)
3227 });
3228 _ = editor.update(cx, |editor, window, cx| {
3229 editor.change_selections(None, window, cx, |s| {
3230 s.select_display_ranges([
3231 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3232 ])
3233 });
3234 editor.delete_line(&DeleteLine, window, cx);
3235 assert_eq!(editor.display_text(cx), "ghi\n");
3236 assert_eq!(
3237 editor.selections.display_ranges(cx),
3238 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3239 );
3240 });
3241}
3242
3243#[gpui::test]
3244fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3245 init_test(cx, |_| {});
3246
3247 cx.add_window(|window, cx| {
3248 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3249 let mut editor = build_editor(buffer.clone(), window, cx);
3250 let buffer = buffer.read(cx).as_singleton().unwrap();
3251
3252 assert_eq!(
3253 editor.selections.ranges::<Point>(cx),
3254 &[Point::new(0, 0)..Point::new(0, 0)]
3255 );
3256
3257 // When on single line, replace newline at end by space
3258 editor.join_lines(&JoinLines, window, cx);
3259 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3260 assert_eq!(
3261 editor.selections.ranges::<Point>(cx),
3262 &[Point::new(0, 3)..Point::new(0, 3)]
3263 );
3264
3265 // When multiple lines are selected, remove newlines that are spanned by the selection
3266 editor.change_selections(None, window, cx, |s| {
3267 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3268 });
3269 editor.join_lines(&JoinLines, window, cx);
3270 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3271 assert_eq!(
3272 editor.selections.ranges::<Point>(cx),
3273 &[Point::new(0, 11)..Point::new(0, 11)]
3274 );
3275
3276 // Undo should be transactional
3277 editor.undo(&Undo, window, cx);
3278 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3279 assert_eq!(
3280 editor.selections.ranges::<Point>(cx),
3281 &[Point::new(0, 5)..Point::new(2, 2)]
3282 );
3283
3284 // When joining an empty line don't insert a space
3285 editor.change_selections(None, window, cx, |s| {
3286 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3287 });
3288 editor.join_lines(&JoinLines, window, cx);
3289 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3290 assert_eq!(
3291 editor.selections.ranges::<Point>(cx),
3292 [Point::new(2, 3)..Point::new(2, 3)]
3293 );
3294
3295 // We can remove trailing newlines
3296 editor.join_lines(&JoinLines, window, cx);
3297 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3298 assert_eq!(
3299 editor.selections.ranges::<Point>(cx),
3300 [Point::new(2, 3)..Point::new(2, 3)]
3301 );
3302
3303 // We don't blow up on the last line
3304 editor.join_lines(&JoinLines, window, cx);
3305 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3306 assert_eq!(
3307 editor.selections.ranges::<Point>(cx),
3308 [Point::new(2, 3)..Point::new(2, 3)]
3309 );
3310
3311 // reset to test indentation
3312 editor.buffer.update(cx, |buffer, cx| {
3313 buffer.edit(
3314 [
3315 (Point::new(1, 0)..Point::new(1, 2), " "),
3316 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3317 ],
3318 None,
3319 cx,
3320 )
3321 });
3322
3323 // We remove any leading spaces
3324 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3325 editor.change_selections(None, window, cx, |s| {
3326 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3327 });
3328 editor.join_lines(&JoinLines, window, cx);
3329 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3330
3331 // We don't insert a space for a line containing only spaces
3332 editor.join_lines(&JoinLines, window, cx);
3333 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3334
3335 // We ignore any leading tabs
3336 editor.join_lines(&JoinLines, window, cx);
3337 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3338
3339 editor
3340 });
3341}
3342
3343#[gpui::test]
3344fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3345 init_test(cx, |_| {});
3346
3347 cx.add_window(|window, cx| {
3348 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3349 let mut editor = build_editor(buffer.clone(), window, cx);
3350 let buffer = buffer.read(cx).as_singleton().unwrap();
3351
3352 editor.change_selections(None, window, cx, |s| {
3353 s.select_ranges([
3354 Point::new(0, 2)..Point::new(1, 1),
3355 Point::new(1, 2)..Point::new(1, 2),
3356 Point::new(3, 1)..Point::new(3, 2),
3357 ])
3358 });
3359
3360 editor.join_lines(&JoinLines, window, cx);
3361 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3362
3363 assert_eq!(
3364 editor.selections.ranges::<Point>(cx),
3365 [
3366 Point::new(0, 7)..Point::new(0, 7),
3367 Point::new(1, 3)..Point::new(1, 3)
3368 ]
3369 );
3370 editor
3371 });
3372}
3373
3374#[gpui::test]
3375async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3376 init_test(cx, |_| {});
3377
3378 let mut cx = EditorTestContext::new(cx).await;
3379
3380 let diff_base = r#"
3381 Line 0
3382 Line 1
3383 Line 2
3384 Line 3
3385 "#
3386 .unindent();
3387
3388 cx.set_state(
3389 &r#"
3390 ˇLine 0
3391 Line 1
3392 Line 2
3393 Line 3
3394 "#
3395 .unindent(),
3396 );
3397
3398 cx.set_head_text(&diff_base);
3399 executor.run_until_parked();
3400
3401 // Join lines
3402 cx.update_editor(|editor, window, cx| {
3403 editor.join_lines(&JoinLines, window, cx);
3404 });
3405 executor.run_until_parked();
3406
3407 cx.assert_editor_state(
3408 &r#"
3409 Line 0ˇ Line 1
3410 Line 2
3411 Line 3
3412 "#
3413 .unindent(),
3414 );
3415 // Join again
3416 cx.update_editor(|editor, window, cx| {
3417 editor.join_lines(&JoinLines, window, cx);
3418 });
3419 executor.run_until_parked();
3420
3421 cx.assert_editor_state(
3422 &r#"
3423 Line 0 Line 1ˇ Line 2
3424 Line 3
3425 "#
3426 .unindent(),
3427 );
3428}
3429
3430#[gpui::test]
3431async fn test_custom_newlines_cause_no_false_positive_diffs(
3432 executor: BackgroundExecutor,
3433 cx: &mut TestAppContext,
3434) {
3435 init_test(cx, |_| {});
3436 let mut cx = EditorTestContext::new(cx).await;
3437 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3438 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3439 executor.run_until_parked();
3440
3441 cx.update_editor(|editor, window, cx| {
3442 let snapshot = editor.snapshot(window, cx);
3443 assert_eq!(
3444 snapshot
3445 .buffer_snapshot
3446 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3447 .collect::<Vec<_>>(),
3448 Vec::new(),
3449 "Should not have any diffs for files with custom newlines"
3450 );
3451 });
3452}
3453
3454#[gpui::test]
3455async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3456 init_test(cx, |_| {});
3457
3458 let mut cx = EditorTestContext::new(cx).await;
3459
3460 // Test sort_lines_case_insensitive()
3461 cx.set_state(indoc! {"
3462 «z
3463 y
3464 x
3465 Z
3466 Y
3467 Xˇ»
3468 "});
3469 cx.update_editor(|e, window, cx| {
3470 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3471 });
3472 cx.assert_editor_state(indoc! {"
3473 «x
3474 X
3475 y
3476 Y
3477 z
3478 Zˇ»
3479 "});
3480
3481 // Test reverse_lines()
3482 cx.set_state(indoc! {"
3483 «5
3484 4
3485 3
3486 2
3487 1ˇ»
3488 "});
3489 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3490 cx.assert_editor_state(indoc! {"
3491 «1
3492 2
3493 3
3494 4
3495 5ˇ»
3496 "});
3497
3498 // Skip testing shuffle_line()
3499
3500 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3501 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3502
3503 // Don't manipulate when cursor is on single line, but expand the selection
3504 cx.set_state(indoc! {"
3505 ddˇdd
3506 ccc
3507 bb
3508 a
3509 "});
3510 cx.update_editor(|e, window, cx| {
3511 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3512 });
3513 cx.assert_editor_state(indoc! {"
3514 «ddddˇ»
3515 ccc
3516 bb
3517 a
3518 "});
3519
3520 // Basic manipulate case
3521 // Start selection moves to column 0
3522 // End of selection shrinks to fit shorter line
3523 cx.set_state(indoc! {"
3524 dd«d
3525 ccc
3526 bb
3527 aaaaaˇ»
3528 "});
3529 cx.update_editor(|e, window, cx| {
3530 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3531 });
3532 cx.assert_editor_state(indoc! {"
3533 «aaaaa
3534 bb
3535 ccc
3536 dddˇ»
3537 "});
3538
3539 // Manipulate case with newlines
3540 cx.set_state(indoc! {"
3541 dd«d
3542 ccc
3543
3544 bb
3545 aaaaa
3546
3547 ˇ»
3548 "});
3549 cx.update_editor(|e, window, cx| {
3550 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3551 });
3552 cx.assert_editor_state(indoc! {"
3553 «
3554
3555 aaaaa
3556 bb
3557 ccc
3558 dddˇ»
3559
3560 "});
3561
3562 // Adding new line
3563 cx.set_state(indoc! {"
3564 aa«a
3565 bbˇ»b
3566 "});
3567 cx.update_editor(|e, window, cx| {
3568 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3569 });
3570 cx.assert_editor_state(indoc! {"
3571 «aaa
3572 bbb
3573 added_lineˇ»
3574 "});
3575
3576 // Removing line
3577 cx.set_state(indoc! {"
3578 aa«a
3579 bbbˇ»
3580 "});
3581 cx.update_editor(|e, window, cx| {
3582 e.manipulate_lines(window, cx, |lines| {
3583 lines.pop();
3584 })
3585 });
3586 cx.assert_editor_state(indoc! {"
3587 «aaaˇ»
3588 "});
3589
3590 // Removing all lines
3591 cx.set_state(indoc! {"
3592 aa«a
3593 bbbˇ»
3594 "});
3595 cx.update_editor(|e, window, cx| {
3596 e.manipulate_lines(window, cx, |lines| {
3597 lines.drain(..);
3598 })
3599 });
3600 cx.assert_editor_state(indoc! {"
3601 ˇ
3602 "});
3603}
3604
3605#[gpui::test]
3606async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3607 init_test(cx, |_| {});
3608
3609 let mut cx = EditorTestContext::new(cx).await;
3610
3611 // Consider continuous selection as single selection
3612 cx.set_state(indoc! {"
3613 Aaa«aa
3614 cˇ»c«c
3615 bb
3616 aaaˇ»aa
3617 "});
3618 cx.update_editor(|e, window, cx| {
3619 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3620 });
3621 cx.assert_editor_state(indoc! {"
3622 «Aaaaa
3623 ccc
3624 bb
3625 aaaaaˇ»
3626 "});
3627
3628 cx.set_state(indoc! {"
3629 Aaa«aa
3630 cˇ»c«c
3631 bb
3632 aaaˇ»aa
3633 "});
3634 cx.update_editor(|e, window, cx| {
3635 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3636 });
3637 cx.assert_editor_state(indoc! {"
3638 «Aaaaa
3639 ccc
3640 bbˇ»
3641 "});
3642
3643 // Consider non continuous selection as distinct dedup operations
3644 cx.set_state(indoc! {"
3645 «aaaaa
3646 bb
3647 aaaaa
3648 aaaaaˇ»
3649
3650 aaa«aaˇ»
3651 "});
3652 cx.update_editor(|e, window, cx| {
3653 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3654 });
3655 cx.assert_editor_state(indoc! {"
3656 «aaaaa
3657 bbˇ»
3658
3659 «aaaaaˇ»
3660 "});
3661}
3662
3663#[gpui::test]
3664async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3665 init_test(cx, |_| {});
3666
3667 let mut cx = EditorTestContext::new(cx).await;
3668
3669 cx.set_state(indoc! {"
3670 «Aaa
3671 aAa
3672 Aaaˇ»
3673 "});
3674 cx.update_editor(|e, window, cx| {
3675 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3676 });
3677 cx.assert_editor_state(indoc! {"
3678 «Aaa
3679 aAaˇ»
3680 "});
3681
3682 cx.set_state(indoc! {"
3683 «Aaa
3684 aAa
3685 aaAˇ»
3686 "});
3687 cx.update_editor(|e, window, cx| {
3688 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3689 });
3690 cx.assert_editor_state(indoc! {"
3691 «Aaaˇ»
3692 "});
3693}
3694
3695#[gpui::test]
3696async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3697 init_test(cx, |_| {});
3698
3699 let mut cx = EditorTestContext::new(cx).await;
3700
3701 // Manipulate with multiple selections on a single line
3702 cx.set_state(indoc! {"
3703 dd«dd
3704 cˇ»c«c
3705 bb
3706 aaaˇ»aa
3707 "});
3708 cx.update_editor(|e, window, cx| {
3709 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3710 });
3711 cx.assert_editor_state(indoc! {"
3712 «aaaaa
3713 bb
3714 ccc
3715 ddddˇ»
3716 "});
3717
3718 // Manipulate with multiple disjoin selections
3719 cx.set_state(indoc! {"
3720 5«
3721 4
3722 3
3723 2
3724 1ˇ»
3725
3726 dd«dd
3727 ccc
3728 bb
3729 aaaˇ»aa
3730 "});
3731 cx.update_editor(|e, window, cx| {
3732 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3733 });
3734 cx.assert_editor_state(indoc! {"
3735 «1
3736 2
3737 3
3738 4
3739 5ˇ»
3740
3741 «aaaaa
3742 bb
3743 ccc
3744 ddddˇ»
3745 "});
3746
3747 // Adding lines on each selection
3748 cx.set_state(indoc! {"
3749 2«
3750 1ˇ»
3751
3752 bb«bb
3753 aaaˇ»aa
3754 "});
3755 cx.update_editor(|e, window, cx| {
3756 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3757 });
3758 cx.assert_editor_state(indoc! {"
3759 «2
3760 1
3761 added lineˇ»
3762
3763 «bbbb
3764 aaaaa
3765 added lineˇ»
3766 "});
3767
3768 // Removing lines on each selection
3769 cx.set_state(indoc! {"
3770 2«
3771 1ˇ»
3772
3773 bb«bb
3774 aaaˇ»aa
3775 "});
3776 cx.update_editor(|e, window, cx| {
3777 e.manipulate_lines(window, cx, |lines| {
3778 lines.pop();
3779 })
3780 });
3781 cx.assert_editor_state(indoc! {"
3782 «2ˇ»
3783
3784 «bbbbˇ»
3785 "});
3786}
3787
3788#[gpui::test]
3789async fn test_manipulate_text(cx: &mut TestAppContext) {
3790 init_test(cx, |_| {});
3791
3792 let mut cx = EditorTestContext::new(cx).await;
3793
3794 // Test convert_to_upper_case()
3795 cx.set_state(indoc! {"
3796 «hello worldˇ»
3797 "});
3798 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3799 cx.assert_editor_state(indoc! {"
3800 «HELLO WORLDˇ»
3801 "});
3802
3803 // Test convert_to_lower_case()
3804 cx.set_state(indoc! {"
3805 «HELLO WORLDˇ»
3806 "});
3807 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3808 cx.assert_editor_state(indoc! {"
3809 «hello worldˇ»
3810 "});
3811
3812 // Test multiple line, single selection case
3813 cx.set_state(indoc! {"
3814 «The quick brown
3815 fox jumps over
3816 the lazy dogˇ»
3817 "});
3818 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3819 cx.assert_editor_state(indoc! {"
3820 «The Quick Brown
3821 Fox Jumps Over
3822 The Lazy Dogˇ»
3823 "});
3824
3825 // Test multiple line, single selection case
3826 cx.set_state(indoc! {"
3827 «The quick brown
3828 fox jumps over
3829 the lazy dogˇ»
3830 "});
3831 cx.update_editor(|e, window, cx| {
3832 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3833 });
3834 cx.assert_editor_state(indoc! {"
3835 «TheQuickBrown
3836 FoxJumpsOver
3837 TheLazyDogˇ»
3838 "});
3839
3840 // From here on out, test more complex cases of manipulate_text()
3841
3842 // Test no selection case - should affect words cursors are in
3843 // Cursor at beginning, middle, and end of word
3844 cx.set_state(indoc! {"
3845 ˇhello big beauˇtiful worldˇ
3846 "});
3847 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3848 cx.assert_editor_state(indoc! {"
3849 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3850 "});
3851
3852 // Test multiple selections on a single line and across multiple lines
3853 cx.set_state(indoc! {"
3854 «Theˇ» quick «brown
3855 foxˇ» jumps «overˇ»
3856 the «lazyˇ» dog
3857 "});
3858 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3859 cx.assert_editor_state(indoc! {"
3860 «THEˇ» quick «BROWN
3861 FOXˇ» jumps «OVERˇ»
3862 the «LAZYˇ» dog
3863 "});
3864
3865 // Test case where text length grows
3866 cx.set_state(indoc! {"
3867 «tschüߡ»
3868 "});
3869 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3870 cx.assert_editor_state(indoc! {"
3871 «TSCHÜSSˇ»
3872 "});
3873
3874 // Test to make sure we don't crash when text shrinks
3875 cx.set_state(indoc! {"
3876 aaa_bbbˇ
3877 "});
3878 cx.update_editor(|e, window, cx| {
3879 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3880 });
3881 cx.assert_editor_state(indoc! {"
3882 «aaaBbbˇ»
3883 "});
3884
3885 // Test to make sure we all aware of the fact that each word can grow and shrink
3886 // Final selections should be aware of this fact
3887 cx.set_state(indoc! {"
3888 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3889 "});
3890 cx.update_editor(|e, window, cx| {
3891 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3892 });
3893 cx.assert_editor_state(indoc! {"
3894 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3895 "});
3896
3897 cx.set_state(indoc! {"
3898 «hElLo, WoRld!ˇ»
3899 "});
3900 cx.update_editor(|e, window, cx| {
3901 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3902 });
3903 cx.assert_editor_state(indoc! {"
3904 «HeLlO, wOrLD!ˇ»
3905 "});
3906}
3907
3908#[gpui::test]
3909fn test_duplicate_line(cx: &mut TestAppContext) {
3910 init_test(cx, |_| {});
3911
3912 let editor = cx.add_window(|window, cx| {
3913 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3914 build_editor(buffer, window, cx)
3915 });
3916 _ = editor.update(cx, |editor, window, cx| {
3917 editor.change_selections(None, window, cx, |s| {
3918 s.select_display_ranges([
3919 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3920 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3921 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3922 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3923 ])
3924 });
3925 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3926 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3927 assert_eq!(
3928 editor.selections.display_ranges(cx),
3929 vec![
3930 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3931 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3932 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3933 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3934 ]
3935 );
3936 });
3937
3938 let editor = cx.add_window(|window, cx| {
3939 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3940 build_editor(buffer, window, cx)
3941 });
3942 _ = editor.update(cx, |editor, window, cx| {
3943 editor.change_selections(None, window, cx, |s| {
3944 s.select_display_ranges([
3945 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3946 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3947 ])
3948 });
3949 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3950 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3951 assert_eq!(
3952 editor.selections.display_ranges(cx),
3953 vec![
3954 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3955 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3956 ]
3957 );
3958 });
3959
3960 // With `move_upwards` the selections stay in place, except for
3961 // the lines inserted above them
3962 let editor = cx.add_window(|window, cx| {
3963 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3964 build_editor(buffer, window, cx)
3965 });
3966 _ = editor.update(cx, |editor, window, cx| {
3967 editor.change_selections(None, window, cx, |s| {
3968 s.select_display_ranges([
3969 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3970 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3971 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3972 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3973 ])
3974 });
3975 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3976 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3977 assert_eq!(
3978 editor.selections.display_ranges(cx),
3979 vec![
3980 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3981 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3982 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3983 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3984 ]
3985 );
3986 });
3987
3988 let editor = cx.add_window(|window, cx| {
3989 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3990 build_editor(buffer, window, cx)
3991 });
3992 _ = editor.update(cx, |editor, window, cx| {
3993 editor.change_selections(None, window, cx, |s| {
3994 s.select_display_ranges([
3995 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3996 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3997 ])
3998 });
3999 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4000 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4001 assert_eq!(
4002 editor.selections.display_ranges(cx),
4003 vec![
4004 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4005 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4006 ]
4007 );
4008 });
4009
4010 let editor = cx.add_window(|window, cx| {
4011 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4012 build_editor(buffer, window, cx)
4013 });
4014 _ = editor.update(cx, |editor, window, cx| {
4015 editor.change_selections(None, window, cx, |s| {
4016 s.select_display_ranges([
4017 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4018 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4019 ])
4020 });
4021 editor.duplicate_selection(&DuplicateSelection, window, cx);
4022 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4023 assert_eq!(
4024 editor.selections.display_ranges(cx),
4025 vec![
4026 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4027 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4028 ]
4029 );
4030 });
4031}
4032
4033#[gpui::test]
4034fn test_move_line_up_down(cx: &mut TestAppContext) {
4035 init_test(cx, |_| {});
4036
4037 let editor = cx.add_window(|window, cx| {
4038 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4039 build_editor(buffer, window, cx)
4040 });
4041 _ = editor.update(cx, |editor, window, cx| {
4042 editor.fold_creases(
4043 vec![
4044 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4045 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4046 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4047 ],
4048 true,
4049 window,
4050 cx,
4051 );
4052 editor.change_selections(None, window, cx, |s| {
4053 s.select_display_ranges([
4054 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4055 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4056 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4057 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4058 ])
4059 });
4060 assert_eq!(
4061 editor.display_text(cx),
4062 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4063 );
4064
4065 editor.move_line_up(&MoveLineUp, window, cx);
4066 assert_eq!(
4067 editor.display_text(cx),
4068 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4069 );
4070 assert_eq!(
4071 editor.selections.display_ranges(cx),
4072 vec![
4073 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4074 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4075 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4076 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4077 ]
4078 );
4079 });
4080
4081 _ = editor.update(cx, |editor, window, cx| {
4082 editor.move_line_down(&MoveLineDown, window, cx);
4083 assert_eq!(
4084 editor.display_text(cx),
4085 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4086 );
4087 assert_eq!(
4088 editor.selections.display_ranges(cx),
4089 vec![
4090 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4091 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4092 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4093 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4094 ]
4095 );
4096 });
4097
4098 _ = editor.update(cx, |editor, window, cx| {
4099 editor.move_line_down(&MoveLineDown, window, cx);
4100 assert_eq!(
4101 editor.display_text(cx),
4102 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4103 );
4104 assert_eq!(
4105 editor.selections.display_ranges(cx),
4106 vec![
4107 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4108 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4109 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4110 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4111 ]
4112 );
4113 });
4114
4115 _ = editor.update(cx, |editor, window, cx| {
4116 editor.move_line_up(&MoveLineUp, window, cx);
4117 assert_eq!(
4118 editor.display_text(cx),
4119 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4120 );
4121 assert_eq!(
4122 editor.selections.display_ranges(cx),
4123 vec![
4124 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4125 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4126 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4127 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4128 ]
4129 );
4130 });
4131}
4132
4133#[gpui::test]
4134fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4135 init_test(cx, |_| {});
4136
4137 let editor = cx.add_window(|window, cx| {
4138 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4139 build_editor(buffer, window, cx)
4140 });
4141 _ = editor.update(cx, |editor, window, cx| {
4142 let snapshot = editor.buffer.read(cx).snapshot(cx);
4143 editor.insert_blocks(
4144 [BlockProperties {
4145 style: BlockStyle::Fixed,
4146 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4147 height: 1,
4148 render: Arc::new(|_| div().into_any()),
4149 priority: 0,
4150 }],
4151 Some(Autoscroll::fit()),
4152 cx,
4153 );
4154 editor.change_selections(None, window, cx, |s| {
4155 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4156 });
4157 editor.move_line_down(&MoveLineDown, window, cx);
4158 });
4159}
4160
4161#[gpui::test]
4162async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4163 init_test(cx, |_| {});
4164
4165 let mut cx = EditorTestContext::new(cx).await;
4166 cx.set_state(
4167 &"
4168 ˇzero
4169 one
4170 two
4171 three
4172 four
4173 five
4174 "
4175 .unindent(),
4176 );
4177
4178 // Create a four-line block that replaces three lines of text.
4179 cx.update_editor(|editor, window, cx| {
4180 let snapshot = editor.snapshot(window, cx);
4181 let snapshot = &snapshot.buffer_snapshot;
4182 let placement = BlockPlacement::Replace(
4183 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4184 );
4185 editor.insert_blocks(
4186 [BlockProperties {
4187 placement,
4188 height: 4,
4189 style: BlockStyle::Sticky,
4190 render: Arc::new(|_| gpui::div().into_any_element()),
4191 priority: 0,
4192 }],
4193 None,
4194 cx,
4195 );
4196 });
4197
4198 // Move down so that the cursor touches the block.
4199 cx.update_editor(|editor, window, cx| {
4200 editor.move_down(&Default::default(), window, cx);
4201 });
4202 cx.assert_editor_state(
4203 &"
4204 zero
4205 «one
4206 two
4207 threeˇ»
4208 four
4209 five
4210 "
4211 .unindent(),
4212 );
4213
4214 // Move down past the block.
4215 cx.update_editor(|editor, window, cx| {
4216 editor.move_down(&Default::default(), window, cx);
4217 });
4218 cx.assert_editor_state(
4219 &"
4220 zero
4221 one
4222 two
4223 three
4224 ˇfour
4225 five
4226 "
4227 .unindent(),
4228 );
4229}
4230
4231#[gpui::test]
4232fn test_transpose(cx: &mut TestAppContext) {
4233 init_test(cx, |_| {});
4234
4235 _ = cx.add_window(|window, cx| {
4236 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4237 editor.set_style(EditorStyle::default(), window, cx);
4238 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4239 editor.transpose(&Default::default(), window, cx);
4240 assert_eq!(editor.text(cx), "bac");
4241 assert_eq!(editor.selections.ranges(cx), [2..2]);
4242
4243 editor.transpose(&Default::default(), window, cx);
4244 assert_eq!(editor.text(cx), "bca");
4245 assert_eq!(editor.selections.ranges(cx), [3..3]);
4246
4247 editor.transpose(&Default::default(), window, cx);
4248 assert_eq!(editor.text(cx), "bac");
4249 assert_eq!(editor.selections.ranges(cx), [3..3]);
4250
4251 editor
4252 });
4253
4254 _ = cx.add_window(|window, cx| {
4255 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4256 editor.set_style(EditorStyle::default(), window, cx);
4257 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4258 editor.transpose(&Default::default(), window, cx);
4259 assert_eq!(editor.text(cx), "acb\nde");
4260 assert_eq!(editor.selections.ranges(cx), [3..3]);
4261
4262 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4263 editor.transpose(&Default::default(), window, cx);
4264 assert_eq!(editor.text(cx), "acbd\ne");
4265 assert_eq!(editor.selections.ranges(cx), [5..5]);
4266
4267 editor.transpose(&Default::default(), window, cx);
4268 assert_eq!(editor.text(cx), "acbde\n");
4269 assert_eq!(editor.selections.ranges(cx), [6..6]);
4270
4271 editor.transpose(&Default::default(), window, cx);
4272 assert_eq!(editor.text(cx), "acbd\ne");
4273 assert_eq!(editor.selections.ranges(cx), [6..6]);
4274
4275 editor
4276 });
4277
4278 _ = cx.add_window(|window, cx| {
4279 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4280 editor.set_style(EditorStyle::default(), window, cx);
4281 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4282 editor.transpose(&Default::default(), window, cx);
4283 assert_eq!(editor.text(cx), "bacd\ne");
4284 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4285
4286 editor.transpose(&Default::default(), window, cx);
4287 assert_eq!(editor.text(cx), "bcade\n");
4288 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4289
4290 editor.transpose(&Default::default(), window, cx);
4291 assert_eq!(editor.text(cx), "bcda\ne");
4292 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4293
4294 editor.transpose(&Default::default(), window, cx);
4295 assert_eq!(editor.text(cx), "bcade\n");
4296 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4297
4298 editor.transpose(&Default::default(), window, cx);
4299 assert_eq!(editor.text(cx), "bcaed\n");
4300 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4301
4302 editor
4303 });
4304
4305 _ = cx.add_window(|window, cx| {
4306 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4307 editor.set_style(EditorStyle::default(), window, cx);
4308 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4309 editor.transpose(&Default::default(), window, cx);
4310 assert_eq!(editor.text(cx), "🏀🍐✋");
4311 assert_eq!(editor.selections.ranges(cx), [8..8]);
4312
4313 editor.transpose(&Default::default(), window, cx);
4314 assert_eq!(editor.text(cx), "🏀✋🍐");
4315 assert_eq!(editor.selections.ranges(cx), [11..11]);
4316
4317 editor.transpose(&Default::default(), window, cx);
4318 assert_eq!(editor.text(cx), "🏀🍐✋");
4319 assert_eq!(editor.selections.ranges(cx), [11..11]);
4320
4321 editor
4322 });
4323}
4324
4325#[gpui::test]
4326async fn test_rewrap(cx: &mut TestAppContext) {
4327 init_test(cx, |settings| {
4328 settings.languages.extend([
4329 (
4330 "Markdown".into(),
4331 LanguageSettingsContent {
4332 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4333 ..Default::default()
4334 },
4335 ),
4336 (
4337 "Plain Text".into(),
4338 LanguageSettingsContent {
4339 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4340 ..Default::default()
4341 },
4342 ),
4343 ])
4344 });
4345
4346 let mut cx = EditorTestContext::new(cx).await;
4347
4348 let language_with_c_comments = Arc::new(Language::new(
4349 LanguageConfig {
4350 line_comments: vec!["// ".into()],
4351 ..LanguageConfig::default()
4352 },
4353 None,
4354 ));
4355 let language_with_pound_comments = Arc::new(Language::new(
4356 LanguageConfig {
4357 line_comments: vec!["# ".into()],
4358 ..LanguageConfig::default()
4359 },
4360 None,
4361 ));
4362 let markdown_language = Arc::new(Language::new(
4363 LanguageConfig {
4364 name: "Markdown".into(),
4365 ..LanguageConfig::default()
4366 },
4367 None,
4368 ));
4369 let language_with_doc_comments = Arc::new(Language::new(
4370 LanguageConfig {
4371 line_comments: vec!["// ".into(), "/// ".into()],
4372 ..LanguageConfig::default()
4373 },
4374 Some(tree_sitter_rust::LANGUAGE.into()),
4375 ));
4376
4377 let plaintext_language = Arc::new(Language::new(
4378 LanguageConfig {
4379 name: "Plain Text".into(),
4380 ..LanguageConfig::default()
4381 },
4382 None,
4383 ));
4384
4385 assert_rewrap(
4386 indoc! {"
4387 // ˇ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.
4388 "},
4389 indoc! {"
4390 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4391 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4392 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4393 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4394 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4395 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4396 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4397 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4398 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4399 // porttitor id. Aliquam id accumsan eros.
4400 "},
4401 language_with_c_comments.clone(),
4402 &mut cx,
4403 );
4404
4405 // Test that rewrapping works inside of a selection
4406 assert_rewrap(
4407 indoc! {"
4408 «// 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.ˇ»
4409 "},
4410 indoc! {"
4411 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4412 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4413 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4414 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4415 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4416 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4417 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4418 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4419 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4420 // porttitor id. Aliquam id accumsan eros.ˇ»
4421 "},
4422 language_with_c_comments.clone(),
4423 &mut cx,
4424 );
4425
4426 // Test that cursors that expand to the same region are collapsed.
4427 assert_rewrap(
4428 indoc! {"
4429 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4430 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4431 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4432 // ˇ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.
4433 "},
4434 indoc! {"
4435 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4436 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4437 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4438 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4439 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4440 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4441 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4442 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4443 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4444 // porttitor id. Aliquam id accumsan eros.
4445 "},
4446 language_with_c_comments.clone(),
4447 &mut cx,
4448 );
4449
4450 // Test that non-contiguous selections are treated separately.
4451 assert_rewrap(
4452 indoc! {"
4453 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4454 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4455 //
4456 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4457 // ˇ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.
4458 "},
4459 indoc! {"
4460 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4461 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4462 // auctor, eu lacinia sapien scelerisque.
4463 //
4464 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4465 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4466 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4467 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4468 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4469 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4470 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4471 "},
4472 language_with_c_comments.clone(),
4473 &mut cx,
4474 );
4475
4476 // Test that different comment prefixes are supported.
4477 assert_rewrap(
4478 indoc! {"
4479 # ˇ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.
4480 "},
4481 indoc! {"
4482 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4483 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4484 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4485 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4486 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4487 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4488 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4489 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4490 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4491 # accumsan eros.
4492 "},
4493 language_with_pound_comments.clone(),
4494 &mut cx,
4495 );
4496
4497 // Test that rewrapping is ignored outside of comments in most languages.
4498 assert_rewrap(
4499 indoc! {"
4500 /// Adds two numbers.
4501 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4502 fn add(a: u32, b: u32) -> u32 {
4503 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ˇ
4504 }
4505 "},
4506 indoc! {"
4507 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4508 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4509 fn add(a: u32, b: u32) -> u32 {
4510 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ˇ
4511 }
4512 "},
4513 language_with_doc_comments.clone(),
4514 &mut cx,
4515 );
4516
4517 // Test that rewrapping works in Markdown and Plain Text languages.
4518 assert_rewrap(
4519 indoc! {"
4520 # Hello
4521
4522 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.
4523 "},
4524 indoc! {"
4525 # Hello
4526
4527 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4528 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4529 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4530 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4531 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4532 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4533 Integer sit amet scelerisque nisi.
4534 "},
4535 markdown_language,
4536 &mut cx,
4537 );
4538
4539 assert_rewrap(
4540 indoc! {"
4541 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.
4542 "},
4543 indoc! {"
4544 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4545 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4546 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4547 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4548 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4549 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4550 Integer sit amet scelerisque nisi.
4551 "},
4552 plaintext_language,
4553 &mut cx,
4554 );
4555
4556 // Test rewrapping unaligned comments in a selection.
4557 assert_rewrap(
4558 indoc! {"
4559 fn foo() {
4560 if true {
4561 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4562 // Praesent semper egestas tellus id dignissim.ˇ»
4563 do_something();
4564 } else {
4565 //
4566 }
4567 }
4568 "},
4569 indoc! {"
4570 fn foo() {
4571 if true {
4572 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4573 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4574 // egestas tellus id dignissim.ˇ»
4575 do_something();
4576 } else {
4577 //
4578 }
4579 }
4580 "},
4581 language_with_doc_comments.clone(),
4582 &mut cx,
4583 );
4584
4585 assert_rewrap(
4586 indoc! {"
4587 fn foo() {
4588 if true {
4589 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4590 // Praesent semper egestas tellus id dignissim.»
4591 do_something();
4592 } else {
4593 //
4594 }
4595
4596 }
4597 "},
4598 indoc! {"
4599 fn foo() {
4600 if true {
4601 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4602 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4603 // egestas tellus id dignissim.»
4604 do_something();
4605 } else {
4606 //
4607 }
4608
4609 }
4610 "},
4611 language_with_doc_comments.clone(),
4612 &mut cx,
4613 );
4614
4615 #[track_caller]
4616 fn assert_rewrap(
4617 unwrapped_text: &str,
4618 wrapped_text: &str,
4619 language: Arc<Language>,
4620 cx: &mut EditorTestContext,
4621 ) {
4622 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4623 cx.set_state(unwrapped_text);
4624 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4625 cx.assert_editor_state(wrapped_text);
4626 }
4627}
4628
4629#[gpui::test]
4630async fn test_clipboard(cx: &mut TestAppContext) {
4631 init_test(cx, |_| {});
4632
4633 let mut cx = EditorTestContext::new(cx).await;
4634
4635 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4636 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4637 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4638
4639 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4640 cx.set_state("two ˇfour ˇsix ˇ");
4641 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4642 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4643
4644 // Paste again but with only two cursors. Since the number of cursors doesn't
4645 // match the number of slices in the clipboard, the entire clipboard text
4646 // is pasted at each cursor.
4647 cx.set_state("ˇtwo one✅ four three six five ˇ");
4648 cx.update_editor(|e, window, cx| {
4649 e.handle_input("( ", window, cx);
4650 e.paste(&Paste, window, cx);
4651 e.handle_input(") ", window, cx);
4652 });
4653 cx.assert_editor_state(
4654 &([
4655 "( one✅ ",
4656 "three ",
4657 "five ) ˇtwo one✅ four three six five ( one✅ ",
4658 "three ",
4659 "five ) ˇ",
4660 ]
4661 .join("\n")),
4662 );
4663
4664 // Cut with three selections, one of which is full-line.
4665 cx.set_state(indoc! {"
4666 1«2ˇ»3
4667 4ˇ567
4668 «8ˇ»9"});
4669 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4670 cx.assert_editor_state(indoc! {"
4671 1ˇ3
4672 ˇ9"});
4673
4674 // Paste with three selections, noticing how the copied selection that was full-line
4675 // gets inserted before the second cursor.
4676 cx.set_state(indoc! {"
4677 1ˇ3
4678 9ˇ
4679 «oˇ»ne"});
4680 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4681 cx.assert_editor_state(indoc! {"
4682 12ˇ3
4683 4567
4684 9ˇ
4685 8ˇne"});
4686
4687 // Copy with a single cursor only, which writes the whole line into the clipboard.
4688 cx.set_state(indoc! {"
4689 The quick brown
4690 fox juˇmps over
4691 the lazy dog"});
4692 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4693 assert_eq!(
4694 cx.read_from_clipboard()
4695 .and_then(|item| item.text().as_deref().map(str::to_string)),
4696 Some("fox jumps over\n".to_string())
4697 );
4698
4699 // Paste with three selections, noticing how the copied full-line selection is inserted
4700 // before the empty selections but replaces the selection that is non-empty.
4701 cx.set_state(indoc! {"
4702 Tˇhe quick brown
4703 «foˇ»x jumps over
4704 tˇhe lazy dog"});
4705 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4706 cx.assert_editor_state(indoc! {"
4707 fox jumps over
4708 Tˇhe quick brown
4709 fox jumps over
4710 ˇx jumps over
4711 fox jumps over
4712 tˇhe lazy dog"});
4713}
4714
4715#[gpui::test]
4716async fn test_paste_multiline(cx: &mut TestAppContext) {
4717 init_test(cx, |_| {});
4718
4719 let mut cx = EditorTestContext::new(cx).await;
4720 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4721
4722 // Cut an indented block, without the leading whitespace.
4723 cx.set_state(indoc! {"
4724 const a: B = (
4725 c(),
4726 «d(
4727 e,
4728 f
4729 )ˇ»
4730 );
4731 "});
4732 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4733 cx.assert_editor_state(indoc! {"
4734 const a: B = (
4735 c(),
4736 ˇ
4737 );
4738 "});
4739
4740 // Paste it at the same position.
4741 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4742 cx.assert_editor_state(indoc! {"
4743 const a: B = (
4744 c(),
4745 d(
4746 e,
4747 f
4748 )ˇ
4749 );
4750 "});
4751
4752 // Paste it at a line with a lower indent level.
4753 cx.set_state(indoc! {"
4754 ˇ
4755 const a: B = (
4756 c(),
4757 );
4758 "});
4759 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4760 cx.assert_editor_state(indoc! {"
4761 d(
4762 e,
4763 f
4764 )ˇ
4765 const a: B = (
4766 c(),
4767 );
4768 "});
4769
4770 // Cut an indented block, with the leading whitespace.
4771 cx.set_state(indoc! {"
4772 const a: B = (
4773 c(),
4774 « d(
4775 e,
4776 f
4777 )
4778 ˇ»);
4779 "});
4780 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4781 cx.assert_editor_state(indoc! {"
4782 const a: B = (
4783 c(),
4784 ˇ);
4785 "});
4786
4787 // Paste it at the same position.
4788 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4789 cx.assert_editor_state(indoc! {"
4790 const a: B = (
4791 c(),
4792 d(
4793 e,
4794 f
4795 )
4796 ˇ);
4797 "});
4798
4799 // Paste it at a line with a higher indent level.
4800 cx.set_state(indoc! {"
4801 const a: B = (
4802 c(),
4803 d(
4804 e,
4805 fˇ
4806 )
4807 );
4808 "});
4809 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4810 cx.assert_editor_state(indoc! {"
4811 const a: B = (
4812 c(),
4813 d(
4814 e,
4815 f d(
4816 e,
4817 f
4818 )
4819 ˇ
4820 )
4821 );
4822 "});
4823}
4824
4825#[gpui::test]
4826async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4827 init_test(cx, |_| {});
4828
4829 cx.write_to_clipboard(ClipboardItem::new_string(
4830 " d(\n e\n );\n".into(),
4831 ));
4832
4833 let mut cx = EditorTestContext::new(cx).await;
4834 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4835
4836 cx.set_state(indoc! {"
4837 fn a() {
4838 b();
4839 if c() {
4840 ˇ
4841 }
4842 }
4843 "});
4844
4845 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4846 cx.assert_editor_state(indoc! {"
4847 fn a() {
4848 b();
4849 if c() {
4850 d(
4851 e
4852 );
4853 ˇ
4854 }
4855 }
4856 "});
4857
4858 cx.set_state(indoc! {"
4859 fn a() {
4860 b();
4861 ˇ
4862 }
4863 "});
4864
4865 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4866 cx.assert_editor_state(indoc! {"
4867 fn a() {
4868 b();
4869 d(
4870 e
4871 );
4872 ˇ
4873 }
4874 "});
4875}
4876
4877#[gpui::test]
4878fn test_select_all(cx: &mut TestAppContext) {
4879 init_test(cx, |_| {});
4880
4881 let editor = cx.add_window(|window, cx| {
4882 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4883 build_editor(buffer, window, cx)
4884 });
4885 _ = editor.update(cx, |editor, window, cx| {
4886 editor.select_all(&SelectAll, window, cx);
4887 assert_eq!(
4888 editor.selections.display_ranges(cx),
4889 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4890 );
4891 });
4892}
4893
4894#[gpui::test]
4895fn test_select_line(cx: &mut TestAppContext) {
4896 init_test(cx, |_| {});
4897
4898 let editor = cx.add_window(|window, cx| {
4899 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4900 build_editor(buffer, window, cx)
4901 });
4902 _ = editor.update(cx, |editor, window, cx| {
4903 editor.change_selections(None, window, cx, |s| {
4904 s.select_display_ranges([
4905 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4906 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4907 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4908 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4909 ])
4910 });
4911 editor.select_line(&SelectLine, window, cx);
4912 assert_eq!(
4913 editor.selections.display_ranges(cx),
4914 vec![
4915 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4916 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4917 ]
4918 );
4919 });
4920
4921 _ = editor.update(cx, |editor, window, cx| {
4922 editor.select_line(&SelectLine, window, cx);
4923 assert_eq!(
4924 editor.selections.display_ranges(cx),
4925 vec![
4926 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4927 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4928 ]
4929 );
4930 });
4931
4932 _ = editor.update(cx, |editor, window, cx| {
4933 editor.select_line(&SelectLine, window, cx);
4934 assert_eq!(
4935 editor.selections.display_ranges(cx),
4936 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4937 );
4938 });
4939}
4940
4941#[gpui::test]
4942async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4943 init_test(cx, |_| {});
4944 let mut cx = EditorTestContext::new(cx).await;
4945
4946 #[track_caller]
4947 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
4948 cx.set_state(initial_state);
4949 cx.update_editor(|e, window, cx| {
4950 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
4951 });
4952 cx.assert_editor_state(expected_state);
4953 }
4954
4955 // Selection starts and ends at the middle of lines, left-to-right
4956 test(
4957 &mut cx,
4958 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
4959 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4960 );
4961 // Same thing, right-to-left
4962 test(
4963 &mut cx,
4964 "aa\nb«b\ncc\ndd\neˇ»e\nff",
4965 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
4966 );
4967
4968 // Whole buffer, left-to-right, last line *doesn't* end with newline
4969 test(
4970 &mut cx,
4971 "«ˇaa\nbb\ncc\ndd\nee\nff»",
4972 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4973 );
4974 // Same thing, right-to-left
4975 test(
4976 &mut cx,
4977 "«aa\nbb\ncc\ndd\nee\nffˇ»",
4978 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
4979 );
4980
4981 // Whole buffer, left-to-right, last line ends with newline
4982 test(
4983 &mut cx,
4984 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
4985 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4986 );
4987 // Same thing, right-to-left
4988 test(
4989 &mut cx,
4990 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
4991 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
4992 );
4993
4994 // Starts at the end of a line, ends at the start of another
4995 test(
4996 &mut cx,
4997 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
4998 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
4999 );
5000}
5001
5002#[gpui::test]
5003async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5004 init_test(cx, |_| {});
5005
5006 let editor = cx.add_window(|window, cx| {
5007 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5008 build_editor(buffer, window, cx)
5009 });
5010
5011 // setup
5012 _ = editor.update(cx, |editor, window, cx| {
5013 editor.fold_creases(
5014 vec![
5015 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5016 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5017 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5018 ],
5019 true,
5020 window,
5021 cx,
5022 );
5023 assert_eq!(
5024 editor.display_text(cx),
5025 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5026 );
5027 });
5028
5029 _ = editor.update(cx, |editor, window, cx| {
5030 editor.change_selections(None, window, cx, |s| {
5031 s.select_display_ranges([
5032 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5033 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5034 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5035 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5036 ])
5037 });
5038 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5039 assert_eq!(
5040 editor.display_text(cx),
5041 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5042 );
5043 });
5044 EditorTestContext::for_editor(editor, cx)
5045 .await
5046 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5047
5048 _ = editor.update(cx, |editor, window, cx| {
5049 editor.change_selections(None, window, cx, |s| {
5050 s.select_display_ranges([
5051 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5052 ])
5053 });
5054 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5055 assert_eq!(
5056 editor.display_text(cx),
5057 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5058 );
5059 assert_eq!(
5060 editor.selections.display_ranges(cx),
5061 [
5062 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5063 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5064 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5065 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5066 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5067 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5068 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5069 ]
5070 );
5071 });
5072 EditorTestContext::for_editor(editor, cx)
5073 .await
5074 .assert_editor_state(
5075 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5076 );
5077}
5078
5079#[gpui::test]
5080async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5081 init_test(cx, |_| {});
5082
5083 let mut cx = EditorTestContext::new(cx).await;
5084
5085 cx.set_state(indoc!(
5086 r#"abc
5087 defˇghi
5088
5089 jk
5090 nlmo
5091 "#
5092 ));
5093
5094 cx.update_editor(|editor, window, cx| {
5095 editor.add_selection_above(&Default::default(), window, cx);
5096 });
5097
5098 cx.assert_editor_state(indoc!(
5099 r#"abcˇ
5100 defˇghi
5101
5102 jk
5103 nlmo
5104 "#
5105 ));
5106
5107 cx.update_editor(|editor, window, cx| {
5108 editor.add_selection_above(&Default::default(), window, cx);
5109 });
5110
5111 cx.assert_editor_state(indoc!(
5112 r#"abcˇ
5113 defˇghi
5114
5115 jk
5116 nlmo
5117 "#
5118 ));
5119
5120 cx.update_editor(|editor, window, cx| {
5121 editor.add_selection_below(&Default::default(), window, cx);
5122 });
5123
5124 cx.assert_editor_state(indoc!(
5125 r#"abc
5126 defˇghi
5127
5128 jk
5129 nlmo
5130 "#
5131 ));
5132
5133 cx.update_editor(|editor, window, cx| {
5134 editor.undo_selection(&Default::default(), window, cx);
5135 });
5136
5137 cx.assert_editor_state(indoc!(
5138 r#"abcˇ
5139 defˇghi
5140
5141 jk
5142 nlmo
5143 "#
5144 ));
5145
5146 cx.update_editor(|editor, window, cx| {
5147 editor.redo_selection(&Default::default(), window, cx);
5148 });
5149
5150 cx.assert_editor_state(indoc!(
5151 r#"abc
5152 defˇghi
5153
5154 jk
5155 nlmo
5156 "#
5157 ));
5158
5159 cx.update_editor(|editor, window, cx| {
5160 editor.add_selection_below(&Default::default(), window, cx);
5161 });
5162
5163 cx.assert_editor_state(indoc!(
5164 r#"abc
5165 defˇghi
5166
5167 jk
5168 nlmˇo
5169 "#
5170 ));
5171
5172 cx.update_editor(|editor, window, cx| {
5173 editor.add_selection_below(&Default::default(), window, cx);
5174 });
5175
5176 cx.assert_editor_state(indoc!(
5177 r#"abc
5178 defˇghi
5179
5180 jk
5181 nlmˇo
5182 "#
5183 ));
5184
5185 // change selections
5186 cx.set_state(indoc!(
5187 r#"abc
5188 def«ˇg»hi
5189
5190 jk
5191 nlmo
5192 "#
5193 ));
5194
5195 cx.update_editor(|editor, window, cx| {
5196 editor.add_selection_below(&Default::default(), window, cx);
5197 });
5198
5199 cx.assert_editor_state(indoc!(
5200 r#"abc
5201 def«ˇg»hi
5202
5203 jk
5204 nlm«ˇo»
5205 "#
5206 ));
5207
5208 cx.update_editor(|editor, window, cx| {
5209 editor.add_selection_below(&Default::default(), window, cx);
5210 });
5211
5212 cx.assert_editor_state(indoc!(
5213 r#"abc
5214 def«ˇg»hi
5215
5216 jk
5217 nlm«ˇo»
5218 "#
5219 ));
5220
5221 cx.update_editor(|editor, window, cx| {
5222 editor.add_selection_above(&Default::default(), window, cx);
5223 });
5224
5225 cx.assert_editor_state(indoc!(
5226 r#"abc
5227 def«ˇg»hi
5228
5229 jk
5230 nlmo
5231 "#
5232 ));
5233
5234 cx.update_editor(|editor, window, cx| {
5235 editor.add_selection_above(&Default::default(), window, cx);
5236 });
5237
5238 cx.assert_editor_state(indoc!(
5239 r#"abc
5240 def«ˇg»hi
5241
5242 jk
5243 nlmo
5244 "#
5245 ));
5246
5247 // Change selections again
5248 cx.set_state(indoc!(
5249 r#"a«bc
5250 defgˇ»hi
5251
5252 jk
5253 nlmo
5254 "#
5255 ));
5256
5257 cx.update_editor(|editor, window, cx| {
5258 editor.add_selection_below(&Default::default(), window, cx);
5259 });
5260
5261 cx.assert_editor_state(indoc!(
5262 r#"a«bcˇ»
5263 d«efgˇ»hi
5264
5265 j«kˇ»
5266 nlmo
5267 "#
5268 ));
5269
5270 cx.update_editor(|editor, window, cx| {
5271 editor.add_selection_below(&Default::default(), window, cx);
5272 });
5273 cx.assert_editor_state(indoc!(
5274 r#"a«bcˇ»
5275 d«efgˇ»hi
5276
5277 j«kˇ»
5278 n«lmoˇ»
5279 "#
5280 ));
5281 cx.update_editor(|editor, window, cx| {
5282 editor.add_selection_above(&Default::default(), window, cx);
5283 });
5284
5285 cx.assert_editor_state(indoc!(
5286 r#"a«bcˇ»
5287 d«efgˇ»hi
5288
5289 j«kˇ»
5290 nlmo
5291 "#
5292 ));
5293
5294 // Change selections again
5295 cx.set_state(indoc!(
5296 r#"abc
5297 d«ˇefghi
5298
5299 jk
5300 nlm»o
5301 "#
5302 ));
5303
5304 cx.update_editor(|editor, window, cx| {
5305 editor.add_selection_above(&Default::default(), window, cx);
5306 });
5307
5308 cx.assert_editor_state(indoc!(
5309 r#"a«ˇbc»
5310 d«ˇef»ghi
5311
5312 j«ˇk»
5313 n«ˇlm»o
5314 "#
5315 ));
5316
5317 cx.update_editor(|editor, window, cx| {
5318 editor.add_selection_below(&Default::default(), window, cx);
5319 });
5320
5321 cx.assert_editor_state(indoc!(
5322 r#"abc
5323 d«ˇef»ghi
5324
5325 j«ˇk»
5326 n«ˇlm»o
5327 "#
5328 ));
5329}
5330
5331#[gpui::test]
5332async fn test_select_next(cx: &mut TestAppContext) {
5333 init_test(cx, |_| {});
5334
5335 let mut cx = EditorTestContext::new(cx).await;
5336 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5337
5338 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5339 .unwrap();
5340 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5341
5342 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5343 .unwrap();
5344 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5345
5346 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5347 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5348
5349 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5350 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5351
5352 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5353 .unwrap();
5354 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5355
5356 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5357 .unwrap();
5358 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5359}
5360
5361#[gpui::test]
5362async fn test_select_all_matches(cx: &mut TestAppContext) {
5363 init_test(cx, |_| {});
5364
5365 let mut cx = EditorTestContext::new(cx).await;
5366
5367 // Test caret-only selections
5368 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5369 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5370 .unwrap();
5371 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5372
5373 // Test left-to-right selections
5374 cx.set_state("abc\n«abcˇ»\nabc");
5375 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5376 .unwrap();
5377 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5378
5379 // Test right-to-left selections
5380 cx.set_state("abc\n«ˇabc»\nabc");
5381 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5382 .unwrap();
5383 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5384
5385 // Test selecting whitespace with caret selection
5386 cx.set_state("abc\nˇ abc\nabc");
5387 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5388 .unwrap();
5389 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5390
5391 // Test selecting whitespace with left-to-right selection
5392 cx.set_state("abc\n«ˇ »abc\nabc");
5393 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5394 .unwrap();
5395 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5396
5397 // Test no matches with right-to-left selection
5398 cx.set_state("abc\n« ˇ»abc\nabc");
5399 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5400 .unwrap();
5401 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5402}
5403
5404#[gpui::test]
5405async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5406 init_test(cx, |_| {});
5407
5408 let mut cx = EditorTestContext::new(cx).await;
5409 cx.set_state(
5410 r#"let foo = 2;
5411lˇet foo = 2;
5412let fooˇ = 2;
5413let foo = 2;
5414let foo = ˇ2;"#,
5415 );
5416
5417 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5418 .unwrap();
5419 cx.assert_editor_state(
5420 r#"let foo = 2;
5421«letˇ» foo = 2;
5422let «fooˇ» = 2;
5423let foo = 2;
5424let foo = «2ˇ»;"#,
5425 );
5426
5427 // noop for multiple selections with different contents
5428 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5429 .unwrap();
5430 cx.assert_editor_state(
5431 r#"let foo = 2;
5432«letˇ» foo = 2;
5433let «fooˇ» = 2;
5434let foo = 2;
5435let foo = «2ˇ»;"#,
5436 );
5437}
5438
5439#[gpui::test]
5440async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5441 init_test(cx, |_| {});
5442
5443 let mut cx =
5444 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5445
5446 cx.assert_editor_state(indoc! {"
5447 ˇbbb
5448 ccc
5449
5450 bbb
5451 ccc
5452 "});
5453 cx.dispatch_action(SelectPrevious::default());
5454 cx.assert_editor_state(indoc! {"
5455 «bbbˇ»
5456 ccc
5457
5458 bbb
5459 ccc
5460 "});
5461 cx.dispatch_action(SelectPrevious::default());
5462 cx.assert_editor_state(indoc! {"
5463 «bbbˇ»
5464 ccc
5465
5466 «bbbˇ»
5467 ccc
5468 "});
5469}
5470
5471#[gpui::test]
5472async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5473 init_test(cx, |_| {});
5474
5475 let mut cx = EditorTestContext::new(cx).await;
5476 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5477
5478 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5479 .unwrap();
5480 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5481
5482 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5483 .unwrap();
5484 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5485
5486 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5487 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5488
5489 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5490 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5491
5492 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5493 .unwrap();
5494 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5495
5496 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5497 .unwrap();
5498 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5499
5500 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5501 .unwrap();
5502 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5503}
5504
5505#[gpui::test]
5506async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5507 init_test(cx, |_| {});
5508
5509 let mut cx = EditorTestContext::new(cx).await;
5510 cx.set_state("aˇ");
5511
5512 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5513 .unwrap();
5514 cx.assert_editor_state("«aˇ»");
5515 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5516 .unwrap();
5517 cx.assert_editor_state("«aˇ»");
5518}
5519
5520#[gpui::test]
5521async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5522 init_test(cx, |_| {});
5523
5524 let mut cx = EditorTestContext::new(cx).await;
5525 cx.set_state(
5526 r#"let foo = 2;
5527lˇet foo = 2;
5528let fooˇ = 2;
5529let foo = 2;
5530let foo = ˇ2;"#,
5531 );
5532
5533 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5534 .unwrap();
5535 cx.assert_editor_state(
5536 r#"let foo = 2;
5537«letˇ» foo = 2;
5538let «fooˇ» = 2;
5539let foo = 2;
5540let foo = «2ˇ»;"#,
5541 );
5542
5543 // noop for multiple selections with different contents
5544 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5545 .unwrap();
5546 cx.assert_editor_state(
5547 r#"let foo = 2;
5548«letˇ» foo = 2;
5549let «fooˇ» = 2;
5550let foo = 2;
5551let foo = «2ˇ»;"#,
5552 );
5553}
5554
5555#[gpui::test]
5556async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5557 init_test(cx, |_| {});
5558
5559 let mut cx = EditorTestContext::new(cx).await;
5560 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5561
5562 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5563 .unwrap();
5564 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5565
5566 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5567 .unwrap();
5568 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5569
5570 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5571 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5572
5573 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5574 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5575
5576 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5577 .unwrap();
5578 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5579
5580 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5581 .unwrap();
5582 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5583}
5584
5585#[gpui::test]
5586async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5587 init_test(cx, |_| {});
5588
5589 let language = Arc::new(Language::new(
5590 LanguageConfig::default(),
5591 Some(tree_sitter_rust::LANGUAGE.into()),
5592 ));
5593
5594 let text = r#"
5595 use mod1::mod2::{mod3, mod4};
5596
5597 fn fn_1(param1: bool, param2: &str) {
5598 let var1 = "text";
5599 }
5600 "#
5601 .unindent();
5602
5603 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5604 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5605 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5606
5607 editor
5608 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5609 .await;
5610
5611 editor.update_in(cx, |editor, window, cx| {
5612 editor.change_selections(None, window, cx, |s| {
5613 s.select_display_ranges([
5614 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5615 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5616 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5617 ]);
5618 });
5619 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5620 });
5621 editor.update(cx, |editor, cx| {
5622 assert_text_with_selections(
5623 editor,
5624 indoc! {r#"
5625 use mod1::mod2::{mod3, «mod4ˇ»};
5626
5627 fn fn_1«ˇ(param1: bool, param2: &str)» {
5628 let var1 = "«textˇ»";
5629 }
5630 "#},
5631 cx,
5632 );
5633 });
5634
5635 editor.update_in(cx, |editor, window, cx| {
5636 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5637 });
5638 editor.update(cx, |editor, cx| {
5639 assert_text_with_selections(
5640 editor,
5641 indoc! {r#"
5642 use mod1::mod2::«{mod3, mod4}ˇ»;
5643
5644 «ˇfn fn_1(param1: bool, param2: &str) {
5645 let var1 = "text";
5646 }»
5647 "#},
5648 cx,
5649 );
5650 });
5651
5652 editor.update_in(cx, |editor, window, cx| {
5653 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5654 });
5655 assert_eq!(
5656 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5657 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5658 );
5659
5660 // Trying to expand the selected syntax node one more time has no effect.
5661 editor.update_in(cx, |editor, window, cx| {
5662 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5663 });
5664 assert_eq!(
5665 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5666 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5667 );
5668
5669 editor.update_in(cx, |editor, window, cx| {
5670 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5671 });
5672 editor.update(cx, |editor, cx| {
5673 assert_text_with_selections(
5674 editor,
5675 indoc! {r#"
5676 use mod1::mod2::«{mod3, mod4}ˇ»;
5677
5678 «ˇfn fn_1(param1: bool, param2: &str) {
5679 let var1 = "text";
5680 }»
5681 "#},
5682 cx,
5683 );
5684 });
5685
5686 editor.update_in(cx, |editor, window, cx| {
5687 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5688 });
5689 editor.update(cx, |editor, cx| {
5690 assert_text_with_selections(
5691 editor,
5692 indoc! {r#"
5693 use mod1::mod2::{mod3, «mod4ˇ»};
5694
5695 fn fn_1«ˇ(param1: bool, param2: &str)» {
5696 let var1 = "«textˇ»";
5697 }
5698 "#},
5699 cx,
5700 );
5701 });
5702
5703 editor.update_in(cx, |editor, window, cx| {
5704 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5705 });
5706 editor.update(cx, |editor, cx| {
5707 assert_text_with_selections(
5708 editor,
5709 indoc! {r#"
5710 use mod1::mod2::{mod3, mo«ˇ»d4};
5711
5712 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5713 let var1 = "te«ˇ»xt";
5714 }
5715 "#},
5716 cx,
5717 );
5718 });
5719
5720 // Trying to shrink the selected syntax node one more time has no effect.
5721 editor.update_in(cx, |editor, window, cx| {
5722 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5723 });
5724 editor.update_in(cx, |editor, _, cx| {
5725 assert_text_with_selections(
5726 editor,
5727 indoc! {r#"
5728 use mod1::mod2::{mod3, mo«ˇ»d4};
5729
5730 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5731 let var1 = "te«ˇ»xt";
5732 }
5733 "#},
5734 cx,
5735 );
5736 });
5737
5738 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5739 // a fold.
5740 editor.update_in(cx, |editor, window, cx| {
5741 editor.fold_creases(
5742 vec![
5743 Crease::simple(
5744 Point::new(0, 21)..Point::new(0, 24),
5745 FoldPlaceholder::test(),
5746 ),
5747 Crease::simple(
5748 Point::new(3, 20)..Point::new(3, 22),
5749 FoldPlaceholder::test(),
5750 ),
5751 ],
5752 true,
5753 window,
5754 cx,
5755 );
5756 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5757 });
5758 editor.update(cx, |editor, cx| {
5759 assert_text_with_selections(
5760 editor,
5761 indoc! {r#"
5762 use mod1::mod2::«{mod3, mod4}ˇ»;
5763
5764 fn fn_1«ˇ(param1: bool, param2: &str)» {
5765 «let var1 = "text";ˇ»
5766 }
5767 "#},
5768 cx,
5769 );
5770 });
5771}
5772
5773#[gpui::test]
5774async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5775 init_test(cx, |_| {});
5776
5777 let base_text = r#"
5778 impl A {
5779 // this is an uncommitted comment
5780
5781 fn b() {
5782 c();
5783 }
5784
5785 // this is another uncommitted comment
5786
5787 fn d() {
5788 // e
5789 // f
5790 }
5791 }
5792
5793 fn g() {
5794 // h
5795 }
5796 "#
5797 .unindent();
5798
5799 let text = r#"
5800 ˇimpl A {
5801
5802 fn b() {
5803 c();
5804 }
5805
5806 fn d() {
5807 // e
5808 // f
5809 }
5810 }
5811
5812 fn g() {
5813 // h
5814 }
5815 "#
5816 .unindent();
5817
5818 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5819 cx.set_state(&text);
5820 cx.set_head_text(&base_text);
5821 cx.update_editor(|editor, window, cx| {
5822 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5823 });
5824
5825 cx.assert_state_with_diff(
5826 "
5827 ˇimpl A {
5828 - // this is an uncommitted comment
5829
5830 fn b() {
5831 c();
5832 }
5833
5834 - // this is another uncommitted comment
5835 -
5836 fn d() {
5837 // e
5838 // f
5839 }
5840 }
5841
5842 fn g() {
5843 // h
5844 }
5845 "
5846 .unindent(),
5847 );
5848
5849 let expected_display_text = "
5850 impl A {
5851 // this is an uncommitted comment
5852
5853 fn b() {
5854 ⋯
5855 }
5856
5857 // this is another uncommitted comment
5858
5859 fn d() {
5860 ⋯
5861 }
5862 }
5863
5864 fn g() {
5865 ⋯
5866 }
5867 "
5868 .unindent();
5869
5870 cx.update_editor(|editor, window, cx| {
5871 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5872 assert_eq!(editor.display_text(cx), expected_display_text);
5873 });
5874}
5875
5876#[gpui::test]
5877async fn test_autoindent(cx: &mut TestAppContext) {
5878 init_test(cx, |_| {});
5879
5880 let language = Arc::new(
5881 Language::new(
5882 LanguageConfig {
5883 brackets: BracketPairConfig {
5884 pairs: vec![
5885 BracketPair {
5886 start: "{".to_string(),
5887 end: "}".to_string(),
5888 close: false,
5889 surround: false,
5890 newline: true,
5891 },
5892 BracketPair {
5893 start: "(".to_string(),
5894 end: ")".to_string(),
5895 close: false,
5896 surround: false,
5897 newline: true,
5898 },
5899 ],
5900 ..Default::default()
5901 },
5902 ..Default::default()
5903 },
5904 Some(tree_sitter_rust::LANGUAGE.into()),
5905 )
5906 .with_indents_query(
5907 r#"
5908 (_ "(" ")" @end) @indent
5909 (_ "{" "}" @end) @indent
5910 "#,
5911 )
5912 .unwrap(),
5913 );
5914
5915 let text = "fn a() {}";
5916
5917 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5918 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5919 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5920 editor
5921 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5922 .await;
5923
5924 editor.update_in(cx, |editor, window, cx| {
5925 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5926 editor.newline(&Newline, window, cx);
5927 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5928 assert_eq!(
5929 editor.selections.ranges(cx),
5930 &[
5931 Point::new(1, 4)..Point::new(1, 4),
5932 Point::new(3, 4)..Point::new(3, 4),
5933 Point::new(5, 0)..Point::new(5, 0)
5934 ]
5935 );
5936 });
5937}
5938
5939#[gpui::test]
5940async fn test_autoindent_selections(cx: &mut TestAppContext) {
5941 init_test(cx, |_| {});
5942
5943 {
5944 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5945 cx.set_state(indoc! {"
5946 impl A {
5947
5948 fn b() {}
5949
5950 «fn c() {
5951
5952 }ˇ»
5953 }
5954 "});
5955
5956 cx.update_editor(|editor, window, cx| {
5957 editor.autoindent(&Default::default(), window, cx);
5958 });
5959
5960 cx.assert_editor_state(indoc! {"
5961 impl A {
5962
5963 fn b() {}
5964
5965 «fn c() {
5966
5967 }ˇ»
5968 }
5969 "});
5970 }
5971
5972 {
5973 let mut cx = EditorTestContext::new_multibuffer(
5974 cx,
5975 [indoc! { "
5976 impl A {
5977 «
5978 // a
5979 fn b(){}
5980 »
5981 «
5982 }
5983 fn c(){}
5984 »
5985 "}],
5986 );
5987
5988 let buffer = cx.update_editor(|editor, _, cx| {
5989 let buffer = editor.buffer().update(cx, |buffer, _| {
5990 buffer.all_buffers().iter().next().unwrap().clone()
5991 });
5992 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5993 buffer
5994 });
5995
5996 cx.run_until_parked();
5997 cx.update_editor(|editor, window, cx| {
5998 editor.select_all(&Default::default(), window, cx);
5999 editor.autoindent(&Default::default(), window, cx)
6000 });
6001 cx.run_until_parked();
6002
6003 cx.update(|_, cx| {
6004 pretty_assertions::assert_eq!(
6005 buffer.read(cx).text(),
6006 indoc! { "
6007 impl A {
6008
6009 // a
6010 fn b(){}
6011
6012
6013 }
6014 fn c(){}
6015
6016 " }
6017 )
6018 });
6019 }
6020}
6021
6022#[gpui::test]
6023async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6024 init_test(cx, |_| {});
6025
6026 let mut cx = EditorTestContext::new(cx).await;
6027
6028 let language = Arc::new(Language::new(
6029 LanguageConfig {
6030 brackets: BracketPairConfig {
6031 pairs: vec![
6032 BracketPair {
6033 start: "{".to_string(),
6034 end: "}".to_string(),
6035 close: true,
6036 surround: true,
6037 newline: true,
6038 },
6039 BracketPair {
6040 start: "(".to_string(),
6041 end: ")".to_string(),
6042 close: true,
6043 surround: true,
6044 newline: true,
6045 },
6046 BracketPair {
6047 start: "/*".to_string(),
6048 end: " */".to_string(),
6049 close: true,
6050 surround: true,
6051 newline: true,
6052 },
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: true,
6064 surround: true,
6065 newline: false,
6066 },
6067 BracketPair {
6068 start: "<".to_string(),
6069 end: ">".to_string(),
6070 close: false,
6071 surround: true,
6072 newline: true,
6073 },
6074 ],
6075 ..Default::default()
6076 },
6077 autoclose_before: "})]".to_string(),
6078 ..Default::default()
6079 },
6080 Some(tree_sitter_rust::LANGUAGE.into()),
6081 ));
6082
6083 cx.language_registry().add(language.clone());
6084 cx.update_buffer(|buffer, cx| {
6085 buffer.set_language(Some(language), cx);
6086 });
6087
6088 cx.set_state(
6089 &r#"
6090 🏀ˇ
6091 εˇ
6092 ❤️ˇ
6093 "#
6094 .unindent(),
6095 );
6096
6097 // autoclose multiple nested brackets at multiple cursors
6098 cx.update_editor(|editor, window, cx| {
6099 editor.handle_input("{", window, cx);
6100 editor.handle_input("{", window, cx);
6101 editor.handle_input("{", window, cx);
6102 });
6103 cx.assert_editor_state(
6104 &"
6105 🏀{{{ˇ}}}
6106 ε{{{ˇ}}}
6107 ❤️{{{ˇ}}}
6108 "
6109 .unindent(),
6110 );
6111
6112 // insert a different closing bracket
6113 cx.update_editor(|editor, window, cx| {
6114 editor.handle_input(")", window, cx);
6115 });
6116 cx.assert_editor_state(
6117 &"
6118 🏀{{{)ˇ}}}
6119 ε{{{)ˇ}}}
6120 ❤️{{{)ˇ}}}
6121 "
6122 .unindent(),
6123 );
6124
6125 // skip over the auto-closed brackets when typing a closing bracket
6126 cx.update_editor(|editor, window, cx| {
6127 editor.move_right(&MoveRight, window, cx);
6128 editor.handle_input("}", window, cx);
6129 editor.handle_input("}", window, cx);
6130 editor.handle_input("}", window, cx);
6131 });
6132 cx.assert_editor_state(
6133 &"
6134 🏀{{{)}}}}ˇ
6135 ε{{{)}}}}ˇ
6136 ❤️{{{)}}}}ˇ
6137 "
6138 .unindent(),
6139 );
6140
6141 // autoclose multi-character pairs
6142 cx.set_state(
6143 &"
6144 ˇ
6145 ˇ
6146 "
6147 .unindent(),
6148 );
6149 cx.update_editor(|editor, window, cx| {
6150 editor.handle_input("/", window, cx);
6151 editor.handle_input("*", window, cx);
6152 });
6153 cx.assert_editor_state(
6154 &"
6155 /*ˇ */
6156 /*ˇ */
6157 "
6158 .unindent(),
6159 );
6160
6161 // one cursor autocloses a multi-character pair, one cursor
6162 // does not autoclose.
6163 cx.set_state(
6164 &"
6165 /ˇ
6166 ˇ
6167 "
6168 .unindent(),
6169 );
6170 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6171 cx.assert_editor_state(
6172 &"
6173 /*ˇ */
6174 *ˇ
6175 "
6176 .unindent(),
6177 );
6178
6179 // Don't autoclose if the next character isn't whitespace and isn't
6180 // listed in the language's "autoclose_before" section.
6181 cx.set_state("ˇa b");
6182 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6183 cx.assert_editor_state("{ˇa b");
6184
6185 // Don't autoclose if `close` is false for the bracket pair
6186 cx.set_state("ˇ");
6187 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6188 cx.assert_editor_state("[ˇ");
6189
6190 // Surround with brackets if text is selected
6191 cx.set_state("«aˇ» b");
6192 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6193 cx.assert_editor_state("{«aˇ»} b");
6194
6195 // Autclose pair where the start and end characters are the same
6196 cx.set_state("aˇ");
6197 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6198 cx.assert_editor_state("a\"ˇ\"");
6199 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6200 cx.assert_editor_state("a\"\"ˇ");
6201
6202 // Don't autoclose pair if autoclose is disabled
6203 cx.set_state("ˇ");
6204 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6205 cx.assert_editor_state("<ˇ");
6206
6207 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6208 cx.set_state("«aˇ» b");
6209 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6210 cx.assert_editor_state("<«aˇ»> b");
6211}
6212
6213#[gpui::test]
6214async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6215 init_test(cx, |settings| {
6216 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6217 });
6218
6219 let mut cx = EditorTestContext::new(cx).await;
6220
6221 let language = Arc::new(Language::new(
6222 LanguageConfig {
6223 brackets: BracketPairConfig {
6224 pairs: vec![
6225 BracketPair {
6226 start: "{".to_string(),
6227 end: "}".to_string(),
6228 close: true,
6229 surround: true,
6230 newline: true,
6231 },
6232 BracketPair {
6233 start: "(".to_string(),
6234 end: ")".to_string(),
6235 close: true,
6236 surround: true,
6237 newline: true,
6238 },
6239 BracketPair {
6240 start: "[".to_string(),
6241 end: "]".to_string(),
6242 close: false,
6243 surround: false,
6244 newline: true,
6245 },
6246 ],
6247 ..Default::default()
6248 },
6249 autoclose_before: "})]".to_string(),
6250 ..Default::default()
6251 },
6252 Some(tree_sitter_rust::LANGUAGE.into()),
6253 ));
6254
6255 cx.language_registry().add(language.clone());
6256 cx.update_buffer(|buffer, cx| {
6257 buffer.set_language(Some(language), cx);
6258 });
6259
6260 cx.set_state(
6261 &"
6262 ˇ
6263 ˇ
6264 ˇ
6265 "
6266 .unindent(),
6267 );
6268
6269 // ensure only matching closing brackets are skipped over
6270 cx.update_editor(|editor, window, cx| {
6271 editor.handle_input("}", window, cx);
6272 editor.move_left(&MoveLeft, window, cx);
6273 editor.handle_input(")", window, cx);
6274 editor.move_left(&MoveLeft, window, cx);
6275 });
6276 cx.assert_editor_state(
6277 &"
6278 ˇ)}
6279 ˇ)}
6280 ˇ)}
6281 "
6282 .unindent(),
6283 );
6284
6285 // skip-over closing brackets at multiple cursors
6286 cx.update_editor(|editor, window, cx| {
6287 editor.handle_input(")", window, cx);
6288 editor.handle_input("}", window, cx);
6289 });
6290 cx.assert_editor_state(
6291 &"
6292 )}ˇ
6293 )}ˇ
6294 )}ˇ
6295 "
6296 .unindent(),
6297 );
6298
6299 // ignore non-close brackets
6300 cx.update_editor(|editor, window, cx| {
6301 editor.handle_input("]", window, cx);
6302 editor.move_left(&MoveLeft, window, cx);
6303 editor.handle_input("]", window, cx);
6304 });
6305 cx.assert_editor_state(
6306 &"
6307 )}]ˇ]
6308 )}]ˇ]
6309 )}]ˇ]
6310 "
6311 .unindent(),
6312 );
6313}
6314
6315#[gpui::test]
6316async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6317 init_test(cx, |_| {});
6318
6319 let mut cx = EditorTestContext::new(cx).await;
6320
6321 let html_language = Arc::new(
6322 Language::new(
6323 LanguageConfig {
6324 name: "HTML".into(),
6325 brackets: BracketPairConfig {
6326 pairs: vec![
6327 BracketPair {
6328 start: "<".into(),
6329 end: ">".into(),
6330 close: true,
6331 ..Default::default()
6332 },
6333 BracketPair {
6334 start: "{".into(),
6335 end: "}".into(),
6336 close: true,
6337 ..Default::default()
6338 },
6339 BracketPair {
6340 start: "(".into(),
6341 end: ")".into(),
6342 close: true,
6343 ..Default::default()
6344 },
6345 ],
6346 ..Default::default()
6347 },
6348 autoclose_before: "})]>".into(),
6349 ..Default::default()
6350 },
6351 Some(tree_sitter_html::LANGUAGE.into()),
6352 )
6353 .with_injection_query(
6354 r#"
6355 (script_element
6356 (raw_text) @injection.content
6357 (#set! injection.language "javascript"))
6358 "#,
6359 )
6360 .unwrap(),
6361 );
6362
6363 let javascript_language = Arc::new(Language::new(
6364 LanguageConfig {
6365 name: "JavaScript".into(),
6366 brackets: BracketPairConfig {
6367 pairs: vec![
6368 BracketPair {
6369 start: "/*".into(),
6370 end: " */".into(),
6371 close: true,
6372 ..Default::default()
6373 },
6374 BracketPair {
6375 start: "{".into(),
6376 end: "}".into(),
6377 close: true,
6378 ..Default::default()
6379 },
6380 BracketPair {
6381 start: "(".into(),
6382 end: ")".into(),
6383 close: true,
6384 ..Default::default()
6385 },
6386 ],
6387 ..Default::default()
6388 },
6389 autoclose_before: "})]>".into(),
6390 ..Default::default()
6391 },
6392 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6393 ));
6394
6395 cx.language_registry().add(html_language.clone());
6396 cx.language_registry().add(javascript_language.clone());
6397
6398 cx.update_buffer(|buffer, cx| {
6399 buffer.set_language(Some(html_language), cx);
6400 });
6401
6402 cx.set_state(
6403 &r#"
6404 <body>ˇ
6405 <script>
6406 var x = 1;ˇ
6407 </script>
6408 </body>ˇ
6409 "#
6410 .unindent(),
6411 );
6412
6413 // Precondition: different languages are active at different locations.
6414 cx.update_editor(|editor, window, cx| {
6415 let snapshot = editor.snapshot(window, cx);
6416 let cursors = editor.selections.ranges::<usize>(cx);
6417 let languages = cursors
6418 .iter()
6419 .map(|c| snapshot.language_at(c.start).unwrap().name())
6420 .collect::<Vec<_>>();
6421 assert_eq!(
6422 languages,
6423 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6424 );
6425 });
6426
6427 // Angle brackets autoclose in HTML, but not JavaScript.
6428 cx.update_editor(|editor, window, cx| {
6429 editor.handle_input("<", window, cx);
6430 editor.handle_input("a", window, cx);
6431 });
6432 cx.assert_editor_state(
6433 &r#"
6434 <body><aˇ>
6435 <script>
6436 var x = 1;<aˇ
6437 </script>
6438 </body><aˇ>
6439 "#
6440 .unindent(),
6441 );
6442
6443 // Curly braces and parens autoclose in both HTML and JavaScript.
6444 cx.update_editor(|editor, window, cx| {
6445 editor.handle_input(" b=", window, cx);
6446 editor.handle_input("{", window, cx);
6447 editor.handle_input("c", window, cx);
6448 editor.handle_input("(", window, cx);
6449 });
6450 cx.assert_editor_state(
6451 &r#"
6452 <body><a b={c(ˇ)}>
6453 <script>
6454 var x = 1;<a b={c(ˇ)}
6455 </script>
6456 </body><a b={c(ˇ)}>
6457 "#
6458 .unindent(),
6459 );
6460
6461 // Brackets that were already autoclosed are skipped.
6462 cx.update_editor(|editor, window, cx| {
6463 editor.handle_input(")", window, cx);
6464 editor.handle_input("d", window, cx);
6465 editor.handle_input("}", window, cx);
6466 });
6467 cx.assert_editor_state(
6468 &r#"
6469 <body><a b={c()d}ˇ>
6470 <script>
6471 var x = 1;<a b={c()d}ˇ
6472 </script>
6473 </body><a b={c()d}ˇ>
6474 "#
6475 .unindent(),
6476 );
6477 cx.update_editor(|editor, window, cx| {
6478 editor.handle_input(">", window, cx);
6479 });
6480 cx.assert_editor_state(
6481 &r#"
6482 <body><a b={c()d}>ˇ
6483 <script>
6484 var x = 1;<a b={c()d}>ˇ
6485 </script>
6486 </body><a b={c()d}>ˇ
6487 "#
6488 .unindent(),
6489 );
6490
6491 // Reset
6492 cx.set_state(
6493 &r#"
6494 <body>ˇ
6495 <script>
6496 var x = 1;ˇ
6497 </script>
6498 </body>ˇ
6499 "#
6500 .unindent(),
6501 );
6502
6503 cx.update_editor(|editor, window, cx| {
6504 editor.handle_input("<", window, cx);
6505 });
6506 cx.assert_editor_state(
6507 &r#"
6508 <body><ˇ>
6509 <script>
6510 var x = 1;<ˇ
6511 </script>
6512 </body><ˇ>
6513 "#
6514 .unindent(),
6515 );
6516
6517 // When backspacing, the closing angle brackets are removed.
6518 cx.update_editor(|editor, window, cx| {
6519 editor.backspace(&Backspace, window, cx);
6520 });
6521 cx.assert_editor_state(
6522 &r#"
6523 <body>ˇ
6524 <script>
6525 var x = 1;ˇ
6526 </script>
6527 </body>ˇ
6528 "#
6529 .unindent(),
6530 );
6531
6532 // Block comments autoclose in JavaScript, but not HTML.
6533 cx.update_editor(|editor, window, cx| {
6534 editor.handle_input("/", window, cx);
6535 editor.handle_input("*", window, cx);
6536 });
6537 cx.assert_editor_state(
6538 &r#"
6539 <body>/*ˇ
6540 <script>
6541 var x = 1;/*ˇ */
6542 </script>
6543 </body>/*ˇ
6544 "#
6545 .unindent(),
6546 );
6547}
6548
6549#[gpui::test]
6550async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6551 init_test(cx, |_| {});
6552
6553 let mut cx = EditorTestContext::new(cx).await;
6554
6555 let rust_language = Arc::new(
6556 Language::new(
6557 LanguageConfig {
6558 name: "Rust".into(),
6559 brackets: serde_json::from_value(json!([
6560 { "start": "{", "end": "}", "close": true, "newline": true },
6561 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6562 ]))
6563 .unwrap(),
6564 autoclose_before: "})]>".into(),
6565 ..Default::default()
6566 },
6567 Some(tree_sitter_rust::LANGUAGE.into()),
6568 )
6569 .with_override_query("(string_literal) @string")
6570 .unwrap(),
6571 );
6572
6573 cx.language_registry().add(rust_language.clone());
6574 cx.update_buffer(|buffer, cx| {
6575 buffer.set_language(Some(rust_language), cx);
6576 });
6577
6578 cx.set_state(
6579 &r#"
6580 let x = ˇ
6581 "#
6582 .unindent(),
6583 );
6584
6585 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6586 cx.update_editor(|editor, window, cx| {
6587 editor.handle_input("\"", window, cx);
6588 });
6589 cx.assert_editor_state(
6590 &r#"
6591 let x = "ˇ"
6592 "#
6593 .unindent(),
6594 );
6595
6596 // Inserting another quotation mark. The cursor moves across the existing
6597 // automatically-inserted quotation mark.
6598 cx.update_editor(|editor, window, cx| {
6599 editor.handle_input("\"", window, cx);
6600 });
6601 cx.assert_editor_state(
6602 &r#"
6603 let x = ""ˇ
6604 "#
6605 .unindent(),
6606 );
6607
6608 // Reset
6609 cx.set_state(
6610 &r#"
6611 let x = ˇ
6612 "#
6613 .unindent(),
6614 );
6615
6616 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6617 cx.update_editor(|editor, window, cx| {
6618 editor.handle_input("\"", window, cx);
6619 editor.handle_input(" ", window, cx);
6620 editor.move_left(&Default::default(), window, cx);
6621 editor.handle_input("\\", window, cx);
6622 editor.handle_input("\"", window, cx);
6623 });
6624 cx.assert_editor_state(
6625 &r#"
6626 let x = "\"ˇ "
6627 "#
6628 .unindent(),
6629 );
6630
6631 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6632 // mark. Nothing is inserted.
6633 cx.update_editor(|editor, window, cx| {
6634 editor.move_right(&Default::default(), window, cx);
6635 editor.handle_input("\"", window, cx);
6636 });
6637 cx.assert_editor_state(
6638 &r#"
6639 let x = "\" "ˇ
6640 "#
6641 .unindent(),
6642 );
6643}
6644
6645#[gpui::test]
6646async fn test_surround_with_pair(cx: &mut TestAppContext) {
6647 init_test(cx, |_| {});
6648
6649 let language = Arc::new(Language::new(
6650 LanguageConfig {
6651 brackets: BracketPairConfig {
6652 pairs: vec![
6653 BracketPair {
6654 start: "{".to_string(),
6655 end: "}".to_string(),
6656 close: true,
6657 surround: true,
6658 newline: true,
6659 },
6660 BracketPair {
6661 start: "/* ".to_string(),
6662 end: "*/".to_string(),
6663 close: true,
6664 surround: true,
6665 ..Default::default()
6666 },
6667 ],
6668 ..Default::default()
6669 },
6670 ..Default::default()
6671 },
6672 Some(tree_sitter_rust::LANGUAGE.into()),
6673 ));
6674
6675 let text = r#"
6676 a
6677 b
6678 c
6679 "#
6680 .unindent();
6681
6682 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6684 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6685 editor
6686 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6687 .await;
6688
6689 editor.update_in(cx, |editor, window, cx| {
6690 editor.change_selections(None, window, cx, |s| {
6691 s.select_display_ranges([
6692 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6693 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6694 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6695 ])
6696 });
6697
6698 editor.handle_input("{", window, cx);
6699 editor.handle_input("{", window, cx);
6700 editor.handle_input("{", window, cx);
6701 assert_eq!(
6702 editor.text(cx),
6703 "
6704 {{{a}}}
6705 {{{b}}}
6706 {{{c}}}
6707 "
6708 .unindent()
6709 );
6710 assert_eq!(
6711 editor.selections.display_ranges(cx),
6712 [
6713 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6714 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6715 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6716 ]
6717 );
6718
6719 editor.undo(&Undo, window, cx);
6720 editor.undo(&Undo, window, cx);
6721 editor.undo(&Undo, window, cx);
6722 assert_eq!(
6723 editor.text(cx),
6724 "
6725 a
6726 b
6727 c
6728 "
6729 .unindent()
6730 );
6731 assert_eq!(
6732 editor.selections.display_ranges(cx),
6733 [
6734 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6735 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6736 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6737 ]
6738 );
6739
6740 // Ensure inserting the first character of a multi-byte bracket pair
6741 // doesn't surround the selections with the bracket.
6742 editor.handle_input("/", window, cx);
6743 assert_eq!(
6744 editor.text(cx),
6745 "
6746 /
6747 /
6748 /
6749 "
6750 .unindent()
6751 );
6752 assert_eq!(
6753 editor.selections.display_ranges(cx),
6754 [
6755 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6756 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6757 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6758 ]
6759 );
6760
6761 editor.undo(&Undo, window, cx);
6762 assert_eq!(
6763 editor.text(cx),
6764 "
6765 a
6766 b
6767 c
6768 "
6769 .unindent()
6770 );
6771 assert_eq!(
6772 editor.selections.display_ranges(cx),
6773 [
6774 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6775 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6776 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6777 ]
6778 );
6779
6780 // Ensure inserting the last character of a multi-byte bracket pair
6781 // doesn't surround the selections with the bracket.
6782 editor.handle_input("*", window, cx);
6783 assert_eq!(
6784 editor.text(cx),
6785 "
6786 *
6787 *
6788 *
6789 "
6790 .unindent()
6791 );
6792 assert_eq!(
6793 editor.selections.display_ranges(cx),
6794 [
6795 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6796 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6797 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6798 ]
6799 );
6800 });
6801}
6802
6803#[gpui::test]
6804async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6805 init_test(cx, |_| {});
6806
6807 let language = Arc::new(Language::new(
6808 LanguageConfig {
6809 brackets: BracketPairConfig {
6810 pairs: vec![BracketPair {
6811 start: "{".to_string(),
6812 end: "}".to_string(),
6813 close: true,
6814 surround: true,
6815 newline: true,
6816 }],
6817 ..Default::default()
6818 },
6819 autoclose_before: "}".to_string(),
6820 ..Default::default()
6821 },
6822 Some(tree_sitter_rust::LANGUAGE.into()),
6823 ));
6824
6825 let text = r#"
6826 a
6827 b
6828 c
6829 "#
6830 .unindent();
6831
6832 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6833 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6834 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6835 editor
6836 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6837 .await;
6838
6839 editor.update_in(cx, |editor, window, cx| {
6840 editor.change_selections(None, window, cx, |s| {
6841 s.select_ranges([
6842 Point::new(0, 1)..Point::new(0, 1),
6843 Point::new(1, 1)..Point::new(1, 1),
6844 Point::new(2, 1)..Point::new(2, 1),
6845 ])
6846 });
6847
6848 editor.handle_input("{", window, cx);
6849 editor.handle_input("{", window, cx);
6850 editor.handle_input("_", window, cx);
6851 assert_eq!(
6852 editor.text(cx),
6853 "
6854 a{{_}}
6855 b{{_}}
6856 c{{_}}
6857 "
6858 .unindent()
6859 );
6860 assert_eq!(
6861 editor.selections.ranges::<Point>(cx),
6862 [
6863 Point::new(0, 4)..Point::new(0, 4),
6864 Point::new(1, 4)..Point::new(1, 4),
6865 Point::new(2, 4)..Point::new(2, 4)
6866 ]
6867 );
6868
6869 editor.backspace(&Default::default(), window, cx);
6870 editor.backspace(&Default::default(), window, cx);
6871 assert_eq!(
6872 editor.text(cx),
6873 "
6874 a{}
6875 b{}
6876 c{}
6877 "
6878 .unindent()
6879 );
6880 assert_eq!(
6881 editor.selections.ranges::<Point>(cx),
6882 [
6883 Point::new(0, 2)..Point::new(0, 2),
6884 Point::new(1, 2)..Point::new(1, 2),
6885 Point::new(2, 2)..Point::new(2, 2)
6886 ]
6887 );
6888
6889 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6890 assert_eq!(
6891 editor.text(cx),
6892 "
6893 a
6894 b
6895 c
6896 "
6897 .unindent()
6898 );
6899 assert_eq!(
6900 editor.selections.ranges::<Point>(cx),
6901 [
6902 Point::new(0, 1)..Point::new(0, 1),
6903 Point::new(1, 1)..Point::new(1, 1),
6904 Point::new(2, 1)..Point::new(2, 1)
6905 ]
6906 );
6907 });
6908}
6909
6910#[gpui::test]
6911async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
6912 init_test(cx, |settings| {
6913 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6914 });
6915
6916 let mut cx = EditorTestContext::new(cx).await;
6917
6918 let language = Arc::new(Language::new(
6919 LanguageConfig {
6920 brackets: BracketPairConfig {
6921 pairs: vec![
6922 BracketPair {
6923 start: "{".to_string(),
6924 end: "}".to_string(),
6925 close: true,
6926 surround: true,
6927 newline: true,
6928 },
6929 BracketPair {
6930 start: "(".to_string(),
6931 end: ")".to_string(),
6932 close: true,
6933 surround: true,
6934 newline: true,
6935 },
6936 BracketPair {
6937 start: "[".to_string(),
6938 end: "]".to_string(),
6939 close: false,
6940 surround: true,
6941 newline: true,
6942 },
6943 ],
6944 ..Default::default()
6945 },
6946 autoclose_before: "})]".to_string(),
6947 ..Default::default()
6948 },
6949 Some(tree_sitter_rust::LANGUAGE.into()),
6950 ));
6951
6952 cx.language_registry().add(language.clone());
6953 cx.update_buffer(|buffer, cx| {
6954 buffer.set_language(Some(language), cx);
6955 });
6956
6957 cx.set_state(
6958 &"
6959 {(ˇ)}
6960 [[ˇ]]
6961 {(ˇ)}
6962 "
6963 .unindent(),
6964 );
6965
6966 cx.update_editor(|editor, window, cx| {
6967 editor.backspace(&Default::default(), window, cx);
6968 editor.backspace(&Default::default(), window, cx);
6969 });
6970
6971 cx.assert_editor_state(
6972 &"
6973 ˇ
6974 ˇ]]
6975 ˇ
6976 "
6977 .unindent(),
6978 );
6979
6980 cx.update_editor(|editor, window, cx| {
6981 editor.handle_input("{", window, cx);
6982 editor.handle_input("{", window, cx);
6983 editor.move_right(&MoveRight, window, cx);
6984 editor.move_right(&MoveRight, window, cx);
6985 editor.move_left(&MoveLeft, window, cx);
6986 editor.move_left(&MoveLeft, window, cx);
6987 editor.backspace(&Default::default(), window, cx);
6988 });
6989
6990 cx.assert_editor_state(
6991 &"
6992 {ˇ}
6993 {ˇ}]]
6994 {ˇ}
6995 "
6996 .unindent(),
6997 );
6998
6999 cx.update_editor(|editor, window, cx| {
7000 editor.backspace(&Default::default(), window, cx);
7001 });
7002
7003 cx.assert_editor_state(
7004 &"
7005 ˇ
7006 ˇ]]
7007 ˇ
7008 "
7009 .unindent(),
7010 );
7011}
7012
7013#[gpui::test]
7014async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7015 init_test(cx, |_| {});
7016
7017 let language = Arc::new(Language::new(
7018 LanguageConfig::default(),
7019 Some(tree_sitter_rust::LANGUAGE.into()),
7020 ));
7021
7022 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7023 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7024 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7025 editor
7026 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7027 .await;
7028
7029 editor.update_in(cx, |editor, window, cx| {
7030 editor.set_auto_replace_emoji_shortcode(true);
7031
7032 editor.handle_input("Hello ", window, cx);
7033 editor.handle_input(":wave", window, cx);
7034 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7035
7036 editor.handle_input(":", window, cx);
7037 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7038
7039 editor.handle_input(" :smile", window, cx);
7040 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7041
7042 editor.handle_input(":", window, cx);
7043 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7044
7045 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7046 editor.handle_input(":wave", window, cx);
7047 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7048
7049 editor.handle_input(":", window, cx);
7050 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7051
7052 editor.handle_input(":1", window, cx);
7053 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7054
7055 editor.handle_input(":", window, cx);
7056 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7057
7058 // Ensure shortcode does not get replaced when it is part of a word
7059 editor.handle_input(" Test:wave", window, cx);
7060 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7061
7062 editor.handle_input(":", window, cx);
7063 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7064
7065 editor.set_auto_replace_emoji_shortcode(false);
7066
7067 // Ensure shortcode does not get replaced when auto replace is off
7068 editor.handle_input(" :wave", window, cx);
7069 assert_eq!(
7070 editor.text(cx),
7071 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7072 );
7073
7074 editor.handle_input(":", window, cx);
7075 assert_eq!(
7076 editor.text(cx),
7077 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7078 );
7079 });
7080}
7081
7082#[gpui::test]
7083async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7084 init_test(cx, |_| {});
7085
7086 let (text, insertion_ranges) = marked_text_ranges(
7087 indoc! {"
7088 ˇ
7089 "},
7090 false,
7091 );
7092
7093 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7094 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7095
7096 _ = editor.update_in(cx, |editor, window, cx| {
7097 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7098
7099 editor
7100 .insert_snippet(&insertion_ranges, snippet, window, cx)
7101 .unwrap();
7102
7103 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7104 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7105 assert_eq!(editor.text(cx), expected_text);
7106 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7107 }
7108
7109 assert(
7110 editor,
7111 cx,
7112 indoc! {"
7113 type «» =•
7114 "},
7115 );
7116
7117 assert!(editor.context_menu_visible(), "There should be a matches");
7118 });
7119}
7120
7121#[gpui::test]
7122async fn test_snippets(cx: &mut TestAppContext) {
7123 init_test(cx, |_| {});
7124
7125 let (text, insertion_ranges) = marked_text_ranges(
7126 indoc! {"
7127 a.ˇ b
7128 a.ˇ b
7129 a.ˇ b
7130 "},
7131 false,
7132 );
7133
7134 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7135 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7136
7137 editor.update_in(cx, |editor, window, cx| {
7138 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7139
7140 editor
7141 .insert_snippet(&insertion_ranges, snippet, window, cx)
7142 .unwrap();
7143
7144 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7145 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7146 assert_eq!(editor.text(cx), expected_text);
7147 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7148 }
7149
7150 assert(
7151 editor,
7152 cx,
7153 indoc! {"
7154 a.f(«one», two, «three») b
7155 a.f(«one», two, «three») b
7156 a.f(«one», two, «three») b
7157 "},
7158 );
7159
7160 // Can't move earlier than the first tab stop
7161 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7162 assert(
7163 editor,
7164 cx,
7165 indoc! {"
7166 a.f(«one», two, «three») b
7167 a.f(«one», two, «three») b
7168 a.f(«one», two, «three») b
7169 "},
7170 );
7171
7172 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7173 assert(
7174 editor,
7175 cx,
7176 indoc! {"
7177 a.f(one, «two», three) b
7178 a.f(one, «two», three) b
7179 a.f(one, «two», three) b
7180 "},
7181 );
7182
7183 editor.move_to_prev_snippet_tabstop(window, cx);
7184 assert(
7185 editor,
7186 cx,
7187 indoc! {"
7188 a.f(«one», two, «three») b
7189 a.f(«one», two, «three») b
7190 a.f(«one», two, «three») b
7191 "},
7192 );
7193
7194 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7195 assert(
7196 editor,
7197 cx,
7198 indoc! {"
7199 a.f(one, «two», three) b
7200 a.f(one, «two», three) b
7201 a.f(one, «two», three) b
7202 "},
7203 );
7204 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7205 assert(
7206 editor,
7207 cx,
7208 indoc! {"
7209 a.f(one, two, three)ˇ b
7210 a.f(one, two, three)ˇ b
7211 a.f(one, two, three)ˇ b
7212 "},
7213 );
7214
7215 // As soon as the last tab stop is reached, snippet state is gone
7216 editor.move_to_prev_snippet_tabstop(window, cx);
7217 assert(
7218 editor,
7219 cx,
7220 indoc! {"
7221 a.f(one, two, three)ˇ b
7222 a.f(one, two, three)ˇ b
7223 a.f(one, two, three)ˇ b
7224 "},
7225 );
7226 });
7227}
7228
7229#[gpui::test]
7230async fn test_document_format_during_save(cx: &mut TestAppContext) {
7231 init_test(cx, |_| {});
7232
7233 let fs = FakeFs::new(cx.executor());
7234 fs.insert_file(path!("/file.rs"), Default::default()).await;
7235
7236 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7237
7238 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7239 language_registry.add(rust_lang());
7240 let mut fake_servers = language_registry.register_fake_lsp(
7241 "Rust",
7242 FakeLspAdapter {
7243 capabilities: lsp::ServerCapabilities {
7244 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7245 ..Default::default()
7246 },
7247 ..Default::default()
7248 },
7249 );
7250
7251 let buffer = project
7252 .update(cx, |project, cx| {
7253 project.open_local_buffer(path!("/file.rs"), cx)
7254 })
7255 .await
7256 .unwrap();
7257
7258 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7259 let (editor, cx) = cx.add_window_view(|window, cx| {
7260 build_editor_with_project(project.clone(), buffer, window, cx)
7261 });
7262 editor.update_in(cx, |editor, window, cx| {
7263 editor.set_text("one\ntwo\nthree\n", window, cx)
7264 });
7265 assert!(cx.read(|cx| editor.is_dirty(cx)));
7266
7267 cx.executor().start_waiting();
7268 let fake_server = fake_servers.next().await.unwrap();
7269
7270 let save = editor
7271 .update_in(cx, |editor, window, cx| {
7272 editor.save(true, project.clone(), window, cx)
7273 })
7274 .unwrap();
7275 fake_server
7276 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7277 assert_eq!(
7278 params.text_document.uri,
7279 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7280 );
7281 assert_eq!(params.options.tab_size, 4);
7282 Ok(Some(vec![lsp::TextEdit::new(
7283 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7284 ", ".to_string(),
7285 )]))
7286 })
7287 .next()
7288 .await;
7289 cx.executor().start_waiting();
7290 save.await;
7291
7292 assert_eq!(
7293 editor.update(cx, |editor, cx| editor.text(cx)),
7294 "one, two\nthree\n"
7295 );
7296 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7297
7298 editor.update_in(cx, |editor, window, cx| {
7299 editor.set_text("one\ntwo\nthree\n", window, cx)
7300 });
7301 assert!(cx.read(|cx| editor.is_dirty(cx)));
7302
7303 // Ensure we can still save even if formatting hangs.
7304 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7305 assert_eq!(
7306 params.text_document.uri,
7307 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7308 );
7309 futures::future::pending::<()>().await;
7310 unreachable!()
7311 });
7312 let save = editor
7313 .update_in(cx, |editor, window, cx| {
7314 editor.save(true, project.clone(), window, cx)
7315 })
7316 .unwrap();
7317 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7318 cx.executor().start_waiting();
7319 save.await;
7320 assert_eq!(
7321 editor.update(cx, |editor, cx| editor.text(cx)),
7322 "one\ntwo\nthree\n"
7323 );
7324 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7325
7326 // For non-dirty buffer, no formatting request should be sent
7327 let save = editor
7328 .update_in(cx, |editor, window, cx| {
7329 editor.save(true, project.clone(), window, cx)
7330 })
7331 .unwrap();
7332 let _pending_format_request = fake_server
7333 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7334 panic!("Should not be invoked on non-dirty buffer");
7335 })
7336 .next();
7337 cx.executor().start_waiting();
7338 save.await;
7339
7340 // Set rust language override and assert overridden tabsize is sent to language server
7341 update_test_language_settings(cx, |settings| {
7342 settings.languages.insert(
7343 "Rust".into(),
7344 LanguageSettingsContent {
7345 tab_size: NonZeroU32::new(8),
7346 ..Default::default()
7347 },
7348 );
7349 });
7350
7351 editor.update_in(cx, |editor, window, cx| {
7352 editor.set_text("somehting_new\n", window, cx)
7353 });
7354 assert!(cx.read(|cx| editor.is_dirty(cx)));
7355 let save = editor
7356 .update_in(cx, |editor, window, cx| {
7357 editor.save(true, project.clone(), window, cx)
7358 })
7359 .unwrap();
7360 fake_server
7361 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7362 assert_eq!(
7363 params.text_document.uri,
7364 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7365 );
7366 assert_eq!(params.options.tab_size, 8);
7367 Ok(Some(vec![]))
7368 })
7369 .next()
7370 .await;
7371 cx.executor().start_waiting();
7372 save.await;
7373}
7374
7375#[gpui::test]
7376async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7377 init_test(cx, |_| {});
7378
7379 let cols = 4;
7380 let rows = 10;
7381 let sample_text_1 = sample_text(rows, cols, 'a');
7382 assert_eq!(
7383 sample_text_1,
7384 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7385 );
7386 let sample_text_2 = sample_text(rows, cols, 'l');
7387 assert_eq!(
7388 sample_text_2,
7389 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7390 );
7391 let sample_text_3 = sample_text(rows, cols, 'v');
7392 assert_eq!(
7393 sample_text_3,
7394 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7395 );
7396
7397 let fs = FakeFs::new(cx.executor());
7398 fs.insert_tree(
7399 path!("/a"),
7400 json!({
7401 "main.rs": sample_text_1,
7402 "other.rs": sample_text_2,
7403 "lib.rs": sample_text_3,
7404 }),
7405 )
7406 .await;
7407
7408 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7409 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7410 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7411
7412 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7413 language_registry.add(rust_lang());
7414 let mut fake_servers = language_registry.register_fake_lsp(
7415 "Rust",
7416 FakeLspAdapter {
7417 capabilities: lsp::ServerCapabilities {
7418 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7419 ..Default::default()
7420 },
7421 ..Default::default()
7422 },
7423 );
7424
7425 let worktree = project.update(cx, |project, cx| {
7426 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7427 assert_eq!(worktrees.len(), 1);
7428 worktrees.pop().unwrap()
7429 });
7430 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7431
7432 let buffer_1 = project
7433 .update(cx, |project, cx| {
7434 project.open_buffer((worktree_id, "main.rs"), cx)
7435 })
7436 .await
7437 .unwrap();
7438 let buffer_2 = project
7439 .update(cx, |project, cx| {
7440 project.open_buffer((worktree_id, "other.rs"), cx)
7441 })
7442 .await
7443 .unwrap();
7444 let buffer_3 = project
7445 .update(cx, |project, cx| {
7446 project.open_buffer((worktree_id, "lib.rs"), cx)
7447 })
7448 .await
7449 .unwrap();
7450
7451 let multi_buffer = cx.new(|cx| {
7452 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7453 multi_buffer.push_excerpts(
7454 buffer_1.clone(),
7455 [
7456 ExcerptRange {
7457 context: Point::new(0, 0)..Point::new(3, 0),
7458 primary: None,
7459 },
7460 ExcerptRange {
7461 context: Point::new(5, 0)..Point::new(7, 0),
7462 primary: None,
7463 },
7464 ExcerptRange {
7465 context: Point::new(9, 0)..Point::new(10, 4),
7466 primary: None,
7467 },
7468 ],
7469 cx,
7470 );
7471 multi_buffer.push_excerpts(
7472 buffer_2.clone(),
7473 [
7474 ExcerptRange {
7475 context: Point::new(0, 0)..Point::new(3, 0),
7476 primary: None,
7477 },
7478 ExcerptRange {
7479 context: Point::new(5, 0)..Point::new(7, 0),
7480 primary: None,
7481 },
7482 ExcerptRange {
7483 context: Point::new(9, 0)..Point::new(10, 4),
7484 primary: None,
7485 },
7486 ],
7487 cx,
7488 );
7489 multi_buffer.push_excerpts(
7490 buffer_3.clone(),
7491 [
7492 ExcerptRange {
7493 context: Point::new(0, 0)..Point::new(3, 0),
7494 primary: None,
7495 },
7496 ExcerptRange {
7497 context: Point::new(5, 0)..Point::new(7, 0),
7498 primary: None,
7499 },
7500 ExcerptRange {
7501 context: Point::new(9, 0)..Point::new(10, 4),
7502 primary: None,
7503 },
7504 ],
7505 cx,
7506 );
7507 multi_buffer
7508 });
7509 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7510 Editor::new(
7511 EditorMode::Full,
7512 multi_buffer,
7513 Some(project.clone()),
7514 true,
7515 window,
7516 cx,
7517 )
7518 });
7519
7520 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7521 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7522 s.select_ranges(Some(1..2))
7523 });
7524 editor.insert("|one|two|three|", window, cx);
7525 });
7526 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7527 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7528 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7529 s.select_ranges(Some(60..70))
7530 });
7531 editor.insert("|four|five|six|", window, cx);
7532 });
7533 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7534
7535 // First two buffers should be edited, but not the third one.
7536 assert_eq!(
7537 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7538 "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}",
7539 );
7540 buffer_1.update(cx, |buffer, _| {
7541 assert!(buffer.is_dirty());
7542 assert_eq!(
7543 buffer.text(),
7544 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7545 )
7546 });
7547 buffer_2.update(cx, |buffer, _| {
7548 assert!(buffer.is_dirty());
7549 assert_eq!(
7550 buffer.text(),
7551 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7552 )
7553 });
7554 buffer_3.update(cx, |buffer, _| {
7555 assert!(!buffer.is_dirty());
7556 assert_eq!(buffer.text(), sample_text_3,)
7557 });
7558 cx.executor().run_until_parked();
7559
7560 cx.executor().start_waiting();
7561 let save = multi_buffer_editor
7562 .update_in(cx, |editor, window, cx| {
7563 editor.save(true, project.clone(), window, cx)
7564 })
7565 .unwrap();
7566
7567 let fake_server = fake_servers.next().await.unwrap();
7568 fake_server
7569 .server
7570 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7571 Ok(Some(vec![lsp::TextEdit::new(
7572 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7573 format!("[{} formatted]", params.text_document.uri),
7574 )]))
7575 })
7576 .detach();
7577 save.await;
7578
7579 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7580 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7581 assert_eq!(
7582 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7583 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}"),
7584 );
7585 buffer_1.update(cx, |buffer, _| {
7586 assert!(!buffer.is_dirty());
7587 assert_eq!(
7588 buffer.text(),
7589 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7590 )
7591 });
7592 buffer_2.update(cx, |buffer, _| {
7593 assert!(!buffer.is_dirty());
7594 assert_eq!(
7595 buffer.text(),
7596 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7597 )
7598 });
7599 buffer_3.update(cx, |buffer, _| {
7600 assert!(!buffer.is_dirty());
7601 assert_eq!(buffer.text(), sample_text_3,)
7602 });
7603}
7604
7605#[gpui::test]
7606async fn test_range_format_during_save(cx: &mut TestAppContext) {
7607 init_test(cx, |_| {});
7608
7609 let fs = FakeFs::new(cx.executor());
7610 fs.insert_file(path!("/file.rs"), Default::default()).await;
7611
7612 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7613
7614 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7615 language_registry.add(rust_lang());
7616 let mut fake_servers = language_registry.register_fake_lsp(
7617 "Rust",
7618 FakeLspAdapter {
7619 capabilities: lsp::ServerCapabilities {
7620 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7621 ..Default::default()
7622 },
7623 ..Default::default()
7624 },
7625 );
7626
7627 let buffer = project
7628 .update(cx, |project, cx| {
7629 project.open_local_buffer(path!("/file.rs"), cx)
7630 })
7631 .await
7632 .unwrap();
7633
7634 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7635 let (editor, cx) = cx.add_window_view(|window, cx| {
7636 build_editor_with_project(project.clone(), buffer, window, cx)
7637 });
7638 editor.update_in(cx, |editor, window, cx| {
7639 editor.set_text("one\ntwo\nthree\n", window, cx)
7640 });
7641 assert!(cx.read(|cx| editor.is_dirty(cx)));
7642
7643 cx.executor().start_waiting();
7644 let fake_server = fake_servers.next().await.unwrap();
7645
7646 let save = editor
7647 .update_in(cx, |editor, window, cx| {
7648 editor.save(true, project.clone(), window, cx)
7649 })
7650 .unwrap();
7651 fake_server
7652 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7653 assert_eq!(
7654 params.text_document.uri,
7655 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7656 );
7657 assert_eq!(params.options.tab_size, 4);
7658 Ok(Some(vec![lsp::TextEdit::new(
7659 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7660 ", ".to_string(),
7661 )]))
7662 })
7663 .next()
7664 .await;
7665 cx.executor().start_waiting();
7666 save.await;
7667 assert_eq!(
7668 editor.update(cx, |editor, cx| editor.text(cx)),
7669 "one, two\nthree\n"
7670 );
7671 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7672
7673 editor.update_in(cx, |editor, window, cx| {
7674 editor.set_text("one\ntwo\nthree\n", window, cx)
7675 });
7676 assert!(cx.read(|cx| editor.is_dirty(cx)));
7677
7678 // Ensure we can still save even if formatting hangs.
7679 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7680 move |params, _| async move {
7681 assert_eq!(
7682 params.text_document.uri,
7683 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7684 );
7685 futures::future::pending::<()>().await;
7686 unreachable!()
7687 },
7688 );
7689 let save = editor
7690 .update_in(cx, |editor, window, cx| {
7691 editor.save(true, project.clone(), window, cx)
7692 })
7693 .unwrap();
7694 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7695 cx.executor().start_waiting();
7696 save.await;
7697 assert_eq!(
7698 editor.update(cx, |editor, cx| editor.text(cx)),
7699 "one\ntwo\nthree\n"
7700 );
7701 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7702
7703 // For non-dirty buffer, no formatting request should be sent
7704 let save = editor
7705 .update_in(cx, |editor, window, cx| {
7706 editor.save(true, project.clone(), window, cx)
7707 })
7708 .unwrap();
7709 let _pending_format_request = fake_server
7710 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7711 panic!("Should not be invoked on non-dirty buffer");
7712 })
7713 .next();
7714 cx.executor().start_waiting();
7715 save.await;
7716
7717 // Set Rust language override and assert overridden tabsize is sent to language server
7718 update_test_language_settings(cx, |settings| {
7719 settings.languages.insert(
7720 "Rust".into(),
7721 LanguageSettingsContent {
7722 tab_size: NonZeroU32::new(8),
7723 ..Default::default()
7724 },
7725 );
7726 });
7727
7728 editor.update_in(cx, |editor, window, cx| {
7729 editor.set_text("somehting_new\n", window, cx)
7730 });
7731 assert!(cx.read(|cx| editor.is_dirty(cx)));
7732 let save = editor
7733 .update_in(cx, |editor, window, cx| {
7734 editor.save(true, project.clone(), window, cx)
7735 })
7736 .unwrap();
7737 fake_server
7738 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7739 assert_eq!(
7740 params.text_document.uri,
7741 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7742 );
7743 assert_eq!(params.options.tab_size, 8);
7744 Ok(Some(vec![]))
7745 })
7746 .next()
7747 .await;
7748 cx.executor().start_waiting();
7749 save.await;
7750}
7751
7752#[gpui::test]
7753async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7754 init_test(cx, |settings| {
7755 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7756 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7757 ))
7758 });
7759
7760 let fs = FakeFs::new(cx.executor());
7761 fs.insert_file(path!("/file.rs"), Default::default()).await;
7762
7763 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7764
7765 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7766 language_registry.add(Arc::new(Language::new(
7767 LanguageConfig {
7768 name: "Rust".into(),
7769 matcher: LanguageMatcher {
7770 path_suffixes: vec!["rs".to_string()],
7771 ..Default::default()
7772 },
7773 ..LanguageConfig::default()
7774 },
7775 Some(tree_sitter_rust::LANGUAGE.into()),
7776 )));
7777 update_test_language_settings(cx, |settings| {
7778 // Enable Prettier formatting for the same buffer, and ensure
7779 // LSP is called instead of Prettier.
7780 settings.defaults.prettier = Some(PrettierSettings {
7781 allowed: true,
7782 ..PrettierSettings::default()
7783 });
7784 });
7785 let mut fake_servers = language_registry.register_fake_lsp(
7786 "Rust",
7787 FakeLspAdapter {
7788 capabilities: lsp::ServerCapabilities {
7789 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7790 ..Default::default()
7791 },
7792 ..Default::default()
7793 },
7794 );
7795
7796 let buffer = project
7797 .update(cx, |project, cx| {
7798 project.open_local_buffer(path!("/file.rs"), cx)
7799 })
7800 .await
7801 .unwrap();
7802
7803 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7804 let (editor, cx) = cx.add_window_view(|window, cx| {
7805 build_editor_with_project(project.clone(), buffer, window, cx)
7806 });
7807 editor.update_in(cx, |editor, window, cx| {
7808 editor.set_text("one\ntwo\nthree\n", window, cx)
7809 });
7810
7811 cx.executor().start_waiting();
7812 let fake_server = fake_servers.next().await.unwrap();
7813
7814 let format = editor
7815 .update_in(cx, |editor, window, cx| {
7816 editor.perform_format(
7817 project.clone(),
7818 FormatTrigger::Manual,
7819 FormatTarget::Buffers,
7820 window,
7821 cx,
7822 )
7823 })
7824 .unwrap();
7825 fake_server
7826 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7827 assert_eq!(
7828 params.text_document.uri,
7829 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7830 );
7831 assert_eq!(params.options.tab_size, 4);
7832 Ok(Some(vec![lsp::TextEdit::new(
7833 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7834 ", ".to_string(),
7835 )]))
7836 })
7837 .next()
7838 .await;
7839 cx.executor().start_waiting();
7840 format.await;
7841 assert_eq!(
7842 editor.update(cx, |editor, cx| editor.text(cx)),
7843 "one, two\nthree\n"
7844 );
7845
7846 editor.update_in(cx, |editor, window, cx| {
7847 editor.set_text("one\ntwo\nthree\n", window, cx)
7848 });
7849 // Ensure we don't lock if formatting hangs.
7850 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7851 assert_eq!(
7852 params.text_document.uri,
7853 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7854 );
7855 futures::future::pending::<()>().await;
7856 unreachable!()
7857 });
7858 let format = editor
7859 .update_in(cx, |editor, window, cx| {
7860 editor.perform_format(
7861 project,
7862 FormatTrigger::Manual,
7863 FormatTarget::Buffers,
7864 window,
7865 cx,
7866 )
7867 })
7868 .unwrap();
7869 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7870 cx.executor().start_waiting();
7871 format.await;
7872 assert_eq!(
7873 editor.update(cx, |editor, cx| editor.text(cx)),
7874 "one\ntwo\nthree\n"
7875 );
7876}
7877
7878#[gpui::test]
7879async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
7880 init_test(cx, |_| {});
7881
7882 let mut cx = EditorLspTestContext::new_rust(
7883 lsp::ServerCapabilities {
7884 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7885 ..Default::default()
7886 },
7887 cx,
7888 )
7889 .await;
7890
7891 cx.set_state(indoc! {"
7892 one.twoˇ
7893 "});
7894
7895 // The format request takes a long time. When it completes, it inserts
7896 // a newline and an indent before the `.`
7897 cx.lsp
7898 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7899 let executor = cx.background_executor().clone();
7900 async move {
7901 executor.timer(Duration::from_millis(100)).await;
7902 Ok(Some(vec![lsp::TextEdit {
7903 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7904 new_text: "\n ".into(),
7905 }]))
7906 }
7907 });
7908
7909 // Submit a format request.
7910 let format_1 = cx
7911 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7912 .unwrap();
7913 cx.executor().run_until_parked();
7914
7915 // Submit a second format request.
7916 let format_2 = cx
7917 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7918 .unwrap();
7919 cx.executor().run_until_parked();
7920
7921 // Wait for both format requests to complete
7922 cx.executor().advance_clock(Duration::from_millis(200));
7923 cx.executor().start_waiting();
7924 format_1.await.unwrap();
7925 cx.executor().start_waiting();
7926 format_2.await.unwrap();
7927
7928 // The formatting edits only happens once.
7929 cx.assert_editor_state(indoc! {"
7930 one
7931 .twoˇ
7932 "});
7933}
7934
7935#[gpui::test]
7936async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
7937 init_test(cx, |settings| {
7938 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7939 });
7940
7941 let mut cx = EditorLspTestContext::new_rust(
7942 lsp::ServerCapabilities {
7943 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7944 ..Default::default()
7945 },
7946 cx,
7947 )
7948 .await;
7949
7950 // Set up a buffer white some trailing whitespace and no trailing newline.
7951 cx.set_state(
7952 &[
7953 "one ", //
7954 "twoˇ", //
7955 "three ", //
7956 "four", //
7957 ]
7958 .join("\n"),
7959 );
7960
7961 // Submit a format request.
7962 let format = cx
7963 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7964 .unwrap();
7965
7966 // Record which buffer changes have been sent to the language server
7967 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7968 cx.lsp
7969 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7970 let buffer_changes = buffer_changes.clone();
7971 move |params, _| {
7972 buffer_changes.lock().extend(
7973 params
7974 .content_changes
7975 .into_iter()
7976 .map(|e| (e.range.unwrap(), e.text)),
7977 );
7978 }
7979 });
7980
7981 // Handle formatting requests to the language server.
7982 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7983 let buffer_changes = buffer_changes.clone();
7984 move |_, _| {
7985 // When formatting is requested, trailing whitespace has already been stripped,
7986 // and the trailing newline has already been added.
7987 assert_eq!(
7988 &buffer_changes.lock()[1..],
7989 &[
7990 (
7991 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7992 "".into()
7993 ),
7994 (
7995 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7996 "".into()
7997 ),
7998 (
7999 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8000 "\n".into()
8001 ),
8002 ]
8003 );
8004
8005 // Insert blank lines between each line of the buffer.
8006 async move {
8007 Ok(Some(vec![
8008 lsp::TextEdit {
8009 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8010 new_text: "\n".into(),
8011 },
8012 lsp::TextEdit {
8013 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8014 new_text: "\n".into(),
8015 },
8016 ]))
8017 }
8018 }
8019 });
8020
8021 // After formatting the buffer, the trailing whitespace is stripped,
8022 // a newline is appended, and the edits provided by the language server
8023 // have been applied.
8024 format.await.unwrap();
8025 cx.assert_editor_state(
8026 &[
8027 "one", //
8028 "", //
8029 "twoˇ", //
8030 "", //
8031 "three", //
8032 "four", //
8033 "", //
8034 ]
8035 .join("\n"),
8036 );
8037
8038 // Undoing the formatting undoes the trailing whitespace removal, the
8039 // trailing newline, and the LSP edits.
8040 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8041 cx.assert_editor_state(
8042 &[
8043 "one ", //
8044 "twoˇ", //
8045 "three ", //
8046 "four", //
8047 ]
8048 .join("\n"),
8049 );
8050}
8051
8052#[gpui::test]
8053async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8054 cx: &mut TestAppContext,
8055) {
8056 init_test(cx, |_| {});
8057
8058 cx.update(|cx| {
8059 cx.update_global::<SettingsStore, _>(|settings, cx| {
8060 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8061 settings.auto_signature_help = Some(true);
8062 });
8063 });
8064 });
8065
8066 let mut cx = EditorLspTestContext::new_rust(
8067 lsp::ServerCapabilities {
8068 signature_help_provider: Some(lsp::SignatureHelpOptions {
8069 ..Default::default()
8070 }),
8071 ..Default::default()
8072 },
8073 cx,
8074 )
8075 .await;
8076
8077 let language = Language::new(
8078 LanguageConfig {
8079 name: "Rust".into(),
8080 brackets: BracketPairConfig {
8081 pairs: vec![
8082 BracketPair {
8083 start: "{".to_string(),
8084 end: "}".to_string(),
8085 close: true,
8086 surround: true,
8087 newline: true,
8088 },
8089 BracketPair {
8090 start: "(".to_string(),
8091 end: ")".to_string(),
8092 close: true,
8093 surround: true,
8094 newline: true,
8095 },
8096 BracketPair {
8097 start: "/*".to_string(),
8098 end: " */".to_string(),
8099 close: true,
8100 surround: true,
8101 newline: true,
8102 },
8103 BracketPair {
8104 start: "[".to_string(),
8105 end: "]".to_string(),
8106 close: false,
8107 surround: false,
8108 newline: true,
8109 },
8110 BracketPair {
8111 start: "\"".to_string(),
8112 end: "\"".to_string(),
8113 close: true,
8114 surround: true,
8115 newline: false,
8116 },
8117 BracketPair {
8118 start: "<".to_string(),
8119 end: ">".to_string(),
8120 close: false,
8121 surround: true,
8122 newline: true,
8123 },
8124 ],
8125 ..Default::default()
8126 },
8127 autoclose_before: "})]".to_string(),
8128 ..Default::default()
8129 },
8130 Some(tree_sitter_rust::LANGUAGE.into()),
8131 );
8132 let language = Arc::new(language);
8133
8134 cx.language_registry().add(language.clone());
8135 cx.update_buffer(|buffer, cx| {
8136 buffer.set_language(Some(language), cx);
8137 });
8138
8139 cx.set_state(
8140 &r#"
8141 fn main() {
8142 sampleˇ
8143 }
8144 "#
8145 .unindent(),
8146 );
8147
8148 cx.update_editor(|editor, window, cx| {
8149 editor.handle_input("(", window, cx);
8150 });
8151 cx.assert_editor_state(
8152 &"
8153 fn main() {
8154 sample(ˇ)
8155 }
8156 "
8157 .unindent(),
8158 );
8159
8160 let mocked_response = lsp::SignatureHelp {
8161 signatures: vec![lsp::SignatureInformation {
8162 label: "fn sample(param1: u8, param2: u8)".to_string(),
8163 documentation: None,
8164 parameters: Some(vec![
8165 lsp::ParameterInformation {
8166 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8167 documentation: None,
8168 },
8169 lsp::ParameterInformation {
8170 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8171 documentation: None,
8172 },
8173 ]),
8174 active_parameter: None,
8175 }],
8176 active_signature: Some(0),
8177 active_parameter: Some(0),
8178 };
8179 handle_signature_help_request(&mut cx, mocked_response).await;
8180
8181 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8182 .await;
8183
8184 cx.editor(|editor, _, _| {
8185 let signature_help_state = editor.signature_help_state.popover().cloned();
8186 assert_eq!(
8187 signature_help_state.unwrap().label,
8188 "param1: u8, param2: u8"
8189 );
8190 });
8191}
8192
8193#[gpui::test]
8194async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8195 init_test(cx, |_| {});
8196
8197 cx.update(|cx| {
8198 cx.update_global::<SettingsStore, _>(|settings, cx| {
8199 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8200 settings.auto_signature_help = Some(false);
8201 settings.show_signature_help_after_edits = Some(false);
8202 });
8203 });
8204 });
8205
8206 let mut cx = EditorLspTestContext::new_rust(
8207 lsp::ServerCapabilities {
8208 signature_help_provider: Some(lsp::SignatureHelpOptions {
8209 ..Default::default()
8210 }),
8211 ..Default::default()
8212 },
8213 cx,
8214 )
8215 .await;
8216
8217 let language = Language::new(
8218 LanguageConfig {
8219 name: "Rust".into(),
8220 brackets: BracketPairConfig {
8221 pairs: vec![
8222 BracketPair {
8223 start: "{".to_string(),
8224 end: "}".to_string(),
8225 close: true,
8226 surround: true,
8227 newline: true,
8228 },
8229 BracketPair {
8230 start: "(".to_string(),
8231 end: ")".to_string(),
8232 close: true,
8233 surround: true,
8234 newline: true,
8235 },
8236 BracketPair {
8237 start: "/*".to_string(),
8238 end: " */".to_string(),
8239 close: true,
8240 surround: true,
8241 newline: true,
8242 },
8243 BracketPair {
8244 start: "[".to_string(),
8245 end: "]".to_string(),
8246 close: false,
8247 surround: false,
8248 newline: true,
8249 },
8250 BracketPair {
8251 start: "\"".to_string(),
8252 end: "\"".to_string(),
8253 close: true,
8254 surround: true,
8255 newline: false,
8256 },
8257 BracketPair {
8258 start: "<".to_string(),
8259 end: ">".to_string(),
8260 close: false,
8261 surround: true,
8262 newline: true,
8263 },
8264 ],
8265 ..Default::default()
8266 },
8267 autoclose_before: "})]".to_string(),
8268 ..Default::default()
8269 },
8270 Some(tree_sitter_rust::LANGUAGE.into()),
8271 );
8272 let language = Arc::new(language);
8273
8274 cx.language_registry().add(language.clone());
8275 cx.update_buffer(|buffer, cx| {
8276 buffer.set_language(Some(language), cx);
8277 });
8278
8279 // Ensure that signature_help is not called when no signature help is enabled.
8280 cx.set_state(
8281 &r#"
8282 fn main() {
8283 sampleˇ
8284 }
8285 "#
8286 .unindent(),
8287 );
8288 cx.update_editor(|editor, window, cx| {
8289 editor.handle_input("(", window, cx);
8290 });
8291 cx.assert_editor_state(
8292 &"
8293 fn main() {
8294 sample(ˇ)
8295 }
8296 "
8297 .unindent(),
8298 );
8299 cx.editor(|editor, _, _| {
8300 assert!(editor.signature_help_state.task().is_none());
8301 });
8302
8303 let mocked_response = lsp::SignatureHelp {
8304 signatures: vec![lsp::SignatureInformation {
8305 label: "fn sample(param1: u8, param2: u8)".to_string(),
8306 documentation: None,
8307 parameters: Some(vec![
8308 lsp::ParameterInformation {
8309 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8310 documentation: None,
8311 },
8312 lsp::ParameterInformation {
8313 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8314 documentation: None,
8315 },
8316 ]),
8317 active_parameter: None,
8318 }],
8319 active_signature: Some(0),
8320 active_parameter: Some(0),
8321 };
8322
8323 // Ensure that signature_help is called when enabled afte edits
8324 cx.update(|_, cx| {
8325 cx.update_global::<SettingsStore, _>(|settings, cx| {
8326 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8327 settings.auto_signature_help = Some(false);
8328 settings.show_signature_help_after_edits = Some(true);
8329 });
8330 });
8331 });
8332 cx.set_state(
8333 &r#"
8334 fn main() {
8335 sampleˇ
8336 }
8337 "#
8338 .unindent(),
8339 );
8340 cx.update_editor(|editor, window, cx| {
8341 editor.handle_input("(", window, cx);
8342 });
8343 cx.assert_editor_state(
8344 &"
8345 fn main() {
8346 sample(ˇ)
8347 }
8348 "
8349 .unindent(),
8350 );
8351 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8352 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8353 .await;
8354 cx.update_editor(|editor, _, _| {
8355 let signature_help_state = editor.signature_help_state.popover().cloned();
8356 assert!(signature_help_state.is_some());
8357 assert_eq!(
8358 signature_help_state.unwrap().label,
8359 "param1: u8, param2: u8"
8360 );
8361 editor.signature_help_state = SignatureHelpState::default();
8362 });
8363
8364 // Ensure that signature_help is called when auto signature help override is enabled
8365 cx.update(|_, cx| {
8366 cx.update_global::<SettingsStore, _>(|settings, cx| {
8367 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8368 settings.auto_signature_help = Some(true);
8369 settings.show_signature_help_after_edits = Some(false);
8370 });
8371 });
8372 });
8373 cx.set_state(
8374 &r#"
8375 fn main() {
8376 sampleˇ
8377 }
8378 "#
8379 .unindent(),
8380 );
8381 cx.update_editor(|editor, window, cx| {
8382 editor.handle_input("(", window, cx);
8383 });
8384 cx.assert_editor_state(
8385 &"
8386 fn main() {
8387 sample(ˇ)
8388 }
8389 "
8390 .unindent(),
8391 );
8392 handle_signature_help_request(&mut cx, mocked_response).await;
8393 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8394 .await;
8395 cx.editor(|editor, _, _| {
8396 let signature_help_state = editor.signature_help_state.popover().cloned();
8397 assert!(signature_help_state.is_some());
8398 assert_eq!(
8399 signature_help_state.unwrap().label,
8400 "param1: u8, param2: u8"
8401 );
8402 });
8403}
8404
8405#[gpui::test]
8406async fn test_signature_help(cx: &mut TestAppContext) {
8407 init_test(cx, |_| {});
8408 cx.update(|cx| {
8409 cx.update_global::<SettingsStore, _>(|settings, cx| {
8410 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8411 settings.auto_signature_help = Some(true);
8412 });
8413 });
8414 });
8415
8416 let mut cx = EditorLspTestContext::new_rust(
8417 lsp::ServerCapabilities {
8418 signature_help_provider: Some(lsp::SignatureHelpOptions {
8419 ..Default::default()
8420 }),
8421 ..Default::default()
8422 },
8423 cx,
8424 )
8425 .await;
8426
8427 // A test that directly calls `show_signature_help`
8428 cx.update_editor(|editor, window, cx| {
8429 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8430 });
8431
8432 let mocked_response = lsp::SignatureHelp {
8433 signatures: vec![lsp::SignatureInformation {
8434 label: "fn sample(param1: u8, param2: u8)".to_string(),
8435 documentation: None,
8436 parameters: Some(vec![
8437 lsp::ParameterInformation {
8438 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8439 documentation: None,
8440 },
8441 lsp::ParameterInformation {
8442 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8443 documentation: None,
8444 },
8445 ]),
8446 active_parameter: None,
8447 }],
8448 active_signature: Some(0),
8449 active_parameter: Some(0),
8450 };
8451 handle_signature_help_request(&mut cx, mocked_response).await;
8452
8453 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8454 .await;
8455
8456 cx.editor(|editor, _, _| {
8457 let signature_help_state = editor.signature_help_state.popover().cloned();
8458 assert!(signature_help_state.is_some());
8459 assert_eq!(
8460 signature_help_state.unwrap().label,
8461 "param1: u8, param2: u8"
8462 );
8463 });
8464
8465 // When exiting outside from inside the brackets, `signature_help` is closed.
8466 cx.set_state(indoc! {"
8467 fn main() {
8468 sample(ˇ);
8469 }
8470
8471 fn sample(param1: u8, param2: u8) {}
8472 "});
8473
8474 cx.update_editor(|editor, window, cx| {
8475 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8476 });
8477
8478 let mocked_response = lsp::SignatureHelp {
8479 signatures: Vec::new(),
8480 active_signature: None,
8481 active_parameter: None,
8482 };
8483 handle_signature_help_request(&mut cx, mocked_response).await;
8484
8485 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8486 .await;
8487
8488 cx.editor(|editor, _, _| {
8489 assert!(!editor.signature_help_state.is_shown());
8490 });
8491
8492 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8493 cx.set_state(indoc! {"
8494 fn main() {
8495 sample(ˇ);
8496 }
8497
8498 fn sample(param1: u8, param2: u8) {}
8499 "});
8500
8501 let mocked_response = lsp::SignatureHelp {
8502 signatures: vec![lsp::SignatureInformation {
8503 label: "fn sample(param1: u8, param2: u8)".to_string(),
8504 documentation: None,
8505 parameters: Some(vec![
8506 lsp::ParameterInformation {
8507 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8508 documentation: None,
8509 },
8510 lsp::ParameterInformation {
8511 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8512 documentation: None,
8513 },
8514 ]),
8515 active_parameter: None,
8516 }],
8517 active_signature: Some(0),
8518 active_parameter: Some(0),
8519 };
8520 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8521 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8522 .await;
8523 cx.editor(|editor, _, _| {
8524 assert!(editor.signature_help_state.is_shown());
8525 });
8526
8527 // Restore the popover with more parameter input
8528 cx.set_state(indoc! {"
8529 fn main() {
8530 sample(param1, param2ˇ);
8531 }
8532
8533 fn sample(param1: u8, param2: u8) {}
8534 "});
8535
8536 let mocked_response = lsp::SignatureHelp {
8537 signatures: vec![lsp::SignatureInformation {
8538 label: "fn sample(param1: u8, param2: u8)".to_string(),
8539 documentation: None,
8540 parameters: Some(vec![
8541 lsp::ParameterInformation {
8542 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8543 documentation: None,
8544 },
8545 lsp::ParameterInformation {
8546 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8547 documentation: None,
8548 },
8549 ]),
8550 active_parameter: None,
8551 }],
8552 active_signature: Some(0),
8553 active_parameter: Some(1),
8554 };
8555 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8556 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8557 .await;
8558
8559 // When selecting a range, the popover is gone.
8560 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8561 cx.update_editor(|editor, window, cx| {
8562 editor.change_selections(None, window, cx, |s| {
8563 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8564 })
8565 });
8566 cx.assert_editor_state(indoc! {"
8567 fn main() {
8568 sample(param1, «ˇparam2»);
8569 }
8570
8571 fn sample(param1: u8, param2: u8) {}
8572 "});
8573 cx.editor(|editor, _, _| {
8574 assert!(!editor.signature_help_state.is_shown());
8575 });
8576
8577 // When unselecting again, the popover is back if within the brackets.
8578 cx.update_editor(|editor, window, cx| {
8579 editor.change_selections(None, window, cx, |s| {
8580 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8581 })
8582 });
8583 cx.assert_editor_state(indoc! {"
8584 fn main() {
8585 sample(param1, ˇparam2);
8586 }
8587
8588 fn sample(param1: u8, param2: u8) {}
8589 "});
8590 handle_signature_help_request(&mut cx, mocked_response).await;
8591 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8592 .await;
8593 cx.editor(|editor, _, _| {
8594 assert!(editor.signature_help_state.is_shown());
8595 });
8596
8597 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8598 cx.update_editor(|editor, window, cx| {
8599 editor.change_selections(None, window, cx, |s| {
8600 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8601 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8602 })
8603 });
8604 cx.assert_editor_state(indoc! {"
8605 fn main() {
8606 sample(param1, ˇparam2);
8607 }
8608
8609 fn sample(param1: u8, param2: u8) {}
8610 "});
8611
8612 let mocked_response = lsp::SignatureHelp {
8613 signatures: vec![lsp::SignatureInformation {
8614 label: "fn sample(param1: u8, param2: u8)".to_string(),
8615 documentation: None,
8616 parameters: Some(vec![
8617 lsp::ParameterInformation {
8618 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8619 documentation: None,
8620 },
8621 lsp::ParameterInformation {
8622 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8623 documentation: None,
8624 },
8625 ]),
8626 active_parameter: None,
8627 }],
8628 active_signature: Some(0),
8629 active_parameter: Some(1),
8630 };
8631 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8632 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8633 .await;
8634 cx.update_editor(|editor, _, cx| {
8635 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8636 });
8637 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8638 .await;
8639 cx.update_editor(|editor, window, cx| {
8640 editor.change_selections(None, window, cx, |s| {
8641 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8642 })
8643 });
8644 cx.assert_editor_state(indoc! {"
8645 fn main() {
8646 sample(param1, «ˇparam2»);
8647 }
8648
8649 fn sample(param1: u8, param2: u8) {}
8650 "});
8651 cx.update_editor(|editor, window, cx| {
8652 editor.change_selections(None, window, cx, |s| {
8653 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8654 })
8655 });
8656 cx.assert_editor_state(indoc! {"
8657 fn main() {
8658 sample(param1, ˇparam2);
8659 }
8660
8661 fn sample(param1: u8, param2: u8) {}
8662 "});
8663 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8664 .await;
8665}
8666
8667#[gpui::test]
8668async fn test_completion(cx: &mut TestAppContext) {
8669 init_test(cx, |_| {});
8670
8671 let mut cx = EditorLspTestContext::new_rust(
8672 lsp::ServerCapabilities {
8673 completion_provider: Some(lsp::CompletionOptions {
8674 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8675 resolve_provider: Some(true),
8676 ..Default::default()
8677 }),
8678 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8679 ..Default::default()
8680 },
8681 cx,
8682 )
8683 .await;
8684 let counter = Arc::new(AtomicUsize::new(0));
8685
8686 cx.set_state(indoc! {"
8687 oneˇ
8688 two
8689 three
8690 "});
8691 cx.simulate_keystroke(".");
8692 handle_completion_request(
8693 &mut cx,
8694 indoc! {"
8695 one.|<>
8696 two
8697 three
8698 "},
8699 vec!["first_completion", "second_completion"],
8700 counter.clone(),
8701 )
8702 .await;
8703 cx.condition(|editor, _| editor.context_menu_visible())
8704 .await;
8705 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8706
8707 let _handler = handle_signature_help_request(
8708 &mut cx,
8709 lsp::SignatureHelp {
8710 signatures: vec![lsp::SignatureInformation {
8711 label: "test signature".to_string(),
8712 documentation: None,
8713 parameters: Some(vec![lsp::ParameterInformation {
8714 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8715 documentation: None,
8716 }]),
8717 active_parameter: None,
8718 }],
8719 active_signature: None,
8720 active_parameter: None,
8721 },
8722 );
8723 cx.update_editor(|editor, window, cx| {
8724 assert!(
8725 !editor.signature_help_state.is_shown(),
8726 "No signature help was called for"
8727 );
8728 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8729 });
8730 cx.run_until_parked();
8731 cx.update_editor(|editor, _, _| {
8732 assert!(
8733 !editor.signature_help_state.is_shown(),
8734 "No signature help should be shown when completions menu is open"
8735 );
8736 });
8737
8738 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8739 editor.context_menu_next(&Default::default(), window, cx);
8740 editor
8741 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8742 .unwrap()
8743 });
8744 cx.assert_editor_state(indoc! {"
8745 one.second_completionˇ
8746 two
8747 three
8748 "});
8749
8750 handle_resolve_completion_request(
8751 &mut cx,
8752 Some(vec![
8753 (
8754 //This overlaps with the primary completion edit which is
8755 //misbehavior from the LSP spec, test that we filter it out
8756 indoc! {"
8757 one.second_ˇcompletion
8758 two
8759 threeˇ
8760 "},
8761 "overlapping additional edit",
8762 ),
8763 (
8764 indoc! {"
8765 one.second_completion
8766 two
8767 threeˇ
8768 "},
8769 "\nadditional edit",
8770 ),
8771 ]),
8772 )
8773 .await;
8774 apply_additional_edits.await.unwrap();
8775 cx.assert_editor_state(indoc! {"
8776 one.second_completionˇ
8777 two
8778 three
8779 additional edit
8780 "});
8781
8782 cx.set_state(indoc! {"
8783 one.second_completion
8784 twoˇ
8785 threeˇ
8786 additional edit
8787 "});
8788 cx.simulate_keystroke(" ");
8789 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8790 cx.simulate_keystroke("s");
8791 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8792
8793 cx.assert_editor_state(indoc! {"
8794 one.second_completion
8795 two sˇ
8796 three sˇ
8797 additional edit
8798 "});
8799 handle_completion_request(
8800 &mut cx,
8801 indoc! {"
8802 one.second_completion
8803 two s
8804 three <s|>
8805 additional edit
8806 "},
8807 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8808 counter.clone(),
8809 )
8810 .await;
8811 cx.condition(|editor, _| editor.context_menu_visible())
8812 .await;
8813 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8814
8815 cx.simulate_keystroke("i");
8816
8817 handle_completion_request(
8818 &mut cx,
8819 indoc! {"
8820 one.second_completion
8821 two si
8822 three <si|>
8823 additional edit
8824 "},
8825 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8826 counter.clone(),
8827 )
8828 .await;
8829 cx.condition(|editor, _| editor.context_menu_visible())
8830 .await;
8831 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8832
8833 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8834 editor
8835 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8836 .unwrap()
8837 });
8838 cx.assert_editor_state(indoc! {"
8839 one.second_completion
8840 two sixth_completionˇ
8841 three sixth_completionˇ
8842 additional edit
8843 "});
8844
8845 apply_additional_edits.await.unwrap();
8846
8847 update_test_language_settings(&mut cx, |settings| {
8848 settings.defaults.show_completions_on_input = Some(false);
8849 });
8850 cx.set_state("editorˇ");
8851 cx.simulate_keystroke(".");
8852 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8853 cx.simulate_keystroke("c");
8854 cx.simulate_keystroke("l");
8855 cx.simulate_keystroke("o");
8856 cx.assert_editor_state("editor.cloˇ");
8857 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8858 cx.update_editor(|editor, window, cx| {
8859 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8860 });
8861 handle_completion_request(
8862 &mut cx,
8863 "editor.<clo|>",
8864 vec!["close", "clobber"],
8865 counter.clone(),
8866 )
8867 .await;
8868 cx.condition(|editor, _| editor.context_menu_visible())
8869 .await;
8870 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8871
8872 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8873 editor
8874 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8875 .unwrap()
8876 });
8877 cx.assert_editor_state("editor.closeˇ");
8878 handle_resolve_completion_request(&mut cx, None).await;
8879 apply_additional_edits.await.unwrap();
8880}
8881
8882#[gpui::test]
8883async fn test_multiline_completion(cx: &mut TestAppContext) {
8884 init_test(cx, |_| {});
8885
8886 let fs = FakeFs::new(cx.executor());
8887 fs.insert_tree(
8888 path!("/a"),
8889 json!({
8890 "main.ts": "a",
8891 }),
8892 )
8893 .await;
8894
8895 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
8896 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8897 let typescript_language = Arc::new(Language::new(
8898 LanguageConfig {
8899 name: "TypeScript".into(),
8900 matcher: LanguageMatcher {
8901 path_suffixes: vec!["ts".to_string()],
8902 ..LanguageMatcher::default()
8903 },
8904 line_comments: vec!["// ".into()],
8905 ..LanguageConfig::default()
8906 },
8907 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8908 ));
8909 language_registry.add(typescript_language.clone());
8910 let mut fake_servers = language_registry.register_fake_lsp(
8911 "TypeScript",
8912 FakeLspAdapter {
8913 capabilities: lsp::ServerCapabilities {
8914 completion_provider: Some(lsp::CompletionOptions {
8915 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8916 ..lsp::CompletionOptions::default()
8917 }),
8918 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8919 ..lsp::ServerCapabilities::default()
8920 },
8921 // Emulate vtsls label generation
8922 label_for_completion: Some(Box::new(|item, _| {
8923 let text = if let Some(description) = item
8924 .label_details
8925 .as_ref()
8926 .and_then(|label_details| label_details.description.as_ref())
8927 {
8928 format!("{} {}", item.label, description)
8929 } else if let Some(detail) = &item.detail {
8930 format!("{} {}", item.label, detail)
8931 } else {
8932 item.label.clone()
8933 };
8934 let len = text.len();
8935 Some(language::CodeLabel {
8936 text,
8937 runs: Vec::new(),
8938 filter_range: 0..len,
8939 })
8940 })),
8941 ..FakeLspAdapter::default()
8942 },
8943 );
8944 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8945 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8946 let worktree_id = workspace
8947 .update(cx, |workspace, _window, cx| {
8948 workspace.project().update(cx, |project, cx| {
8949 project.worktrees(cx).next().unwrap().read(cx).id()
8950 })
8951 })
8952 .unwrap();
8953 let _buffer = project
8954 .update(cx, |project, cx| {
8955 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
8956 })
8957 .await
8958 .unwrap();
8959 let editor = workspace
8960 .update(cx, |workspace, window, cx| {
8961 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8962 })
8963 .unwrap()
8964 .await
8965 .unwrap()
8966 .downcast::<Editor>()
8967 .unwrap();
8968 let fake_server = fake_servers.next().await.unwrap();
8969
8970 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8971 let multiline_label_2 = "a\nb\nc\n";
8972 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8973 let multiline_description = "d\ne\nf\n";
8974 let multiline_detail_2 = "g\nh\ni\n";
8975
8976 let mut completion_handle =
8977 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8978 Ok(Some(lsp::CompletionResponse::Array(vec![
8979 lsp::CompletionItem {
8980 label: multiline_label.to_string(),
8981 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8982 range: lsp::Range {
8983 start: lsp::Position {
8984 line: params.text_document_position.position.line,
8985 character: params.text_document_position.position.character,
8986 },
8987 end: lsp::Position {
8988 line: params.text_document_position.position.line,
8989 character: params.text_document_position.position.character,
8990 },
8991 },
8992 new_text: "new_text_1".to_string(),
8993 })),
8994 ..lsp::CompletionItem::default()
8995 },
8996 lsp::CompletionItem {
8997 label: "single line label 1".to_string(),
8998 detail: Some(multiline_detail.to_string()),
8999 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9000 range: lsp::Range {
9001 start: lsp::Position {
9002 line: params.text_document_position.position.line,
9003 character: params.text_document_position.position.character,
9004 },
9005 end: lsp::Position {
9006 line: params.text_document_position.position.line,
9007 character: params.text_document_position.position.character,
9008 },
9009 },
9010 new_text: "new_text_2".to_string(),
9011 })),
9012 ..lsp::CompletionItem::default()
9013 },
9014 lsp::CompletionItem {
9015 label: "single line label 2".to_string(),
9016 label_details: Some(lsp::CompletionItemLabelDetails {
9017 description: Some(multiline_description.to_string()),
9018 detail: None,
9019 }),
9020 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9021 range: lsp::Range {
9022 start: lsp::Position {
9023 line: params.text_document_position.position.line,
9024 character: params.text_document_position.position.character,
9025 },
9026 end: lsp::Position {
9027 line: params.text_document_position.position.line,
9028 character: params.text_document_position.position.character,
9029 },
9030 },
9031 new_text: "new_text_2".to_string(),
9032 })),
9033 ..lsp::CompletionItem::default()
9034 },
9035 lsp::CompletionItem {
9036 label: multiline_label_2.to_string(),
9037 detail: Some(multiline_detail_2.to_string()),
9038 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9039 range: lsp::Range {
9040 start: lsp::Position {
9041 line: params.text_document_position.position.line,
9042 character: params.text_document_position.position.character,
9043 },
9044 end: lsp::Position {
9045 line: params.text_document_position.position.line,
9046 character: params.text_document_position.position.character,
9047 },
9048 },
9049 new_text: "new_text_3".to_string(),
9050 })),
9051 ..lsp::CompletionItem::default()
9052 },
9053 lsp::CompletionItem {
9054 label: "Label with many spaces and \t but without newlines".to_string(),
9055 detail: Some(
9056 "Details with many spaces and \t but without newlines".to_string(),
9057 ),
9058 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9059 range: lsp::Range {
9060 start: lsp::Position {
9061 line: params.text_document_position.position.line,
9062 character: params.text_document_position.position.character,
9063 },
9064 end: lsp::Position {
9065 line: params.text_document_position.position.line,
9066 character: params.text_document_position.position.character,
9067 },
9068 },
9069 new_text: "new_text_4".to_string(),
9070 })),
9071 ..lsp::CompletionItem::default()
9072 },
9073 ])))
9074 });
9075
9076 editor.update_in(cx, |editor, window, cx| {
9077 cx.focus_self(window);
9078 editor.move_to_end(&MoveToEnd, window, cx);
9079 editor.handle_input(".", window, cx);
9080 });
9081 cx.run_until_parked();
9082 completion_handle.next().await.unwrap();
9083
9084 editor.update(cx, |editor, _| {
9085 assert!(editor.context_menu_visible());
9086 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9087 {
9088 let completion_labels = menu
9089 .completions
9090 .borrow()
9091 .iter()
9092 .map(|c| c.label.text.clone())
9093 .collect::<Vec<_>>();
9094 assert_eq!(
9095 completion_labels,
9096 &[
9097 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9098 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9099 "single line label 2 d e f ",
9100 "a b c g h i ",
9101 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9102 ],
9103 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9104 );
9105
9106 for completion in menu
9107 .completions
9108 .borrow()
9109 .iter() {
9110 assert_eq!(
9111 completion.label.filter_range,
9112 0..completion.label.text.len(),
9113 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9114 );
9115 }
9116
9117 } else {
9118 panic!("expected completion menu to be open");
9119 }
9120 });
9121}
9122
9123#[gpui::test]
9124async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9125 init_test(cx, |_| {});
9126 let mut cx = EditorLspTestContext::new_rust(
9127 lsp::ServerCapabilities {
9128 completion_provider: Some(lsp::CompletionOptions {
9129 trigger_characters: Some(vec![".".to_string()]),
9130 ..Default::default()
9131 }),
9132 ..Default::default()
9133 },
9134 cx,
9135 )
9136 .await;
9137 cx.lsp
9138 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9139 Ok(Some(lsp::CompletionResponse::Array(vec![
9140 lsp::CompletionItem {
9141 label: "first".into(),
9142 ..Default::default()
9143 },
9144 lsp::CompletionItem {
9145 label: "last".into(),
9146 ..Default::default()
9147 },
9148 ])))
9149 });
9150 cx.set_state("variableˇ");
9151 cx.simulate_keystroke(".");
9152 cx.executor().run_until_parked();
9153
9154 cx.update_editor(|editor, _, _| {
9155 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9156 {
9157 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9158 } else {
9159 panic!("expected completion menu to be open");
9160 }
9161 });
9162
9163 cx.update_editor(|editor, window, cx| {
9164 editor.move_page_down(&MovePageDown::default(), window, cx);
9165 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9166 {
9167 assert!(
9168 menu.selected_item == 1,
9169 "expected PageDown to select the last item from the context menu"
9170 );
9171 } else {
9172 panic!("expected completion menu to stay open after PageDown");
9173 }
9174 });
9175
9176 cx.update_editor(|editor, window, cx| {
9177 editor.move_page_up(&MovePageUp::default(), window, cx);
9178 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9179 {
9180 assert!(
9181 menu.selected_item == 0,
9182 "expected PageUp to select the first item from the context menu"
9183 );
9184 } else {
9185 panic!("expected completion menu to stay open after PageUp");
9186 }
9187 });
9188}
9189
9190#[gpui::test]
9191async fn test_completion_sort(cx: &mut TestAppContext) {
9192 init_test(cx, |_| {});
9193 let mut cx = EditorLspTestContext::new_rust(
9194 lsp::ServerCapabilities {
9195 completion_provider: Some(lsp::CompletionOptions {
9196 trigger_characters: Some(vec![".".to_string()]),
9197 ..Default::default()
9198 }),
9199 ..Default::default()
9200 },
9201 cx,
9202 )
9203 .await;
9204 cx.lsp
9205 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9206 Ok(Some(lsp::CompletionResponse::Array(vec![
9207 lsp::CompletionItem {
9208 label: "Range".into(),
9209 sort_text: Some("a".into()),
9210 ..Default::default()
9211 },
9212 lsp::CompletionItem {
9213 label: "r".into(),
9214 sort_text: Some("b".into()),
9215 ..Default::default()
9216 },
9217 lsp::CompletionItem {
9218 label: "ret".into(),
9219 sort_text: Some("c".into()),
9220 ..Default::default()
9221 },
9222 lsp::CompletionItem {
9223 label: "return".into(),
9224 sort_text: Some("d".into()),
9225 ..Default::default()
9226 },
9227 lsp::CompletionItem {
9228 label: "slice".into(),
9229 sort_text: Some("d".into()),
9230 ..Default::default()
9231 },
9232 ])))
9233 });
9234 cx.set_state("rˇ");
9235 cx.executor().run_until_parked();
9236 cx.update_editor(|editor, window, cx| {
9237 editor.show_completions(
9238 &ShowCompletions {
9239 trigger: Some("r".into()),
9240 },
9241 window,
9242 cx,
9243 );
9244 });
9245 cx.executor().run_until_parked();
9246
9247 cx.update_editor(|editor, _, _| {
9248 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9249 {
9250 assert_eq!(
9251 completion_menu_entries(&menu),
9252 &["r", "ret", "Range", "return"]
9253 );
9254 } else {
9255 panic!("expected completion menu to be open");
9256 }
9257 });
9258}
9259
9260#[gpui::test]
9261async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9262 init_test(cx, |_| {});
9263
9264 let mut cx = EditorLspTestContext::new_rust(
9265 lsp::ServerCapabilities {
9266 completion_provider: Some(lsp::CompletionOptions {
9267 trigger_characters: Some(vec![".".to_string()]),
9268 resolve_provider: Some(true),
9269 ..Default::default()
9270 }),
9271 ..Default::default()
9272 },
9273 cx,
9274 )
9275 .await;
9276
9277 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9278 cx.simulate_keystroke(".");
9279 let completion_item = lsp::CompletionItem {
9280 label: "Some".into(),
9281 kind: Some(lsp::CompletionItemKind::SNIPPET),
9282 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9283 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9284 kind: lsp::MarkupKind::Markdown,
9285 value: "```rust\nSome(2)\n```".to_string(),
9286 })),
9287 deprecated: Some(false),
9288 sort_text: Some("Some".to_string()),
9289 filter_text: Some("Some".to_string()),
9290 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9291 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9292 range: lsp::Range {
9293 start: lsp::Position {
9294 line: 0,
9295 character: 22,
9296 },
9297 end: lsp::Position {
9298 line: 0,
9299 character: 22,
9300 },
9301 },
9302 new_text: "Some(2)".to_string(),
9303 })),
9304 additional_text_edits: Some(vec![lsp::TextEdit {
9305 range: lsp::Range {
9306 start: lsp::Position {
9307 line: 0,
9308 character: 20,
9309 },
9310 end: lsp::Position {
9311 line: 0,
9312 character: 22,
9313 },
9314 },
9315 new_text: "".to_string(),
9316 }]),
9317 ..Default::default()
9318 };
9319
9320 let closure_completion_item = completion_item.clone();
9321 let counter = Arc::new(AtomicUsize::new(0));
9322 let counter_clone = counter.clone();
9323 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9324 let task_completion_item = closure_completion_item.clone();
9325 counter_clone.fetch_add(1, atomic::Ordering::Release);
9326 async move {
9327 Ok(Some(lsp::CompletionResponse::Array(vec![
9328 task_completion_item,
9329 ])))
9330 }
9331 });
9332
9333 cx.condition(|editor, _| editor.context_menu_visible())
9334 .await;
9335 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9336 assert!(request.next().await.is_some());
9337 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9338
9339 cx.simulate_keystroke("S");
9340 cx.simulate_keystroke("o");
9341 cx.simulate_keystroke("m");
9342 cx.condition(|editor, _| editor.context_menu_visible())
9343 .await;
9344 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9345 assert!(request.next().await.is_some());
9346 assert!(request.next().await.is_some());
9347 assert!(request.next().await.is_some());
9348 request.close();
9349 assert!(request.next().await.is_none());
9350 assert_eq!(
9351 counter.load(atomic::Ordering::Acquire),
9352 4,
9353 "With the completions menu open, only one LSP request should happen per input"
9354 );
9355}
9356
9357#[gpui::test]
9358async fn test_toggle_comment(cx: &mut TestAppContext) {
9359 init_test(cx, |_| {});
9360 let mut cx = EditorTestContext::new(cx).await;
9361 let language = Arc::new(Language::new(
9362 LanguageConfig {
9363 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9364 ..Default::default()
9365 },
9366 Some(tree_sitter_rust::LANGUAGE.into()),
9367 ));
9368 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9369
9370 // If multiple selections intersect a line, the line is only toggled once.
9371 cx.set_state(indoc! {"
9372 fn a() {
9373 «//b();
9374 ˇ»// «c();
9375 //ˇ» d();
9376 }
9377 "});
9378
9379 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9380
9381 cx.assert_editor_state(indoc! {"
9382 fn a() {
9383 «b();
9384 c();
9385 ˇ» d();
9386 }
9387 "});
9388
9389 // The comment prefix is inserted at the same column for every line in a
9390 // selection.
9391 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9392
9393 cx.assert_editor_state(indoc! {"
9394 fn a() {
9395 // «b();
9396 // c();
9397 ˇ»// d();
9398 }
9399 "});
9400
9401 // If a selection ends at the beginning of a line, that line is not toggled.
9402 cx.set_selections_state(indoc! {"
9403 fn a() {
9404 // b();
9405 «// c();
9406 ˇ» // d();
9407 }
9408 "});
9409
9410 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9411
9412 cx.assert_editor_state(indoc! {"
9413 fn a() {
9414 // b();
9415 «c();
9416 ˇ» // d();
9417 }
9418 "});
9419
9420 // If a selection span a single line and is empty, the line is toggled.
9421 cx.set_state(indoc! {"
9422 fn a() {
9423 a();
9424 b();
9425 ˇ
9426 }
9427 "});
9428
9429 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9430
9431 cx.assert_editor_state(indoc! {"
9432 fn a() {
9433 a();
9434 b();
9435 //•ˇ
9436 }
9437 "});
9438
9439 // If a selection span multiple lines, empty lines are not toggled.
9440 cx.set_state(indoc! {"
9441 fn a() {
9442 «a();
9443
9444 c();ˇ»
9445 }
9446 "});
9447
9448 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9449
9450 cx.assert_editor_state(indoc! {"
9451 fn a() {
9452 // «a();
9453
9454 // c();ˇ»
9455 }
9456 "});
9457
9458 // If a selection includes multiple comment prefixes, all lines are uncommented.
9459 cx.set_state(indoc! {"
9460 fn a() {
9461 «// a();
9462 /// b();
9463 //! c();ˇ»
9464 }
9465 "});
9466
9467 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9468
9469 cx.assert_editor_state(indoc! {"
9470 fn a() {
9471 «a();
9472 b();
9473 c();ˇ»
9474 }
9475 "});
9476}
9477
9478#[gpui::test]
9479async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9480 init_test(cx, |_| {});
9481 let mut cx = EditorTestContext::new(cx).await;
9482 let language = Arc::new(Language::new(
9483 LanguageConfig {
9484 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9485 ..Default::default()
9486 },
9487 Some(tree_sitter_rust::LANGUAGE.into()),
9488 ));
9489 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9490
9491 let toggle_comments = &ToggleComments {
9492 advance_downwards: false,
9493 ignore_indent: true,
9494 };
9495
9496 // If multiple selections intersect a line, the line is only toggled once.
9497 cx.set_state(indoc! {"
9498 fn a() {
9499 // «b();
9500 // c();
9501 // ˇ» d();
9502 }
9503 "});
9504
9505 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9506
9507 cx.assert_editor_state(indoc! {"
9508 fn a() {
9509 «b();
9510 c();
9511 ˇ» d();
9512 }
9513 "});
9514
9515 // The comment prefix is inserted at the beginning of each line
9516 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9517
9518 cx.assert_editor_state(indoc! {"
9519 fn a() {
9520 // «b();
9521 // c();
9522 // ˇ» d();
9523 }
9524 "});
9525
9526 // If a selection ends at the beginning of a line, that line is not toggled.
9527 cx.set_selections_state(indoc! {"
9528 fn a() {
9529 // b();
9530 // «c();
9531 ˇ»// d();
9532 }
9533 "});
9534
9535 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9536
9537 cx.assert_editor_state(indoc! {"
9538 fn a() {
9539 // b();
9540 «c();
9541 ˇ»// d();
9542 }
9543 "});
9544
9545 // If a selection span a single line and is empty, the line is toggled.
9546 cx.set_state(indoc! {"
9547 fn a() {
9548 a();
9549 b();
9550 ˇ
9551 }
9552 "});
9553
9554 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9555
9556 cx.assert_editor_state(indoc! {"
9557 fn a() {
9558 a();
9559 b();
9560 //ˇ
9561 }
9562 "});
9563
9564 // If a selection span multiple lines, empty lines are not toggled.
9565 cx.set_state(indoc! {"
9566 fn a() {
9567 «a();
9568
9569 c();ˇ»
9570 }
9571 "});
9572
9573 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9574
9575 cx.assert_editor_state(indoc! {"
9576 fn a() {
9577 // «a();
9578
9579 // c();ˇ»
9580 }
9581 "});
9582
9583 // If a selection includes multiple comment prefixes, all lines are uncommented.
9584 cx.set_state(indoc! {"
9585 fn a() {
9586 // «a();
9587 /// b();
9588 //! c();ˇ»
9589 }
9590 "});
9591
9592 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9593
9594 cx.assert_editor_state(indoc! {"
9595 fn a() {
9596 «a();
9597 b();
9598 c();ˇ»
9599 }
9600 "});
9601}
9602
9603#[gpui::test]
9604async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9605 init_test(cx, |_| {});
9606
9607 let language = Arc::new(Language::new(
9608 LanguageConfig {
9609 line_comments: vec!["// ".into()],
9610 ..Default::default()
9611 },
9612 Some(tree_sitter_rust::LANGUAGE.into()),
9613 ));
9614
9615 let mut cx = EditorTestContext::new(cx).await;
9616
9617 cx.language_registry().add(language.clone());
9618 cx.update_buffer(|buffer, cx| {
9619 buffer.set_language(Some(language), cx);
9620 });
9621
9622 let toggle_comments = &ToggleComments {
9623 advance_downwards: true,
9624 ignore_indent: false,
9625 };
9626
9627 // Single cursor on one line -> advance
9628 // Cursor moves horizontally 3 characters as well on non-blank line
9629 cx.set_state(indoc!(
9630 "fn a() {
9631 ˇdog();
9632 cat();
9633 }"
9634 ));
9635 cx.update_editor(|editor, window, cx| {
9636 editor.toggle_comments(toggle_comments, window, cx);
9637 });
9638 cx.assert_editor_state(indoc!(
9639 "fn a() {
9640 // dog();
9641 catˇ();
9642 }"
9643 ));
9644
9645 // Single selection on one line -> don't advance
9646 cx.set_state(indoc!(
9647 "fn a() {
9648 «dog()ˇ»;
9649 cat();
9650 }"
9651 ));
9652 cx.update_editor(|editor, window, cx| {
9653 editor.toggle_comments(toggle_comments, window, cx);
9654 });
9655 cx.assert_editor_state(indoc!(
9656 "fn a() {
9657 // «dog()ˇ»;
9658 cat();
9659 }"
9660 ));
9661
9662 // Multiple cursors on one line -> advance
9663 cx.set_state(indoc!(
9664 "fn a() {
9665 ˇdˇog();
9666 cat();
9667 }"
9668 ));
9669 cx.update_editor(|editor, window, cx| {
9670 editor.toggle_comments(toggle_comments, window, cx);
9671 });
9672 cx.assert_editor_state(indoc!(
9673 "fn a() {
9674 // dog();
9675 catˇ(ˇ);
9676 }"
9677 ));
9678
9679 // Multiple cursors on one line, with selection -> don't advance
9680 cx.set_state(indoc!(
9681 "fn a() {
9682 ˇdˇog«()ˇ»;
9683 cat();
9684 }"
9685 ));
9686 cx.update_editor(|editor, window, cx| {
9687 editor.toggle_comments(toggle_comments, window, cx);
9688 });
9689 cx.assert_editor_state(indoc!(
9690 "fn a() {
9691 // ˇdˇog«()ˇ»;
9692 cat();
9693 }"
9694 ));
9695
9696 // Single cursor on one line -> advance
9697 // Cursor moves to column 0 on blank line
9698 cx.set_state(indoc!(
9699 "fn a() {
9700 ˇdog();
9701
9702 cat();
9703 }"
9704 ));
9705 cx.update_editor(|editor, window, cx| {
9706 editor.toggle_comments(toggle_comments, window, cx);
9707 });
9708 cx.assert_editor_state(indoc!(
9709 "fn a() {
9710 // dog();
9711 ˇ
9712 cat();
9713 }"
9714 ));
9715
9716 // Single cursor on one line -> advance
9717 // Cursor starts and ends at column 0
9718 cx.set_state(indoc!(
9719 "fn a() {
9720 ˇ dog();
9721 cat();
9722 }"
9723 ));
9724 cx.update_editor(|editor, window, cx| {
9725 editor.toggle_comments(toggle_comments, window, cx);
9726 });
9727 cx.assert_editor_state(indoc!(
9728 "fn a() {
9729 // dog();
9730 ˇ cat();
9731 }"
9732 ));
9733}
9734
9735#[gpui::test]
9736async fn test_toggle_block_comment(cx: &mut TestAppContext) {
9737 init_test(cx, |_| {});
9738
9739 let mut cx = EditorTestContext::new(cx).await;
9740
9741 let html_language = Arc::new(
9742 Language::new(
9743 LanguageConfig {
9744 name: "HTML".into(),
9745 block_comment: Some(("<!-- ".into(), " -->".into())),
9746 ..Default::default()
9747 },
9748 Some(tree_sitter_html::LANGUAGE.into()),
9749 )
9750 .with_injection_query(
9751 r#"
9752 (script_element
9753 (raw_text) @injection.content
9754 (#set! injection.language "javascript"))
9755 "#,
9756 )
9757 .unwrap(),
9758 );
9759
9760 let javascript_language = Arc::new(Language::new(
9761 LanguageConfig {
9762 name: "JavaScript".into(),
9763 line_comments: vec!["// ".into()],
9764 ..Default::default()
9765 },
9766 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9767 ));
9768
9769 cx.language_registry().add(html_language.clone());
9770 cx.language_registry().add(javascript_language.clone());
9771 cx.update_buffer(|buffer, cx| {
9772 buffer.set_language(Some(html_language), cx);
9773 });
9774
9775 // Toggle comments for empty selections
9776 cx.set_state(
9777 &r#"
9778 <p>A</p>ˇ
9779 <p>B</p>ˇ
9780 <p>C</p>ˇ
9781 "#
9782 .unindent(),
9783 );
9784 cx.update_editor(|editor, window, cx| {
9785 editor.toggle_comments(&ToggleComments::default(), window, cx)
9786 });
9787 cx.assert_editor_state(
9788 &r#"
9789 <!-- <p>A</p>ˇ -->
9790 <!-- <p>B</p>ˇ -->
9791 <!-- <p>C</p>ˇ -->
9792 "#
9793 .unindent(),
9794 );
9795 cx.update_editor(|editor, window, cx| {
9796 editor.toggle_comments(&ToggleComments::default(), window, cx)
9797 });
9798 cx.assert_editor_state(
9799 &r#"
9800 <p>A</p>ˇ
9801 <p>B</p>ˇ
9802 <p>C</p>ˇ
9803 "#
9804 .unindent(),
9805 );
9806
9807 // Toggle comments for mixture of empty and non-empty selections, where
9808 // multiple selections occupy a given line.
9809 cx.set_state(
9810 &r#"
9811 <p>A«</p>
9812 <p>ˇ»B</p>ˇ
9813 <p>C«</p>
9814 <p>ˇ»D</p>ˇ
9815 "#
9816 .unindent(),
9817 );
9818
9819 cx.update_editor(|editor, window, cx| {
9820 editor.toggle_comments(&ToggleComments::default(), window, cx)
9821 });
9822 cx.assert_editor_state(
9823 &r#"
9824 <!-- <p>A«</p>
9825 <p>ˇ»B</p>ˇ -->
9826 <!-- <p>C«</p>
9827 <p>ˇ»D</p>ˇ -->
9828 "#
9829 .unindent(),
9830 );
9831 cx.update_editor(|editor, window, cx| {
9832 editor.toggle_comments(&ToggleComments::default(), window, cx)
9833 });
9834 cx.assert_editor_state(
9835 &r#"
9836 <p>A«</p>
9837 <p>ˇ»B</p>ˇ
9838 <p>C«</p>
9839 <p>ˇ»D</p>ˇ
9840 "#
9841 .unindent(),
9842 );
9843
9844 // Toggle comments when different languages are active for different
9845 // selections.
9846 cx.set_state(
9847 &r#"
9848 ˇ<script>
9849 ˇvar x = new Y();
9850 ˇ</script>
9851 "#
9852 .unindent(),
9853 );
9854 cx.executor().run_until_parked();
9855 cx.update_editor(|editor, window, cx| {
9856 editor.toggle_comments(&ToggleComments::default(), window, cx)
9857 });
9858 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9859 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9860 cx.assert_editor_state(
9861 &r#"
9862 <!-- ˇ<script> -->
9863 // ˇvar x = new Y();
9864 <!-- ˇ</script> -->
9865 "#
9866 .unindent(),
9867 );
9868}
9869
9870#[gpui::test]
9871fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9872 init_test(cx, |_| {});
9873
9874 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9875 let multibuffer = cx.new(|cx| {
9876 let mut multibuffer = MultiBuffer::new(ReadWrite);
9877 multibuffer.push_excerpts(
9878 buffer.clone(),
9879 [
9880 ExcerptRange {
9881 context: Point::new(0, 0)..Point::new(0, 4),
9882 primary: None,
9883 },
9884 ExcerptRange {
9885 context: Point::new(1, 0)..Point::new(1, 4),
9886 primary: None,
9887 },
9888 ],
9889 cx,
9890 );
9891 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9892 multibuffer
9893 });
9894
9895 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9896 editor.update_in(cx, |editor, window, cx| {
9897 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9898 editor.change_selections(None, window, cx, |s| {
9899 s.select_ranges([
9900 Point::new(0, 0)..Point::new(0, 0),
9901 Point::new(1, 0)..Point::new(1, 0),
9902 ])
9903 });
9904
9905 editor.handle_input("X", window, cx);
9906 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9907 assert_eq!(
9908 editor.selections.ranges(cx),
9909 [
9910 Point::new(0, 1)..Point::new(0, 1),
9911 Point::new(1, 1)..Point::new(1, 1),
9912 ]
9913 );
9914
9915 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9916 editor.change_selections(None, window, cx, |s| {
9917 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9918 });
9919 editor.backspace(&Default::default(), window, cx);
9920 assert_eq!(editor.text(cx), "Xa\nbbb");
9921 assert_eq!(
9922 editor.selections.ranges(cx),
9923 [Point::new(1, 0)..Point::new(1, 0)]
9924 );
9925
9926 editor.change_selections(None, window, cx, |s| {
9927 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9928 });
9929 editor.backspace(&Default::default(), window, cx);
9930 assert_eq!(editor.text(cx), "X\nbb");
9931 assert_eq!(
9932 editor.selections.ranges(cx),
9933 [Point::new(0, 1)..Point::new(0, 1)]
9934 );
9935 });
9936}
9937
9938#[gpui::test]
9939fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9940 init_test(cx, |_| {});
9941
9942 let markers = vec![('[', ']').into(), ('(', ')').into()];
9943 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9944 indoc! {"
9945 [aaaa
9946 (bbbb]
9947 cccc)",
9948 },
9949 markers.clone(),
9950 );
9951 let excerpt_ranges = markers.into_iter().map(|marker| {
9952 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9953 ExcerptRange {
9954 context,
9955 primary: None,
9956 }
9957 });
9958 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9959 let multibuffer = cx.new(|cx| {
9960 let mut multibuffer = MultiBuffer::new(ReadWrite);
9961 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9962 multibuffer
9963 });
9964
9965 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9966 editor.update_in(cx, |editor, window, cx| {
9967 let (expected_text, selection_ranges) = marked_text_ranges(
9968 indoc! {"
9969 aaaa
9970 bˇbbb
9971 bˇbbˇb
9972 cccc"
9973 },
9974 true,
9975 );
9976 assert_eq!(editor.text(cx), expected_text);
9977 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9978
9979 editor.handle_input("X", window, cx);
9980
9981 let (expected_text, expected_selections) = marked_text_ranges(
9982 indoc! {"
9983 aaaa
9984 bXˇbbXb
9985 bXˇbbXˇb
9986 cccc"
9987 },
9988 false,
9989 );
9990 assert_eq!(editor.text(cx), expected_text);
9991 assert_eq!(editor.selections.ranges(cx), expected_selections);
9992
9993 editor.newline(&Newline, window, cx);
9994 let (expected_text, expected_selections) = marked_text_ranges(
9995 indoc! {"
9996 aaaa
9997 bX
9998 ˇbbX
9999 b
10000 bX
10001 ˇbbX
10002 ˇb
10003 cccc"
10004 },
10005 false,
10006 );
10007 assert_eq!(editor.text(cx), expected_text);
10008 assert_eq!(editor.selections.ranges(cx), expected_selections);
10009 });
10010}
10011
10012#[gpui::test]
10013fn test_refresh_selections(cx: &mut TestAppContext) {
10014 init_test(cx, |_| {});
10015
10016 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10017 let mut excerpt1_id = None;
10018 let multibuffer = cx.new(|cx| {
10019 let mut multibuffer = MultiBuffer::new(ReadWrite);
10020 excerpt1_id = multibuffer
10021 .push_excerpts(
10022 buffer.clone(),
10023 [
10024 ExcerptRange {
10025 context: Point::new(0, 0)..Point::new(1, 4),
10026 primary: None,
10027 },
10028 ExcerptRange {
10029 context: Point::new(1, 0)..Point::new(2, 4),
10030 primary: None,
10031 },
10032 ],
10033 cx,
10034 )
10035 .into_iter()
10036 .next();
10037 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10038 multibuffer
10039 });
10040
10041 let editor = cx.add_window(|window, cx| {
10042 let mut editor = build_editor(multibuffer.clone(), window, cx);
10043 let snapshot = editor.snapshot(window, cx);
10044 editor.change_selections(None, window, cx, |s| {
10045 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10046 });
10047 editor.begin_selection(
10048 Point::new(2, 1).to_display_point(&snapshot),
10049 true,
10050 1,
10051 window,
10052 cx,
10053 );
10054 assert_eq!(
10055 editor.selections.ranges(cx),
10056 [
10057 Point::new(1, 3)..Point::new(1, 3),
10058 Point::new(2, 1)..Point::new(2, 1),
10059 ]
10060 );
10061 editor
10062 });
10063
10064 // Refreshing selections is a no-op when excerpts haven't changed.
10065 _ = editor.update(cx, |editor, window, cx| {
10066 editor.change_selections(None, window, cx, |s| s.refresh());
10067 assert_eq!(
10068 editor.selections.ranges(cx),
10069 [
10070 Point::new(1, 3)..Point::new(1, 3),
10071 Point::new(2, 1)..Point::new(2, 1),
10072 ]
10073 );
10074 });
10075
10076 multibuffer.update(cx, |multibuffer, cx| {
10077 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10078 });
10079 _ = editor.update(cx, |editor, window, cx| {
10080 // Removing an excerpt causes the first selection to become degenerate.
10081 assert_eq!(
10082 editor.selections.ranges(cx),
10083 [
10084 Point::new(0, 0)..Point::new(0, 0),
10085 Point::new(0, 1)..Point::new(0, 1)
10086 ]
10087 );
10088
10089 // Refreshing selections will relocate the first selection to the original buffer
10090 // location.
10091 editor.change_selections(None, window, cx, |s| s.refresh());
10092 assert_eq!(
10093 editor.selections.ranges(cx),
10094 [
10095 Point::new(0, 1)..Point::new(0, 1),
10096 Point::new(0, 3)..Point::new(0, 3)
10097 ]
10098 );
10099 assert!(editor.selections.pending_anchor().is_some());
10100 });
10101}
10102
10103#[gpui::test]
10104fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10105 init_test(cx, |_| {});
10106
10107 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10108 let mut excerpt1_id = None;
10109 let multibuffer = cx.new(|cx| {
10110 let mut multibuffer = MultiBuffer::new(ReadWrite);
10111 excerpt1_id = multibuffer
10112 .push_excerpts(
10113 buffer.clone(),
10114 [
10115 ExcerptRange {
10116 context: Point::new(0, 0)..Point::new(1, 4),
10117 primary: None,
10118 },
10119 ExcerptRange {
10120 context: Point::new(1, 0)..Point::new(2, 4),
10121 primary: None,
10122 },
10123 ],
10124 cx,
10125 )
10126 .into_iter()
10127 .next();
10128 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10129 multibuffer
10130 });
10131
10132 let editor = cx.add_window(|window, cx| {
10133 let mut editor = build_editor(multibuffer.clone(), window, cx);
10134 let snapshot = editor.snapshot(window, cx);
10135 editor.begin_selection(
10136 Point::new(1, 3).to_display_point(&snapshot),
10137 false,
10138 1,
10139 window,
10140 cx,
10141 );
10142 assert_eq!(
10143 editor.selections.ranges(cx),
10144 [Point::new(1, 3)..Point::new(1, 3)]
10145 );
10146 editor
10147 });
10148
10149 multibuffer.update(cx, |multibuffer, cx| {
10150 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10151 });
10152 _ = editor.update(cx, |editor, window, cx| {
10153 assert_eq!(
10154 editor.selections.ranges(cx),
10155 [Point::new(0, 0)..Point::new(0, 0)]
10156 );
10157
10158 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10159 editor.change_selections(None, window, cx, |s| s.refresh());
10160 assert_eq!(
10161 editor.selections.ranges(cx),
10162 [Point::new(0, 3)..Point::new(0, 3)]
10163 );
10164 assert!(editor.selections.pending_anchor().is_some());
10165 });
10166}
10167
10168#[gpui::test]
10169async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10170 init_test(cx, |_| {});
10171
10172 let language = Arc::new(
10173 Language::new(
10174 LanguageConfig {
10175 brackets: BracketPairConfig {
10176 pairs: vec![
10177 BracketPair {
10178 start: "{".to_string(),
10179 end: "}".to_string(),
10180 close: true,
10181 surround: true,
10182 newline: true,
10183 },
10184 BracketPair {
10185 start: "/* ".to_string(),
10186 end: " */".to_string(),
10187 close: true,
10188 surround: true,
10189 newline: true,
10190 },
10191 ],
10192 ..Default::default()
10193 },
10194 ..Default::default()
10195 },
10196 Some(tree_sitter_rust::LANGUAGE.into()),
10197 )
10198 .with_indents_query("")
10199 .unwrap(),
10200 );
10201
10202 let text = concat!(
10203 "{ }\n", //
10204 " x\n", //
10205 " /* */\n", //
10206 "x\n", //
10207 "{{} }\n", //
10208 );
10209
10210 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10211 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10212 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10213 editor
10214 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10215 .await;
10216
10217 editor.update_in(cx, |editor, window, cx| {
10218 editor.change_selections(None, window, cx, |s| {
10219 s.select_display_ranges([
10220 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10221 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10222 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10223 ])
10224 });
10225 editor.newline(&Newline, window, cx);
10226
10227 assert_eq!(
10228 editor.buffer().read(cx).read(cx).text(),
10229 concat!(
10230 "{ \n", // Suppress rustfmt
10231 "\n", //
10232 "}\n", //
10233 " x\n", //
10234 " /* \n", //
10235 " \n", //
10236 " */\n", //
10237 "x\n", //
10238 "{{} \n", //
10239 "}\n", //
10240 )
10241 );
10242 });
10243}
10244
10245#[gpui::test]
10246fn test_highlighted_ranges(cx: &mut TestAppContext) {
10247 init_test(cx, |_| {});
10248
10249 let editor = cx.add_window(|window, cx| {
10250 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10251 build_editor(buffer.clone(), window, cx)
10252 });
10253
10254 _ = editor.update(cx, |editor, window, cx| {
10255 struct Type1;
10256 struct Type2;
10257
10258 let buffer = editor.buffer.read(cx).snapshot(cx);
10259
10260 let anchor_range =
10261 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10262
10263 editor.highlight_background::<Type1>(
10264 &[
10265 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10266 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10267 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10268 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10269 ],
10270 |_| Hsla::red(),
10271 cx,
10272 );
10273 editor.highlight_background::<Type2>(
10274 &[
10275 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10276 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10277 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10278 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10279 ],
10280 |_| Hsla::green(),
10281 cx,
10282 );
10283
10284 let snapshot = editor.snapshot(window, cx);
10285 let mut highlighted_ranges = editor.background_highlights_in_range(
10286 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10287 &snapshot,
10288 cx.theme().colors(),
10289 );
10290 // Enforce a consistent ordering based on color without relying on the ordering of the
10291 // highlight's `TypeId` which is non-executor.
10292 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10293 assert_eq!(
10294 highlighted_ranges,
10295 &[
10296 (
10297 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10298 Hsla::red(),
10299 ),
10300 (
10301 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10302 Hsla::red(),
10303 ),
10304 (
10305 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10306 Hsla::green(),
10307 ),
10308 (
10309 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10310 Hsla::green(),
10311 ),
10312 ]
10313 );
10314 assert_eq!(
10315 editor.background_highlights_in_range(
10316 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10317 &snapshot,
10318 cx.theme().colors(),
10319 ),
10320 &[(
10321 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10322 Hsla::red(),
10323 )]
10324 );
10325 });
10326}
10327
10328#[gpui::test]
10329async fn test_following(cx: &mut TestAppContext) {
10330 init_test(cx, |_| {});
10331
10332 let fs = FakeFs::new(cx.executor());
10333 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10334
10335 let buffer = project.update(cx, |project, cx| {
10336 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10337 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10338 });
10339 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10340 let follower = cx.update(|cx| {
10341 cx.open_window(
10342 WindowOptions {
10343 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10344 gpui::Point::new(px(0.), px(0.)),
10345 gpui::Point::new(px(10.), px(80.)),
10346 ))),
10347 ..Default::default()
10348 },
10349 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10350 )
10351 .unwrap()
10352 });
10353
10354 let is_still_following = Rc::new(RefCell::new(true));
10355 let follower_edit_event_count = Rc::new(RefCell::new(0));
10356 let pending_update = Rc::new(RefCell::new(None));
10357 let leader_entity = leader.root(cx).unwrap();
10358 let follower_entity = follower.root(cx).unwrap();
10359 _ = follower.update(cx, {
10360 let update = pending_update.clone();
10361 let is_still_following = is_still_following.clone();
10362 let follower_edit_event_count = follower_edit_event_count.clone();
10363 |_, window, cx| {
10364 cx.subscribe_in(
10365 &leader_entity,
10366 window,
10367 move |_, leader, event, window, cx| {
10368 leader.read(cx).add_event_to_update_proto(
10369 event,
10370 &mut update.borrow_mut(),
10371 window,
10372 cx,
10373 );
10374 },
10375 )
10376 .detach();
10377
10378 cx.subscribe_in(
10379 &follower_entity,
10380 window,
10381 move |_, _, event: &EditorEvent, _window, _cx| {
10382 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10383 *is_still_following.borrow_mut() = false;
10384 }
10385
10386 if let EditorEvent::BufferEdited = event {
10387 *follower_edit_event_count.borrow_mut() += 1;
10388 }
10389 },
10390 )
10391 .detach();
10392 }
10393 });
10394
10395 // Update the selections only
10396 _ = leader.update(cx, |leader, window, cx| {
10397 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10398 });
10399 follower
10400 .update(cx, |follower, window, cx| {
10401 follower.apply_update_proto(
10402 &project,
10403 pending_update.borrow_mut().take().unwrap(),
10404 window,
10405 cx,
10406 )
10407 })
10408 .unwrap()
10409 .await
10410 .unwrap();
10411 _ = follower.update(cx, |follower, _, cx| {
10412 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10413 });
10414 assert!(*is_still_following.borrow());
10415 assert_eq!(*follower_edit_event_count.borrow(), 0);
10416
10417 // Update the scroll position only
10418 _ = leader.update(cx, |leader, window, cx| {
10419 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10420 });
10421 follower
10422 .update(cx, |follower, window, cx| {
10423 follower.apply_update_proto(
10424 &project,
10425 pending_update.borrow_mut().take().unwrap(),
10426 window,
10427 cx,
10428 )
10429 })
10430 .unwrap()
10431 .await
10432 .unwrap();
10433 assert_eq!(
10434 follower
10435 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10436 .unwrap(),
10437 gpui::Point::new(1.5, 3.5)
10438 );
10439 assert!(*is_still_following.borrow());
10440 assert_eq!(*follower_edit_event_count.borrow(), 0);
10441
10442 // Update the selections and scroll position. The follower's scroll position is updated
10443 // via autoscroll, not via the leader's exact scroll position.
10444 _ = leader.update(cx, |leader, window, cx| {
10445 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10446 leader.request_autoscroll(Autoscroll::newest(), cx);
10447 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10448 });
10449 follower
10450 .update(cx, |follower, window, cx| {
10451 follower.apply_update_proto(
10452 &project,
10453 pending_update.borrow_mut().take().unwrap(),
10454 window,
10455 cx,
10456 )
10457 })
10458 .unwrap()
10459 .await
10460 .unwrap();
10461 _ = follower.update(cx, |follower, _, cx| {
10462 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10463 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10464 });
10465 assert!(*is_still_following.borrow());
10466
10467 // Creating a pending selection that precedes another selection
10468 _ = leader.update(cx, |leader, window, cx| {
10469 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10470 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10471 });
10472 follower
10473 .update(cx, |follower, window, cx| {
10474 follower.apply_update_proto(
10475 &project,
10476 pending_update.borrow_mut().take().unwrap(),
10477 window,
10478 cx,
10479 )
10480 })
10481 .unwrap()
10482 .await
10483 .unwrap();
10484 _ = follower.update(cx, |follower, _, cx| {
10485 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10486 });
10487 assert!(*is_still_following.borrow());
10488
10489 // Extend the pending selection so that it surrounds another selection
10490 _ = leader.update(cx, |leader, window, cx| {
10491 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10492 });
10493 follower
10494 .update(cx, |follower, window, cx| {
10495 follower.apply_update_proto(
10496 &project,
10497 pending_update.borrow_mut().take().unwrap(),
10498 window,
10499 cx,
10500 )
10501 })
10502 .unwrap()
10503 .await
10504 .unwrap();
10505 _ = follower.update(cx, |follower, _, cx| {
10506 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10507 });
10508
10509 // Scrolling locally breaks the follow
10510 _ = follower.update(cx, |follower, window, cx| {
10511 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10512 follower.set_scroll_anchor(
10513 ScrollAnchor {
10514 anchor: top_anchor,
10515 offset: gpui::Point::new(0.0, 0.5),
10516 },
10517 window,
10518 cx,
10519 );
10520 });
10521 assert!(!(*is_still_following.borrow()));
10522}
10523
10524#[gpui::test]
10525async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10526 init_test(cx, |_| {});
10527
10528 let fs = FakeFs::new(cx.executor());
10529 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10530 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10531 let pane = workspace
10532 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10533 .unwrap();
10534
10535 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10536
10537 let leader = pane.update_in(cx, |_, window, cx| {
10538 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10539 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10540 });
10541
10542 // Start following the editor when it has no excerpts.
10543 let mut state_message =
10544 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10545 let workspace_entity = workspace.root(cx).unwrap();
10546 let follower_1 = cx
10547 .update_window(*workspace.deref(), |_, window, cx| {
10548 Editor::from_state_proto(
10549 workspace_entity,
10550 ViewId {
10551 creator: Default::default(),
10552 id: 0,
10553 },
10554 &mut state_message,
10555 window,
10556 cx,
10557 )
10558 })
10559 .unwrap()
10560 .unwrap()
10561 .await
10562 .unwrap();
10563
10564 let update_message = Rc::new(RefCell::new(None));
10565 follower_1.update_in(cx, {
10566 let update = update_message.clone();
10567 |_, window, cx| {
10568 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10569 leader.read(cx).add_event_to_update_proto(
10570 event,
10571 &mut update.borrow_mut(),
10572 window,
10573 cx,
10574 );
10575 })
10576 .detach();
10577 }
10578 });
10579
10580 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10581 (
10582 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10583 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10584 )
10585 });
10586
10587 // Insert some excerpts.
10588 leader.update(cx, |leader, cx| {
10589 leader.buffer.update(cx, |multibuffer, cx| {
10590 let excerpt_ids = multibuffer.push_excerpts(
10591 buffer_1.clone(),
10592 [
10593 ExcerptRange {
10594 context: 1..6,
10595 primary: None,
10596 },
10597 ExcerptRange {
10598 context: 12..15,
10599 primary: None,
10600 },
10601 ExcerptRange {
10602 context: 0..3,
10603 primary: None,
10604 },
10605 ],
10606 cx,
10607 );
10608 multibuffer.insert_excerpts_after(
10609 excerpt_ids[0],
10610 buffer_2.clone(),
10611 [
10612 ExcerptRange {
10613 context: 8..12,
10614 primary: None,
10615 },
10616 ExcerptRange {
10617 context: 0..6,
10618 primary: None,
10619 },
10620 ],
10621 cx,
10622 );
10623 });
10624 });
10625
10626 // Apply the update of adding the excerpts.
10627 follower_1
10628 .update_in(cx, |follower, window, cx| {
10629 follower.apply_update_proto(
10630 &project,
10631 update_message.borrow().clone().unwrap(),
10632 window,
10633 cx,
10634 )
10635 })
10636 .await
10637 .unwrap();
10638 assert_eq!(
10639 follower_1.update(cx, |editor, cx| editor.text(cx)),
10640 leader.update(cx, |editor, cx| editor.text(cx))
10641 );
10642 update_message.borrow_mut().take();
10643
10644 // Start following separately after it already has excerpts.
10645 let mut state_message =
10646 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10647 let workspace_entity = workspace.root(cx).unwrap();
10648 let follower_2 = cx
10649 .update_window(*workspace.deref(), |_, window, cx| {
10650 Editor::from_state_proto(
10651 workspace_entity,
10652 ViewId {
10653 creator: Default::default(),
10654 id: 0,
10655 },
10656 &mut state_message,
10657 window,
10658 cx,
10659 )
10660 })
10661 .unwrap()
10662 .unwrap()
10663 .await
10664 .unwrap();
10665 assert_eq!(
10666 follower_2.update(cx, |editor, cx| editor.text(cx)),
10667 leader.update(cx, |editor, cx| editor.text(cx))
10668 );
10669
10670 // Remove some excerpts.
10671 leader.update(cx, |leader, cx| {
10672 leader.buffer.update(cx, |multibuffer, cx| {
10673 let excerpt_ids = multibuffer.excerpt_ids();
10674 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10675 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10676 });
10677 });
10678
10679 // Apply the update of removing the excerpts.
10680 follower_1
10681 .update_in(cx, |follower, window, cx| {
10682 follower.apply_update_proto(
10683 &project,
10684 update_message.borrow().clone().unwrap(),
10685 window,
10686 cx,
10687 )
10688 })
10689 .await
10690 .unwrap();
10691 follower_2
10692 .update_in(cx, |follower, window, cx| {
10693 follower.apply_update_proto(
10694 &project,
10695 update_message.borrow().clone().unwrap(),
10696 window,
10697 cx,
10698 )
10699 })
10700 .await
10701 .unwrap();
10702 update_message.borrow_mut().take();
10703 assert_eq!(
10704 follower_1.update(cx, |editor, cx| editor.text(cx)),
10705 leader.update(cx, |editor, cx| editor.text(cx))
10706 );
10707}
10708
10709#[gpui::test]
10710async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
10711 init_test(cx, |_| {});
10712
10713 let mut cx = EditorTestContext::new(cx).await;
10714 let lsp_store =
10715 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10716
10717 cx.set_state(indoc! {"
10718 ˇfn func(abc def: i32) -> u32 {
10719 }
10720 "});
10721
10722 cx.update(|_, cx| {
10723 lsp_store.update(cx, |lsp_store, cx| {
10724 lsp_store
10725 .update_diagnostics(
10726 LanguageServerId(0),
10727 lsp::PublishDiagnosticsParams {
10728 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10729 version: None,
10730 diagnostics: vec![
10731 lsp::Diagnostic {
10732 range: lsp::Range::new(
10733 lsp::Position::new(0, 11),
10734 lsp::Position::new(0, 12),
10735 ),
10736 severity: Some(lsp::DiagnosticSeverity::ERROR),
10737 ..Default::default()
10738 },
10739 lsp::Diagnostic {
10740 range: lsp::Range::new(
10741 lsp::Position::new(0, 12),
10742 lsp::Position::new(0, 15),
10743 ),
10744 severity: Some(lsp::DiagnosticSeverity::ERROR),
10745 ..Default::default()
10746 },
10747 lsp::Diagnostic {
10748 range: lsp::Range::new(
10749 lsp::Position::new(0, 25),
10750 lsp::Position::new(0, 28),
10751 ),
10752 severity: Some(lsp::DiagnosticSeverity::ERROR),
10753 ..Default::default()
10754 },
10755 ],
10756 },
10757 &[],
10758 cx,
10759 )
10760 .unwrap()
10761 });
10762 });
10763
10764 executor.run_until_parked();
10765
10766 cx.update_editor(|editor, window, cx| {
10767 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10768 });
10769
10770 cx.assert_editor_state(indoc! {"
10771 fn func(abc def: i32) -> ˇu32 {
10772 }
10773 "});
10774
10775 cx.update_editor(|editor, window, cx| {
10776 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10777 });
10778
10779 cx.assert_editor_state(indoc! {"
10780 fn func(abc ˇdef: i32) -> u32 {
10781 }
10782 "});
10783
10784 cx.update_editor(|editor, window, cx| {
10785 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10786 });
10787
10788 cx.assert_editor_state(indoc! {"
10789 fn func(abcˇ def: i32) -> u32 {
10790 }
10791 "});
10792
10793 cx.update_editor(|editor, window, cx| {
10794 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10795 });
10796
10797 cx.assert_editor_state(indoc! {"
10798 fn func(abc def: i32) -> ˇu32 {
10799 }
10800 "});
10801}
10802
10803#[gpui::test]
10804async fn cycle_through_same_place_diagnostics(
10805 executor: BackgroundExecutor,
10806 cx: &mut TestAppContext,
10807) {
10808 init_test(cx, |_| {});
10809
10810 let mut cx = EditorTestContext::new(cx).await;
10811 let lsp_store =
10812 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10813
10814 cx.set_state(indoc! {"
10815 ˇfn func(abc def: i32) -> u32 {
10816 }
10817 "});
10818
10819 cx.update(|_, cx| {
10820 lsp_store.update(cx, |lsp_store, cx| {
10821 lsp_store
10822 .update_diagnostics(
10823 LanguageServerId(0),
10824 lsp::PublishDiagnosticsParams {
10825 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10826 version: None,
10827 diagnostics: vec![
10828 lsp::Diagnostic {
10829 range: lsp::Range::new(
10830 lsp::Position::new(0, 11),
10831 lsp::Position::new(0, 12),
10832 ),
10833 severity: Some(lsp::DiagnosticSeverity::ERROR),
10834 ..Default::default()
10835 },
10836 lsp::Diagnostic {
10837 range: lsp::Range::new(
10838 lsp::Position::new(0, 12),
10839 lsp::Position::new(0, 15),
10840 ),
10841 severity: Some(lsp::DiagnosticSeverity::ERROR),
10842 ..Default::default()
10843 },
10844 lsp::Diagnostic {
10845 range: lsp::Range::new(
10846 lsp::Position::new(0, 12),
10847 lsp::Position::new(0, 15),
10848 ),
10849 severity: Some(lsp::DiagnosticSeverity::ERROR),
10850 ..Default::default()
10851 },
10852 lsp::Diagnostic {
10853 range: lsp::Range::new(
10854 lsp::Position::new(0, 25),
10855 lsp::Position::new(0, 28),
10856 ),
10857 severity: Some(lsp::DiagnosticSeverity::ERROR),
10858 ..Default::default()
10859 },
10860 ],
10861 },
10862 &[],
10863 cx,
10864 )
10865 .unwrap()
10866 });
10867 });
10868 executor.run_until_parked();
10869
10870 //// Backward
10871
10872 // Fourth diagnostic
10873 cx.update_editor(|editor, window, cx| {
10874 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10875 });
10876 cx.assert_editor_state(indoc! {"
10877 fn func(abc def: i32) -> ˇu32 {
10878 }
10879 "});
10880
10881 // Third diagnostic
10882 cx.update_editor(|editor, window, cx| {
10883 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10884 });
10885 cx.assert_editor_state(indoc! {"
10886 fn func(abc ˇdef: i32) -> u32 {
10887 }
10888 "});
10889
10890 // Second diagnostic, same place
10891 cx.update_editor(|editor, window, cx| {
10892 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10893 });
10894 cx.assert_editor_state(indoc! {"
10895 fn func(abc ˇdef: i32) -> u32 {
10896 }
10897 "});
10898
10899 // First diagnostic
10900 cx.update_editor(|editor, window, cx| {
10901 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10902 });
10903 cx.assert_editor_state(indoc! {"
10904 fn func(abcˇ def: i32) -> u32 {
10905 }
10906 "});
10907
10908 // Wrapped over, fourth diagnostic
10909 cx.update_editor(|editor, window, cx| {
10910 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10911 });
10912 cx.assert_editor_state(indoc! {"
10913 fn func(abc def: i32) -> ˇu32 {
10914 }
10915 "});
10916
10917 cx.update_editor(|editor, window, cx| {
10918 editor.move_to_beginning(&MoveToBeginning, window, cx);
10919 });
10920 cx.assert_editor_state(indoc! {"
10921 ˇfn func(abc def: i32) -> u32 {
10922 }
10923 "});
10924
10925 //// Forward
10926
10927 // First diagnostic
10928 cx.update_editor(|editor, window, cx| {
10929 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10930 });
10931 cx.assert_editor_state(indoc! {"
10932 fn func(abcˇ def: i32) -> u32 {
10933 }
10934 "});
10935
10936 // Second diagnostic
10937 cx.update_editor(|editor, window, cx| {
10938 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10939 });
10940 cx.assert_editor_state(indoc! {"
10941 fn func(abc ˇdef: i32) -> u32 {
10942 }
10943 "});
10944
10945 // Third diagnostic, same place
10946 cx.update_editor(|editor, window, cx| {
10947 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10948 });
10949 cx.assert_editor_state(indoc! {"
10950 fn func(abc ˇdef: i32) -> u32 {
10951 }
10952 "});
10953
10954 // Fourth diagnostic
10955 cx.update_editor(|editor, window, cx| {
10956 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10957 });
10958 cx.assert_editor_state(indoc! {"
10959 fn func(abc def: i32) -> ˇu32 {
10960 }
10961 "});
10962
10963 // Wrapped around, first diagnostic
10964 cx.update_editor(|editor, window, cx| {
10965 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
10966 });
10967 cx.assert_editor_state(indoc! {"
10968 fn func(abcˇ def: i32) -> u32 {
10969 }
10970 "});
10971}
10972
10973#[gpui::test]
10974async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10975 init_test(cx, |_| {});
10976
10977 let mut cx = EditorTestContext::new(cx).await;
10978
10979 cx.set_state(indoc! {"
10980 fn func(abˇc def: i32) -> u32 {
10981 }
10982 "});
10983 let lsp_store =
10984 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10985
10986 cx.update(|_, cx| {
10987 lsp_store.update(cx, |lsp_store, cx| {
10988 lsp_store.update_diagnostics(
10989 LanguageServerId(0),
10990 lsp::PublishDiagnosticsParams {
10991 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10992 version: None,
10993 diagnostics: vec![lsp::Diagnostic {
10994 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10995 severity: Some(lsp::DiagnosticSeverity::ERROR),
10996 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10997 ..Default::default()
10998 }],
10999 },
11000 &[],
11001 cx,
11002 )
11003 })
11004 }).unwrap();
11005 cx.run_until_parked();
11006 cx.update_editor(|editor, window, cx| {
11007 hover_popover::hover(editor, &Default::default(), window, cx)
11008 });
11009 cx.run_until_parked();
11010 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11011}
11012
11013#[gpui::test]
11014async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11015 init_test(cx, |_| {});
11016
11017 let mut cx = EditorTestContext::new(cx).await;
11018
11019 let diff_base = r#"
11020 use some::mod;
11021
11022 const A: u32 = 42;
11023
11024 fn main() {
11025 println!("hello");
11026
11027 println!("world");
11028 }
11029 "#
11030 .unindent();
11031
11032 // Edits are modified, removed, modified, added
11033 cx.set_state(
11034 &r#"
11035 use some::modified;
11036
11037 ˇ
11038 fn main() {
11039 println!("hello there");
11040
11041 println!("around the");
11042 println!("world");
11043 }
11044 "#
11045 .unindent(),
11046 );
11047
11048 cx.set_head_text(&diff_base);
11049 executor.run_until_parked();
11050
11051 cx.update_editor(|editor, window, cx| {
11052 //Wrap around the bottom of the buffer
11053 for _ in 0..3 {
11054 editor.go_to_next_hunk(&GoToHunk, window, cx);
11055 }
11056 });
11057
11058 cx.assert_editor_state(
11059 &r#"
11060 ˇuse some::modified;
11061
11062
11063 fn main() {
11064 println!("hello there");
11065
11066 println!("around the");
11067 println!("world");
11068 }
11069 "#
11070 .unindent(),
11071 );
11072
11073 cx.update_editor(|editor, window, cx| {
11074 //Wrap around the top of the buffer
11075 for _ in 0..2 {
11076 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11077 }
11078 });
11079
11080 cx.assert_editor_state(
11081 &r#"
11082 use some::modified;
11083
11084
11085 fn main() {
11086 ˇ println!("hello there");
11087
11088 println!("around the");
11089 println!("world");
11090 }
11091 "#
11092 .unindent(),
11093 );
11094
11095 cx.update_editor(|editor, window, cx| {
11096 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11097 });
11098
11099 cx.assert_editor_state(
11100 &r#"
11101 use some::modified;
11102
11103 ˇ
11104 fn main() {
11105 println!("hello there");
11106
11107 println!("around the");
11108 println!("world");
11109 }
11110 "#
11111 .unindent(),
11112 );
11113
11114 cx.update_editor(|editor, window, cx| {
11115 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11116 });
11117
11118 cx.assert_editor_state(
11119 &r#"
11120 ˇuse some::modified;
11121
11122
11123 fn main() {
11124 println!("hello there");
11125
11126 println!("around the");
11127 println!("world");
11128 }
11129 "#
11130 .unindent(),
11131 );
11132
11133 cx.update_editor(|editor, window, cx| {
11134 for _ in 0..2 {
11135 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
11136 }
11137 });
11138
11139 cx.assert_editor_state(
11140 &r#"
11141 use some::modified;
11142
11143
11144 fn main() {
11145 ˇ println!("hello there");
11146
11147 println!("around the");
11148 println!("world");
11149 }
11150 "#
11151 .unindent(),
11152 );
11153
11154 cx.update_editor(|editor, window, cx| {
11155 editor.fold(&Fold, window, cx);
11156 });
11157
11158 cx.update_editor(|editor, window, cx| {
11159 editor.go_to_next_hunk(&GoToHunk, window, cx);
11160 });
11161
11162 cx.assert_editor_state(
11163 &r#"
11164 ˇuse some::modified;
11165
11166
11167 fn main() {
11168 println!("hello there");
11169
11170 println!("around the");
11171 println!("world");
11172 }
11173 "#
11174 .unindent(),
11175 );
11176}
11177
11178#[test]
11179fn test_split_words() {
11180 fn split(text: &str) -> Vec<&str> {
11181 split_words(text).collect()
11182 }
11183
11184 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11185 assert_eq!(split("hello_world"), &["hello_", "world"]);
11186 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11187 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11188 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11189 assert_eq!(split("helloworld"), &["helloworld"]);
11190
11191 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11192}
11193
11194#[gpui::test]
11195async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11196 init_test(cx, |_| {});
11197
11198 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11199 let mut assert = |before, after| {
11200 let _state_context = cx.set_state(before);
11201 cx.run_until_parked();
11202 cx.update_editor(|editor, window, cx| {
11203 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11204 });
11205 cx.assert_editor_state(after);
11206 };
11207
11208 // Outside bracket jumps to outside of matching bracket
11209 assert("console.logˇ(var);", "console.log(var)ˇ;");
11210 assert("console.log(var)ˇ;", "console.logˇ(var);");
11211
11212 // Inside bracket jumps to inside of matching bracket
11213 assert("console.log(ˇvar);", "console.log(varˇ);");
11214 assert("console.log(varˇ);", "console.log(ˇvar);");
11215
11216 // When outside a bracket and inside, favor jumping to the inside bracket
11217 assert(
11218 "console.log('foo', [1, 2, 3]ˇ);",
11219 "console.log(ˇ'foo', [1, 2, 3]);",
11220 );
11221 assert(
11222 "console.log(ˇ'foo', [1, 2, 3]);",
11223 "console.log('foo', [1, 2, 3]ˇ);",
11224 );
11225
11226 // Bias forward if two options are equally likely
11227 assert(
11228 "let result = curried_fun()ˇ();",
11229 "let result = curried_fun()()ˇ;",
11230 );
11231
11232 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11233 assert(
11234 indoc! {"
11235 function test() {
11236 console.log('test')ˇ
11237 }"},
11238 indoc! {"
11239 function test() {
11240 console.logˇ('test')
11241 }"},
11242 );
11243}
11244
11245#[gpui::test]
11246async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11247 init_test(cx, |_| {});
11248
11249 let fs = FakeFs::new(cx.executor());
11250 fs.insert_tree(
11251 path!("/a"),
11252 json!({
11253 "main.rs": "fn main() { let a = 5; }",
11254 "other.rs": "// Test file",
11255 }),
11256 )
11257 .await;
11258 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11259
11260 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11261 language_registry.add(Arc::new(Language::new(
11262 LanguageConfig {
11263 name: "Rust".into(),
11264 matcher: LanguageMatcher {
11265 path_suffixes: vec!["rs".to_string()],
11266 ..Default::default()
11267 },
11268 brackets: BracketPairConfig {
11269 pairs: vec![BracketPair {
11270 start: "{".to_string(),
11271 end: "}".to_string(),
11272 close: true,
11273 surround: true,
11274 newline: true,
11275 }],
11276 disabled_scopes_by_bracket_ix: Vec::new(),
11277 },
11278 ..Default::default()
11279 },
11280 Some(tree_sitter_rust::LANGUAGE.into()),
11281 )));
11282 let mut fake_servers = language_registry.register_fake_lsp(
11283 "Rust",
11284 FakeLspAdapter {
11285 capabilities: lsp::ServerCapabilities {
11286 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11287 first_trigger_character: "{".to_string(),
11288 more_trigger_character: None,
11289 }),
11290 ..Default::default()
11291 },
11292 ..Default::default()
11293 },
11294 );
11295
11296 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11297
11298 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11299
11300 let worktree_id = workspace
11301 .update(cx, |workspace, _, cx| {
11302 workspace.project().update(cx, |project, cx| {
11303 project.worktrees(cx).next().unwrap().read(cx).id()
11304 })
11305 })
11306 .unwrap();
11307
11308 let buffer = project
11309 .update(cx, |project, cx| {
11310 project.open_local_buffer(path!("/a/main.rs"), cx)
11311 })
11312 .await
11313 .unwrap();
11314 let editor_handle = workspace
11315 .update(cx, |workspace, window, cx| {
11316 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11317 })
11318 .unwrap()
11319 .await
11320 .unwrap()
11321 .downcast::<Editor>()
11322 .unwrap();
11323
11324 cx.executor().start_waiting();
11325 let fake_server = fake_servers.next().await.unwrap();
11326
11327 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11328 assert_eq!(
11329 params.text_document_position.text_document.uri,
11330 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11331 );
11332 assert_eq!(
11333 params.text_document_position.position,
11334 lsp::Position::new(0, 21),
11335 );
11336
11337 Ok(Some(vec![lsp::TextEdit {
11338 new_text: "]".to_string(),
11339 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11340 }]))
11341 });
11342
11343 editor_handle.update_in(cx, |editor, window, cx| {
11344 window.focus(&editor.focus_handle(cx));
11345 editor.change_selections(None, window, cx, |s| {
11346 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11347 });
11348 editor.handle_input("{", window, cx);
11349 });
11350
11351 cx.executor().run_until_parked();
11352
11353 buffer.update(cx, |buffer, _| {
11354 assert_eq!(
11355 buffer.text(),
11356 "fn main() { let a = {5}; }",
11357 "No extra braces from on type formatting should appear in the buffer"
11358 )
11359 });
11360}
11361
11362#[gpui::test]
11363async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11364 init_test(cx, |_| {});
11365
11366 let fs = FakeFs::new(cx.executor());
11367 fs.insert_tree(
11368 path!("/a"),
11369 json!({
11370 "main.rs": "fn main() { let a = 5; }",
11371 "other.rs": "// Test file",
11372 }),
11373 )
11374 .await;
11375
11376 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11377
11378 let server_restarts = Arc::new(AtomicUsize::new(0));
11379 let closure_restarts = Arc::clone(&server_restarts);
11380 let language_server_name = "test language server";
11381 let language_name: LanguageName = "Rust".into();
11382
11383 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11384 language_registry.add(Arc::new(Language::new(
11385 LanguageConfig {
11386 name: language_name.clone(),
11387 matcher: LanguageMatcher {
11388 path_suffixes: vec!["rs".to_string()],
11389 ..Default::default()
11390 },
11391 ..Default::default()
11392 },
11393 Some(tree_sitter_rust::LANGUAGE.into()),
11394 )));
11395 let mut fake_servers = language_registry.register_fake_lsp(
11396 "Rust",
11397 FakeLspAdapter {
11398 name: language_server_name,
11399 initialization_options: Some(json!({
11400 "testOptionValue": true
11401 })),
11402 initializer: Some(Box::new(move |fake_server| {
11403 let task_restarts = Arc::clone(&closure_restarts);
11404 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11405 task_restarts.fetch_add(1, atomic::Ordering::Release);
11406 futures::future::ready(Ok(()))
11407 });
11408 })),
11409 ..Default::default()
11410 },
11411 );
11412
11413 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11414 let _buffer = project
11415 .update(cx, |project, cx| {
11416 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11417 })
11418 .await
11419 .unwrap();
11420 let _fake_server = fake_servers.next().await.unwrap();
11421 update_test_language_settings(cx, |language_settings| {
11422 language_settings.languages.insert(
11423 language_name.clone(),
11424 LanguageSettingsContent {
11425 tab_size: NonZeroU32::new(8),
11426 ..Default::default()
11427 },
11428 );
11429 });
11430 cx.executor().run_until_parked();
11431 assert_eq!(
11432 server_restarts.load(atomic::Ordering::Acquire),
11433 0,
11434 "Should not restart LSP server on an unrelated change"
11435 );
11436
11437 update_test_project_settings(cx, |project_settings| {
11438 project_settings.lsp.insert(
11439 "Some other server name".into(),
11440 LspSettings {
11441 binary: None,
11442 settings: None,
11443 initialization_options: Some(json!({
11444 "some other init value": false
11445 })),
11446 },
11447 );
11448 });
11449 cx.executor().run_until_parked();
11450 assert_eq!(
11451 server_restarts.load(atomic::Ordering::Acquire),
11452 0,
11453 "Should not restart LSP server on an unrelated LSP settings change"
11454 );
11455
11456 update_test_project_settings(cx, |project_settings| {
11457 project_settings.lsp.insert(
11458 language_server_name.into(),
11459 LspSettings {
11460 binary: None,
11461 settings: None,
11462 initialization_options: Some(json!({
11463 "anotherInitValue": false
11464 })),
11465 },
11466 );
11467 });
11468 cx.executor().run_until_parked();
11469 assert_eq!(
11470 server_restarts.load(atomic::Ordering::Acquire),
11471 1,
11472 "Should restart LSP server on a related LSP settings change"
11473 );
11474
11475 update_test_project_settings(cx, |project_settings| {
11476 project_settings.lsp.insert(
11477 language_server_name.into(),
11478 LspSettings {
11479 binary: None,
11480 settings: None,
11481 initialization_options: Some(json!({
11482 "anotherInitValue": false
11483 })),
11484 },
11485 );
11486 });
11487 cx.executor().run_until_parked();
11488 assert_eq!(
11489 server_restarts.load(atomic::Ordering::Acquire),
11490 1,
11491 "Should not restart LSP server on a related LSP settings change that is the same"
11492 );
11493
11494 update_test_project_settings(cx, |project_settings| {
11495 project_settings.lsp.insert(
11496 language_server_name.into(),
11497 LspSettings {
11498 binary: None,
11499 settings: None,
11500 initialization_options: None,
11501 },
11502 );
11503 });
11504 cx.executor().run_until_parked();
11505 assert_eq!(
11506 server_restarts.load(atomic::Ordering::Acquire),
11507 2,
11508 "Should restart LSP server on another related LSP settings change"
11509 );
11510}
11511
11512#[gpui::test]
11513async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11514 init_test(cx, |_| {});
11515
11516 let mut cx = EditorLspTestContext::new_rust(
11517 lsp::ServerCapabilities {
11518 completion_provider: Some(lsp::CompletionOptions {
11519 trigger_characters: Some(vec![".".to_string()]),
11520 resolve_provider: Some(true),
11521 ..Default::default()
11522 }),
11523 ..Default::default()
11524 },
11525 cx,
11526 )
11527 .await;
11528
11529 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11530 cx.simulate_keystroke(".");
11531 let completion_item = lsp::CompletionItem {
11532 label: "some".into(),
11533 kind: Some(lsp::CompletionItemKind::SNIPPET),
11534 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11535 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11536 kind: lsp::MarkupKind::Markdown,
11537 value: "```rust\nSome(2)\n```".to_string(),
11538 })),
11539 deprecated: Some(false),
11540 sort_text: Some("fffffff2".to_string()),
11541 filter_text: Some("some".to_string()),
11542 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11543 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11544 range: lsp::Range {
11545 start: lsp::Position {
11546 line: 0,
11547 character: 22,
11548 },
11549 end: lsp::Position {
11550 line: 0,
11551 character: 22,
11552 },
11553 },
11554 new_text: "Some(2)".to_string(),
11555 })),
11556 additional_text_edits: Some(vec![lsp::TextEdit {
11557 range: lsp::Range {
11558 start: lsp::Position {
11559 line: 0,
11560 character: 20,
11561 },
11562 end: lsp::Position {
11563 line: 0,
11564 character: 22,
11565 },
11566 },
11567 new_text: "".to_string(),
11568 }]),
11569 ..Default::default()
11570 };
11571
11572 let closure_completion_item = completion_item.clone();
11573 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11574 let task_completion_item = closure_completion_item.clone();
11575 async move {
11576 Ok(Some(lsp::CompletionResponse::Array(vec![
11577 task_completion_item,
11578 ])))
11579 }
11580 });
11581
11582 request.next().await;
11583
11584 cx.condition(|editor, _| editor.context_menu_visible())
11585 .await;
11586 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11587 editor
11588 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11589 .unwrap()
11590 });
11591 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11592
11593 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11594 let task_completion_item = completion_item.clone();
11595 async move { Ok(task_completion_item) }
11596 })
11597 .next()
11598 .await
11599 .unwrap();
11600 apply_additional_edits.await.unwrap();
11601 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11602}
11603
11604#[gpui::test]
11605async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11606 init_test(cx, |_| {});
11607
11608 let mut cx = EditorLspTestContext::new_rust(
11609 lsp::ServerCapabilities {
11610 completion_provider: Some(lsp::CompletionOptions {
11611 trigger_characters: Some(vec![".".to_string()]),
11612 resolve_provider: Some(true),
11613 ..Default::default()
11614 }),
11615 ..Default::default()
11616 },
11617 cx,
11618 )
11619 .await;
11620
11621 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11622 cx.simulate_keystroke(".");
11623
11624 let item1 = lsp::CompletionItem {
11625 label: "method id()".to_string(),
11626 filter_text: Some("id".to_string()),
11627 detail: None,
11628 documentation: None,
11629 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11630 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11631 new_text: ".id".to_string(),
11632 })),
11633 ..lsp::CompletionItem::default()
11634 };
11635
11636 let item2 = lsp::CompletionItem {
11637 label: "other".to_string(),
11638 filter_text: Some("other".to_string()),
11639 detail: None,
11640 documentation: None,
11641 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11642 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11643 new_text: ".other".to_string(),
11644 })),
11645 ..lsp::CompletionItem::default()
11646 };
11647
11648 let item1 = item1.clone();
11649 cx.handle_request::<lsp::request::Completion, _, _>({
11650 let item1 = item1.clone();
11651 move |_, _, _| {
11652 let item1 = item1.clone();
11653 let item2 = item2.clone();
11654 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11655 }
11656 })
11657 .next()
11658 .await;
11659
11660 cx.condition(|editor, _| editor.context_menu_visible())
11661 .await;
11662 cx.update_editor(|editor, _, _| {
11663 let context_menu = editor.context_menu.borrow_mut();
11664 let context_menu = context_menu
11665 .as_ref()
11666 .expect("Should have the context menu deployed");
11667 match context_menu {
11668 CodeContextMenu::Completions(completions_menu) => {
11669 let completions = completions_menu.completions.borrow_mut();
11670 assert_eq!(
11671 completions
11672 .iter()
11673 .map(|completion| &completion.label.text)
11674 .collect::<Vec<_>>(),
11675 vec!["method id()", "other"]
11676 )
11677 }
11678 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11679 }
11680 });
11681
11682 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11683 let item1 = item1.clone();
11684 move |_, item_to_resolve, _| {
11685 let item1 = item1.clone();
11686 async move {
11687 if item1 == item_to_resolve {
11688 Ok(lsp::CompletionItem {
11689 label: "method id()".to_string(),
11690 filter_text: Some("id".to_string()),
11691 detail: Some("Now resolved!".to_string()),
11692 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11693 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11694 range: lsp::Range::new(
11695 lsp::Position::new(0, 22),
11696 lsp::Position::new(0, 22),
11697 ),
11698 new_text: ".id".to_string(),
11699 })),
11700 ..lsp::CompletionItem::default()
11701 })
11702 } else {
11703 Ok(item_to_resolve)
11704 }
11705 }
11706 }
11707 })
11708 .next()
11709 .await
11710 .unwrap();
11711 cx.run_until_parked();
11712
11713 cx.update_editor(|editor, window, cx| {
11714 editor.context_menu_next(&Default::default(), window, cx);
11715 });
11716
11717 cx.update_editor(|editor, _, _| {
11718 let context_menu = editor.context_menu.borrow_mut();
11719 let context_menu = context_menu
11720 .as_ref()
11721 .expect("Should have the context menu deployed");
11722 match context_menu {
11723 CodeContextMenu::Completions(completions_menu) => {
11724 let completions = completions_menu.completions.borrow_mut();
11725 assert_eq!(
11726 completions
11727 .iter()
11728 .map(|completion| &completion.label.text)
11729 .collect::<Vec<_>>(),
11730 vec!["method id() Now resolved!", "other"],
11731 "Should update first completion label, but not second as the filter text did not match."
11732 );
11733 }
11734 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11735 }
11736 });
11737}
11738
11739#[gpui::test]
11740async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
11741 init_test(cx, |_| {});
11742
11743 let mut cx = EditorLspTestContext::new_rust(
11744 lsp::ServerCapabilities {
11745 completion_provider: Some(lsp::CompletionOptions {
11746 trigger_characters: Some(vec![".".to_string()]),
11747 resolve_provider: Some(true),
11748 ..Default::default()
11749 }),
11750 ..Default::default()
11751 },
11752 cx,
11753 )
11754 .await;
11755
11756 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11757 cx.simulate_keystroke(".");
11758
11759 let unresolved_item_1 = lsp::CompletionItem {
11760 label: "id".to_string(),
11761 filter_text: Some("id".to_string()),
11762 detail: None,
11763 documentation: None,
11764 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11765 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11766 new_text: ".id".to_string(),
11767 })),
11768 ..lsp::CompletionItem::default()
11769 };
11770 let resolved_item_1 = lsp::CompletionItem {
11771 additional_text_edits: Some(vec![lsp::TextEdit {
11772 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11773 new_text: "!!".to_string(),
11774 }]),
11775 ..unresolved_item_1.clone()
11776 };
11777 let unresolved_item_2 = lsp::CompletionItem {
11778 label: "other".to_string(),
11779 filter_text: Some("other".to_string()),
11780 detail: None,
11781 documentation: None,
11782 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11783 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11784 new_text: ".other".to_string(),
11785 })),
11786 ..lsp::CompletionItem::default()
11787 };
11788 let resolved_item_2 = lsp::CompletionItem {
11789 additional_text_edits: Some(vec![lsp::TextEdit {
11790 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11791 new_text: "??".to_string(),
11792 }]),
11793 ..unresolved_item_2.clone()
11794 };
11795
11796 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11797 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11798 cx.lsp
11799 .server
11800 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11801 let unresolved_item_1 = unresolved_item_1.clone();
11802 let resolved_item_1 = resolved_item_1.clone();
11803 let unresolved_item_2 = unresolved_item_2.clone();
11804 let resolved_item_2 = resolved_item_2.clone();
11805 let resolve_requests_1 = resolve_requests_1.clone();
11806 let resolve_requests_2 = resolve_requests_2.clone();
11807 move |unresolved_request, _| {
11808 let unresolved_item_1 = unresolved_item_1.clone();
11809 let resolved_item_1 = resolved_item_1.clone();
11810 let unresolved_item_2 = unresolved_item_2.clone();
11811 let resolved_item_2 = resolved_item_2.clone();
11812 let resolve_requests_1 = resolve_requests_1.clone();
11813 let resolve_requests_2 = resolve_requests_2.clone();
11814 async move {
11815 if unresolved_request == unresolved_item_1 {
11816 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11817 Ok(resolved_item_1.clone())
11818 } else if unresolved_request == unresolved_item_2 {
11819 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11820 Ok(resolved_item_2.clone())
11821 } else {
11822 panic!("Unexpected completion item {unresolved_request:?}")
11823 }
11824 }
11825 }
11826 })
11827 .detach();
11828
11829 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11830 let unresolved_item_1 = unresolved_item_1.clone();
11831 let unresolved_item_2 = unresolved_item_2.clone();
11832 async move {
11833 Ok(Some(lsp::CompletionResponse::Array(vec![
11834 unresolved_item_1,
11835 unresolved_item_2,
11836 ])))
11837 }
11838 })
11839 .next()
11840 .await;
11841
11842 cx.condition(|editor, _| editor.context_menu_visible())
11843 .await;
11844 cx.update_editor(|editor, _, _| {
11845 let context_menu = editor.context_menu.borrow_mut();
11846 let context_menu = context_menu
11847 .as_ref()
11848 .expect("Should have the context menu deployed");
11849 match context_menu {
11850 CodeContextMenu::Completions(completions_menu) => {
11851 let completions = completions_menu.completions.borrow_mut();
11852 assert_eq!(
11853 completions
11854 .iter()
11855 .map(|completion| &completion.label.text)
11856 .collect::<Vec<_>>(),
11857 vec!["id", "other"]
11858 )
11859 }
11860 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11861 }
11862 });
11863 cx.run_until_parked();
11864
11865 cx.update_editor(|editor, window, cx| {
11866 editor.context_menu_next(&ContextMenuNext, window, cx);
11867 });
11868 cx.run_until_parked();
11869 cx.update_editor(|editor, window, cx| {
11870 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11871 });
11872 cx.run_until_parked();
11873 cx.update_editor(|editor, window, cx| {
11874 editor.context_menu_next(&ContextMenuNext, window, cx);
11875 });
11876 cx.run_until_parked();
11877 cx.update_editor(|editor, window, cx| {
11878 editor
11879 .compose_completion(&ComposeCompletion::default(), window, cx)
11880 .expect("No task returned")
11881 })
11882 .await
11883 .expect("Completion failed");
11884 cx.run_until_parked();
11885
11886 cx.update_editor(|editor, _, cx| {
11887 assert_eq!(
11888 resolve_requests_1.load(atomic::Ordering::Acquire),
11889 1,
11890 "Should always resolve once despite multiple selections"
11891 );
11892 assert_eq!(
11893 resolve_requests_2.load(atomic::Ordering::Acquire),
11894 1,
11895 "Should always resolve once after multiple selections and applying the completion"
11896 );
11897 assert_eq!(
11898 editor.text(cx),
11899 "fn main() { let a = ??.other; }",
11900 "Should use resolved data when applying the completion"
11901 );
11902 });
11903}
11904
11905#[gpui::test]
11906async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
11907 init_test(cx, |_| {});
11908
11909 let item_0 = lsp::CompletionItem {
11910 label: "abs".into(),
11911 insert_text: Some("abs".into()),
11912 data: Some(json!({ "very": "special"})),
11913 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11914 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11915 lsp::InsertReplaceEdit {
11916 new_text: "abs".to_string(),
11917 insert: lsp::Range::default(),
11918 replace: lsp::Range::default(),
11919 },
11920 )),
11921 ..lsp::CompletionItem::default()
11922 };
11923 let items = iter::once(item_0.clone())
11924 .chain((11..51).map(|i| lsp::CompletionItem {
11925 label: format!("item_{}", i),
11926 insert_text: Some(format!("item_{}", i)),
11927 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11928 ..lsp::CompletionItem::default()
11929 }))
11930 .collect::<Vec<_>>();
11931
11932 let default_commit_characters = vec!["?".to_string()];
11933 let default_data = json!({ "default": "data"});
11934 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11935 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11936 let default_edit_range = lsp::Range {
11937 start: lsp::Position {
11938 line: 0,
11939 character: 5,
11940 },
11941 end: lsp::Position {
11942 line: 0,
11943 character: 5,
11944 },
11945 };
11946
11947 let item_0_out = lsp::CompletionItem {
11948 commit_characters: Some(default_commit_characters.clone()),
11949 insert_text_format: Some(default_insert_text_format),
11950 ..item_0
11951 };
11952 let items_out = iter::once(item_0_out)
11953 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11954 commit_characters: Some(default_commit_characters.clone()),
11955 data: Some(default_data.clone()),
11956 insert_text_mode: Some(default_insert_text_mode),
11957 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11958 range: default_edit_range,
11959 new_text: item.label.clone(),
11960 })),
11961 ..item.clone()
11962 }))
11963 .collect::<Vec<lsp::CompletionItem>>();
11964
11965 let mut cx = EditorLspTestContext::new_rust(
11966 lsp::ServerCapabilities {
11967 completion_provider: Some(lsp::CompletionOptions {
11968 trigger_characters: Some(vec![".".to_string()]),
11969 resolve_provider: Some(true),
11970 ..Default::default()
11971 }),
11972 ..Default::default()
11973 },
11974 cx,
11975 )
11976 .await;
11977
11978 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11979 cx.simulate_keystroke(".");
11980
11981 let completion_data = default_data.clone();
11982 let completion_characters = default_commit_characters.clone();
11983 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11984 let default_data = completion_data.clone();
11985 let default_commit_characters = completion_characters.clone();
11986 let items = items.clone();
11987 async move {
11988 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11989 items,
11990 item_defaults: Some(lsp::CompletionListItemDefaults {
11991 data: Some(default_data.clone()),
11992 commit_characters: Some(default_commit_characters.clone()),
11993 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11994 default_edit_range,
11995 )),
11996 insert_text_format: Some(default_insert_text_format),
11997 insert_text_mode: Some(default_insert_text_mode),
11998 }),
11999 ..lsp::CompletionList::default()
12000 })))
12001 }
12002 })
12003 .next()
12004 .await;
12005
12006 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12007 cx.lsp
12008 .server
12009 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12010 let closure_resolved_items = resolved_items.clone();
12011 move |item_to_resolve, _| {
12012 let closure_resolved_items = closure_resolved_items.clone();
12013 async move {
12014 closure_resolved_items.lock().push(item_to_resolve.clone());
12015 Ok(item_to_resolve)
12016 }
12017 }
12018 })
12019 .detach();
12020
12021 cx.condition(|editor, _| editor.context_menu_visible())
12022 .await;
12023 cx.run_until_parked();
12024 cx.update_editor(|editor, _, _| {
12025 let menu = editor.context_menu.borrow_mut();
12026 match menu.as_ref().expect("should have the completions menu") {
12027 CodeContextMenu::Completions(completions_menu) => {
12028 assert_eq!(
12029 completions_menu
12030 .entries
12031 .borrow()
12032 .iter()
12033 .map(|mat| mat.string.clone())
12034 .collect::<Vec<String>>(),
12035 items_out
12036 .iter()
12037 .map(|completion| completion.label.clone())
12038 .collect::<Vec<String>>()
12039 );
12040 }
12041 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12042 }
12043 });
12044 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12045 // with 4 from the end.
12046 assert_eq!(
12047 *resolved_items.lock(),
12048 [
12049 &items_out[0..16],
12050 &items_out[items_out.len() - 4..items_out.len()]
12051 ]
12052 .concat()
12053 .iter()
12054 .cloned()
12055 .collect::<Vec<lsp::CompletionItem>>()
12056 );
12057 resolved_items.lock().clear();
12058
12059 cx.update_editor(|editor, window, cx| {
12060 editor.context_menu_prev(&ContextMenuPrev, window, cx);
12061 });
12062 cx.run_until_parked();
12063 // Completions that have already been resolved are skipped.
12064 assert_eq!(
12065 *resolved_items.lock(),
12066 items_out[items_out.len() - 16..items_out.len() - 4]
12067 .iter()
12068 .cloned()
12069 .collect::<Vec<lsp::CompletionItem>>()
12070 );
12071 resolved_items.lock().clear();
12072}
12073
12074#[gpui::test]
12075async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12076 init_test(cx, |_| {});
12077
12078 let mut cx = EditorLspTestContext::new(
12079 Language::new(
12080 LanguageConfig {
12081 matcher: LanguageMatcher {
12082 path_suffixes: vec!["jsx".into()],
12083 ..Default::default()
12084 },
12085 overrides: [(
12086 "element".into(),
12087 LanguageConfigOverride {
12088 word_characters: Override::Set(['-'].into_iter().collect()),
12089 ..Default::default()
12090 },
12091 )]
12092 .into_iter()
12093 .collect(),
12094 ..Default::default()
12095 },
12096 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12097 )
12098 .with_override_query("(jsx_self_closing_element) @element")
12099 .unwrap(),
12100 lsp::ServerCapabilities {
12101 completion_provider: Some(lsp::CompletionOptions {
12102 trigger_characters: Some(vec![":".to_string()]),
12103 ..Default::default()
12104 }),
12105 ..Default::default()
12106 },
12107 cx,
12108 )
12109 .await;
12110
12111 cx.lsp
12112 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12113 Ok(Some(lsp::CompletionResponse::Array(vec![
12114 lsp::CompletionItem {
12115 label: "bg-blue".into(),
12116 ..Default::default()
12117 },
12118 lsp::CompletionItem {
12119 label: "bg-red".into(),
12120 ..Default::default()
12121 },
12122 lsp::CompletionItem {
12123 label: "bg-yellow".into(),
12124 ..Default::default()
12125 },
12126 ])))
12127 });
12128
12129 cx.set_state(r#"<p class="bgˇ" />"#);
12130
12131 // Trigger completion when typing a dash, because the dash is an extra
12132 // word character in the 'element' scope, which contains the cursor.
12133 cx.simulate_keystroke("-");
12134 cx.executor().run_until_parked();
12135 cx.update_editor(|editor, _, _| {
12136 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12137 {
12138 assert_eq!(
12139 completion_menu_entries(&menu),
12140 &["bg-red", "bg-blue", "bg-yellow"]
12141 );
12142 } else {
12143 panic!("expected completion menu to be open");
12144 }
12145 });
12146
12147 cx.simulate_keystroke("l");
12148 cx.executor().run_until_parked();
12149 cx.update_editor(|editor, _, _| {
12150 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12151 {
12152 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12153 } else {
12154 panic!("expected completion menu to be open");
12155 }
12156 });
12157
12158 // When filtering completions, consider the character after the '-' to
12159 // be the start of a subword.
12160 cx.set_state(r#"<p class="yelˇ" />"#);
12161 cx.simulate_keystroke("l");
12162 cx.executor().run_until_parked();
12163 cx.update_editor(|editor, _, _| {
12164 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12165 {
12166 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12167 } else {
12168 panic!("expected completion menu to be open");
12169 }
12170 });
12171}
12172
12173fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12174 let entries = menu.entries.borrow();
12175 entries.iter().map(|mat| mat.string.clone()).collect()
12176}
12177
12178#[gpui::test]
12179async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12180 init_test(cx, |settings| {
12181 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12182 FormatterList(vec![Formatter::Prettier].into()),
12183 ))
12184 });
12185
12186 let fs = FakeFs::new(cx.executor());
12187 fs.insert_file(path!("/file.ts"), Default::default()).await;
12188
12189 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12190 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12191
12192 language_registry.add(Arc::new(Language::new(
12193 LanguageConfig {
12194 name: "TypeScript".into(),
12195 matcher: LanguageMatcher {
12196 path_suffixes: vec!["ts".to_string()],
12197 ..Default::default()
12198 },
12199 ..Default::default()
12200 },
12201 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12202 )));
12203 update_test_language_settings(cx, |settings| {
12204 settings.defaults.prettier = Some(PrettierSettings {
12205 allowed: true,
12206 ..PrettierSettings::default()
12207 });
12208 });
12209
12210 let test_plugin = "test_plugin";
12211 let _ = language_registry.register_fake_lsp(
12212 "TypeScript",
12213 FakeLspAdapter {
12214 prettier_plugins: vec![test_plugin],
12215 ..Default::default()
12216 },
12217 );
12218
12219 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12220 let buffer = project
12221 .update(cx, |project, cx| {
12222 project.open_local_buffer(path!("/file.ts"), cx)
12223 })
12224 .await
12225 .unwrap();
12226
12227 let buffer_text = "one\ntwo\nthree\n";
12228 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12229 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12230 editor.update_in(cx, |editor, window, cx| {
12231 editor.set_text(buffer_text, window, cx)
12232 });
12233
12234 editor
12235 .update_in(cx, |editor, window, cx| {
12236 editor.perform_format(
12237 project.clone(),
12238 FormatTrigger::Manual,
12239 FormatTarget::Buffers,
12240 window,
12241 cx,
12242 )
12243 })
12244 .unwrap()
12245 .await;
12246 assert_eq!(
12247 editor.update(cx, |editor, cx| editor.text(cx)),
12248 buffer_text.to_string() + prettier_format_suffix,
12249 "Test prettier formatting was not applied to the original buffer text",
12250 );
12251
12252 update_test_language_settings(cx, |settings| {
12253 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12254 });
12255 let format = editor.update_in(cx, |editor, window, cx| {
12256 editor.perform_format(
12257 project.clone(),
12258 FormatTrigger::Manual,
12259 FormatTarget::Buffers,
12260 window,
12261 cx,
12262 )
12263 });
12264 format.await.unwrap();
12265 assert_eq!(
12266 editor.update(cx, |editor, cx| editor.text(cx)),
12267 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12268 "Autoformatting (via test prettier) was not applied to the original buffer text",
12269 );
12270}
12271
12272#[gpui::test]
12273async fn test_addition_reverts(cx: &mut TestAppContext) {
12274 init_test(cx, |_| {});
12275 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12276 let base_text = indoc! {r#"
12277 struct Row;
12278 struct Row1;
12279 struct Row2;
12280
12281 struct Row4;
12282 struct Row5;
12283 struct Row6;
12284
12285 struct Row8;
12286 struct Row9;
12287 struct Row10;"#};
12288
12289 // When addition hunks are not adjacent to carets, no hunk revert is performed
12290 assert_hunk_revert(
12291 indoc! {r#"struct Row;
12292 struct Row1;
12293 struct Row1.1;
12294 struct Row1.2;
12295 struct Row2;ˇ
12296
12297 struct Row4;
12298 struct Row5;
12299 struct Row6;
12300
12301 struct Row8;
12302 ˇstruct Row9;
12303 struct Row9.1;
12304 struct Row9.2;
12305 struct Row9.3;
12306 struct Row10;"#},
12307 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12308 indoc! {r#"struct Row;
12309 struct Row1;
12310 struct Row1.1;
12311 struct Row1.2;
12312 struct Row2;ˇ
12313
12314 struct Row4;
12315 struct Row5;
12316 struct Row6;
12317
12318 struct Row8;
12319 ˇstruct Row9;
12320 struct Row9.1;
12321 struct Row9.2;
12322 struct Row9.3;
12323 struct Row10;"#},
12324 base_text,
12325 &mut cx,
12326 );
12327 // Same for selections
12328 assert_hunk_revert(
12329 indoc! {r#"struct Row;
12330 struct Row1;
12331 struct Row2;
12332 struct Row2.1;
12333 struct Row2.2;
12334 «ˇ
12335 struct Row4;
12336 struct» Row5;
12337 «struct Row6;
12338 ˇ»
12339 struct Row9.1;
12340 struct Row9.2;
12341 struct Row9.3;
12342 struct Row8;
12343 struct Row9;
12344 struct Row10;"#},
12345 vec![DiffHunkStatus::added_none(), DiffHunkStatus::added_none()],
12346 indoc! {r#"struct Row;
12347 struct Row1;
12348 struct Row2;
12349 struct Row2.1;
12350 struct Row2.2;
12351 «ˇ
12352 struct Row4;
12353 struct» Row5;
12354 «struct Row6;
12355 ˇ»
12356 struct Row9.1;
12357 struct Row9.2;
12358 struct Row9.3;
12359 struct Row8;
12360 struct Row9;
12361 struct Row10;"#},
12362 base_text,
12363 &mut cx,
12364 );
12365
12366 // When carets and selections intersect the addition hunks, those are reverted.
12367 // Adjacent carets got merged.
12368 assert_hunk_revert(
12369 indoc! {r#"struct Row;
12370 ˇ// something on the top
12371 struct Row1;
12372 struct Row2;
12373 struct Roˇw3.1;
12374 struct Row2.2;
12375 struct Row2.3;ˇ
12376
12377 struct Row4;
12378 struct ˇRow5.1;
12379 struct Row5.2;
12380 struct «Rowˇ»5.3;
12381 struct Row5;
12382 struct Row6;
12383 ˇ
12384 struct Row9.1;
12385 struct «Rowˇ»9.2;
12386 struct «ˇRow»9.3;
12387 struct Row8;
12388 struct Row9;
12389 «ˇ// something on bottom»
12390 struct Row10;"#},
12391 vec![
12392 DiffHunkStatus::added_none(),
12393 DiffHunkStatus::added_none(),
12394 DiffHunkStatus::added_none(),
12395 DiffHunkStatus::added_none(),
12396 DiffHunkStatus::added_none(),
12397 ],
12398 indoc! {r#"struct Row;
12399 ˇstruct Row1;
12400 struct Row2;
12401 ˇ
12402 struct Row4;
12403 ˇstruct Row5;
12404 struct Row6;
12405 ˇ
12406 ˇstruct Row8;
12407 struct Row9;
12408 ˇstruct Row10;"#},
12409 base_text,
12410 &mut cx,
12411 );
12412}
12413
12414#[gpui::test]
12415async fn test_modification_reverts(cx: &mut TestAppContext) {
12416 init_test(cx, |_| {});
12417 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12418 let base_text = indoc! {r#"
12419 struct Row;
12420 struct Row1;
12421 struct Row2;
12422
12423 struct Row4;
12424 struct Row5;
12425 struct Row6;
12426
12427 struct Row8;
12428 struct Row9;
12429 struct Row10;"#};
12430
12431 // Modification hunks behave the same as the addition ones.
12432 assert_hunk_revert(
12433 indoc! {r#"struct Row;
12434 struct Row1;
12435 struct Row33;
12436 ˇ
12437 struct Row4;
12438 struct Row5;
12439 struct Row6;
12440 ˇ
12441 struct Row99;
12442 struct Row9;
12443 struct Row10;"#},
12444 vec![
12445 DiffHunkStatus::modified_none(),
12446 DiffHunkStatus::modified_none(),
12447 ],
12448 indoc! {r#"struct Row;
12449 struct Row1;
12450 struct Row33;
12451 ˇ
12452 struct Row4;
12453 struct Row5;
12454 struct Row6;
12455 ˇ
12456 struct Row99;
12457 struct Row9;
12458 struct Row10;"#},
12459 base_text,
12460 &mut cx,
12461 );
12462 assert_hunk_revert(
12463 indoc! {r#"struct Row;
12464 struct Row1;
12465 struct Row33;
12466 «ˇ
12467 struct Row4;
12468 struct» Row5;
12469 «struct Row6;
12470 ˇ»
12471 struct Row99;
12472 struct Row9;
12473 struct Row10;"#},
12474 vec![
12475 DiffHunkStatus::modified_none(),
12476 DiffHunkStatus::modified_none(),
12477 ],
12478 indoc! {r#"struct Row;
12479 struct Row1;
12480 struct Row33;
12481 «ˇ
12482 struct Row4;
12483 struct» Row5;
12484 «struct Row6;
12485 ˇ»
12486 struct Row99;
12487 struct Row9;
12488 struct Row10;"#},
12489 base_text,
12490 &mut cx,
12491 );
12492
12493 assert_hunk_revert(
12494 indoc! {r#"ˇstruct Row1.1;
12495 struct Row1;
12496 «ˇstr»uct Row22;
12497
12498 struct ˇRow44;
12499 struct Row5;
12500 struct «Rˇ»ow66;ˇ
12501
12502 «struˇ»ct Row88;
12503 struct Row9;
12504 struct Row1011;ˇ"#},
12505 vec![
12506 DiffHunkStatus::modified_none(),
12507 DiffHunkStatus::modified_none(),
12508 DiffHunkStatus::modified_none(),
12509 DiffHunkStatus::modified_none(),
12510 DiffHunkStatus::modified_none(),
12511 DiffHunkStatus::modified_none(),
12512 ],
12513 indoc! {r#"struct Row;
12514 ˇstruct Row1;
12515 struct Row2;
12516 ˇ
12517 struct Row4;
12518 ˇstruct Row5;
12519 struct Row6;
12520 ˇ
12521 struct Row8;
12522 ˇstruct Row9;
12523 struct Row10;ˇ"#},
12524 base_text,
12525 &mut cx,
12526 );
12527}
12528
12529#[gpui::test]
12530async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12531 init_test(cx, |_| {});
12532 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12533 let base_text = indoc! {r#"
12534 one
12535
12536 two
12537 three
12538 "#};
12539
12540 cx.set_head_text(base_text);
12541 cx.set_state("\nˇ\n");
12542 cx.executor().run_until_parked();
12543 cx.update_editor(|editor, _window, cx| {
12544 editor.expand_selected_diff_hunks(cx);
12545 });
12546 cx.executor().run_until_parked();
12547 cx.update_editor(|editor, window, cx| {
12548 editor.backspace(&Default::default(), window, cx);
12549 });
12550 cx.run_until_parked();
12551 cx.assert_state_with_diff(
12552 indoc! {r#"
12553
12554 - two
12555 - threeˇ
12556 +
12557 "#}
12558 .to_string(),
12559 );
12560}
12561
12562#[gpui::test]
12563async fn test_deletion_reverts(cx: &mut TestAppContext) {
12564 init_test(cx, |_| {});
12565 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12566 let base_text = indoc! {r#"struct Row;
12567struct Row1;
12568struct Row2;
12569
12570struct Row4;
12571struct Row5;
12572struct Row6;
12573
12574struct Row8;
12575struct Row9;
12576struct Row10;"#};
12577
12578 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12579 assert_hunk_revert(
12580 indoc! {r#"struct Row;
12581 struct Row2;
12582
12583 ˇstruct Row4;
12584 struct Row5;
12585 struct Row6;
12586 ˇ
12587 struct Row8;
12588 struct Row10;"#},
12589 vec![
12590 DiffHunkStatus::deleted_none(),
12591 DiffHunkStatus::deleted_none(),
12592 ],
12593 indoc! {r#"struct Row;
12594 struct Row2;
12595
12596 ˇstruct Row4;
12597 struct Row5;
12598 struct Row6;
12599 ˇ
12600 struct Row8;
12601 struct Row10;"#},
12602 base_text,
12603 &mut cx,
12604 );
12605 assert_hunk_revert(
12606 indoc! {r#"struct Row;
12607 struct Row2;
12608
12609 «ˇstruct Row4;
12610 struct» Row5;
12611 «struct Row6;
12612 ˇ»
12613 struct Row8;
12614 struct Row10;"#},
12615 vec![
12616 DiffHunkStatus::deleted_none(),
12617 DiffHunkStatus::deleted_none(),
12618 ],
12619 indoc! {r#"struct Row;
12620 struct Row2;
12621
12622 «ˇstruct Row4;
12623 struct» Row5;
12624 «struct Row6;
12625 ˇ»
12626 struct Row8;
12627 struct Row10;"#},
12628 base_text,
12629 &mut cx,
12630 );
12631
12632 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12633 assert_hunk_revert(
12634 indoc! {r#"struct Row;
12635 ˇstruct Row2;
12636
12637 struct Row4;
12638 struct Row5;
12639 struct Row6;
12640
12641 struct Row8;ˇ
12642 struct Row10;"#},
12643 vec![
12644 DiffHunkStatus::deleted_none(),
12645 DiffHunkStatus::deleted_none(),
12646 ],
12647 indoc! {r#"struct Row;
12648 struct Row1;
12649 ˇstruct Row2;
12650
12651 struct Row4;
12652 struct Row5;
12653 struct Row6;
12654
12655 struct Row8;ˇ
12656 struct Row9;
12657 struct Row10;"#},
12658 base_text,
12659 &mut cx,
12660 );
12661 assert_hunk_revert(
12662 indoc! {r#"struct Row;
12663 struct Row2«ˇ;
12664 struct Row4;
12665 struct» Row5;
12666 «struct Row6;
12667
12668 struct Row8;ˇ»
12669 struct Row10;"#},
12670 vec![
12671 DiffHunkStatus::deleted_none(),
12672 DiffHunkStatus::deleted_none(),
12673 DiffHunkStatus::deleted_none(),
12674 ],
12675 indoc! {r#"struct Row;
12676 struct Row1;
12677 struct Row2«ˇ;
12678
12679 struct Row4;
12680 struct» Row5;
12681 «struct Row6;
12682
12683 struct Row8;ˇ»
12684 struct Row9;
12685 struct Row10;"#},
12686 base_text,
12687 &mut cx,
12688 );
12689}
12690
12691#[gpui::test]
12692async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
12693 init_test(cx, |_| {});
12694
12695 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12696 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12697 let base_text_3 =
12698 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12699
12700 let text_1 = edit_first_char_of_every_line(base_text_1);
12701 let text_2 = edit_first_char_of_every_line(base_text_2);
12702 let text_3 = edit_first_char_of_every_line(base_text_3);
12703
12704 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12705 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12706 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12707
12708 let multibuffer = cx.new(|cx| {
12709 let mut multibuffer = MultiBuffer::new(ReadWrite);
12710 multibuffer.push_excerpts(
12711 buffer_1.clone(),
12712 [
12713 ExcerptRange {
12714 context: Point::new(0, 0)..Point::new(3, 0),
12715 primary: None,
12716 },
12717 ExcerptRange {
12718 context: Point::new(5, 0)..Point::new(7, 0),
12719 primary: None,
12720 },
12721 ExcerptRange {
12722 context: Point::new(9, 0)..Point::new(10, 4),
12723 primary: None,
12724 },
12725 ],
12726 cx,
12727 );
12728 multibuffer.push_excerpts(
12729 buffer_2.clone(),
12730 [
12731 ExcerptRange {
12732 context: Point::new(0, 0)..Point::new(3, 0),
12733 primary: None,
12734 },
12735 ExcerptRange {
12736 context: Point::new(5, 0)..Point::new(7, 0),
12737 primary: None,
12738 },
12739 ExcerptRange {
12740 context: Point::new(9, 0)..Point::new(10, 4),
12741 primary: None,
12742 },
12743 ],
12744 cx,
12745 );
12746 multibuffer.push_excerpts(
12747 buffer_3.clone(),
12748 [
12749 ExcerptRange {
12750 context: Point::new(0, 0)..Point::new(3, 0),
12751 primary: None,
12752 },
12753 ExcerptRange {
12754 context: Point::new(5, 0)..Point::new(7, 0),
12755 primary: None,
12756 },
12757 ExcerptRange {
12758 context: Point::new(9, 0)..Point::new(10, 4),
12759 primary: None,
12760 },
12761 ],
12762 cx,
12763 );
12764 multibuffer
12765 });
12766
12767 let fs = FakeFs::new(cx.executor());
12768 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12769 let (editor, cx) = cx
12770 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
12771 editor.update_in(cx, |editor, _window, cx| {
12772 for (buffer, diff_base) in [
12773 (buffer_1.clone(), base_text_1),
12774 (buffer_2.clone(), base_text_2),
12775 (buffer_3.clone(), base_text_3),
12776 ] {
12777 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
12778 editor
12779 .buffer
12780 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
12781 }
12782 });
12783 cx.executor().run_until_parked();
12784
12785 editor.update_in(cx, |editor, window, cx| {
12786 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}");
12787 editor.select_all(&SelectAll, window, cx);
12788 editor.git_restore(&Default::default(), window, cx);
12789 });
12790 cx.executor().run_until_parked();
12791
12792 // When all ranges are selected, all buffer hunks are reverted.
12793 editor.update(cx, |editor, cx| {
12794 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");
12795 });
12796 buffer_1.update(cx, |buffer, _| {
12797 assert_eq!(buffer.text(), base_text_1);
12798 });
12799 buffer_2.update(cx, |buffer, _| {
12800 assert_eq!(buffer.text(), base_text_2);
12801 });
12802 buffer_3.update(cx, |buffer, _| {
12803 assert_eq!(buffer.text(), base_text_3);
12804 });
12805
12806 editor.update_in(cx, |editor, window, cx| {
12807 editor.undo(&Default::default(), window, cx);
12808 });
12809
12810 editor.update_in(cx, |editor, window, cx| {
12811 editor.change_selections(None, window, cx, |s| {
12812 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12813 });
12814 editor.git_restore(&Default::default(), window, cx);
12815 });
12816
12817 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12818 // but not affect buffer_2 and its related excerpts.
12819 editor.update(cx, |editor, cx| {
12820 assert_eq!(
12821 editor.text(cx),
12822 "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}"
12823 );
12824 });
12825 buffer_1.update(cx, |buffer, _| {
12826 assert_eq!(buffer.text(), base_text_1);
12827 });
12828 buffer_2.update(cx, |buffer, _| {
12829 assert_eq!(
12830 buffer.text(),
12831 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12832 );
12833 });
12834 buffer_3.update(cx, |buffer, _| {
12835 assert_eq!(
12836 buffer.text(),
12837 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12838 );
12839 });
12840
12841 fn edit_first_char_of_every_line(text: &str) -> String {
12842 text.split('\n')
12843 .map(|line| format!("X{}", &line[1..]))
12844 .collect::<Vec<_>>()
12845 .join("\n")
12846 }
12847}
12848
12849#[gpui::test]
12850async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
12851 init_test(cx, |_| {});
12852
12853 let cols = 4;
12854 let rows = 10;
12855 let sample_text_1 = sample_text(rows, cols, 'a');
12856 assert_eq!(
12857 sample_text_1,
12858 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12859 );
12860 let sample_text_2 = sample_text(rows, cols, 'l');
12861 assert_eq!(
12862 sample_text_2,
12863 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12864 );
12865 let sample_text_3 = sample_text(rows, cols, 'v');
12866 assert_eq!(
12867 sample_text_3,
12868 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12869 );
12870
12871 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12872 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12873 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12874
12875 let multi_buffer = cx.new(|cx| {
12876 let mut multibuffer = MultiBuffer::new(ReadWrite);
12877 multibuffer.push_excerpts(
12878 buffer_1.clone(),
12879 [
12880 ExcerptRange {
12881 context: Point::new(0, 0)..Point::new(3, 0),
12882 primary: None,
12883 },
12884 ExcerptRange {
12885 context: Point::new(5, 0)..Point::new(7, 0),
12886 primary: None,
12887 },
12888 ExcerptRange {
12889 context: Point::new(9, 0)..Point::new(10, 4),
12890 primary: None,
12891 },
12892 ],
12893 cx,
12894 );
12895 multibuffer.push_excerpts(
12896 buffer_2.clone(),
12897 [
12898 ExcerptRange {
12899 context: Point::new(0, 0)..Point::new(3, 0),
12900 primary: None,
12901 },
12902 ExcerptRange {
12903 context: Point::new(5, 0)..Point::new(7, 0),
12904 primary: None,
12905 },
12906 ExcerptRange {
12907 context: Point::new(9, 0)..Point::new(10, 4),
12908 primary: None,
12909 },
12910 ],
12911 cx,
12912 );
12913 multibuffer.push_excerpts(
12914 buffer_3.clone(),
12915 [
12916 ExcerptRange {
12917 context: Point::new(0, 0)..Point::new(3, 0),
12918 primary: None,
12919 },
12920 ExcerptRange {
12921 context: Point::new(5, 0)..Point::new(7, 0),
12922 primary: None,
12923 },
12924 ExcerptRange {
12925 context: Point::new(9, 0)..Point::new(10, 4),
12926 primary: None,
12927 },
12928 ],
12929 cx,
12930 );
12931 multibuffer
12932 });
12933
12934 let fs = FakeFs::new(cx.executor());
12935 fs.insert_tree(
12936 "/a",
12937 json!({
12938 "main.rs": sample_text_1,
12939 "other.rs": sample_text_2,
12940 "lib.rs": sample_text_3,
12941 }),
12942 )
12943 .await;
12944 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12945 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12946 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12947 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12948 Editor::new(
12949 EditorMode::Full,
12950 multi_buffer,
12951 Some(project.clone()),
12952 true,
12953 window,
12954 cx,
12955 )
12956 });
12957 let multibuffer_item_id = workspace
12958 .update(cx, |workspace, window, cx| {
12959 assert!(
12960 workspace.active_item(cx).is_none(),
12961 "active item should be None before the first item is added"
12962 );
12963 workspace.add_item_to_active_pane(
12964 Box::new(multi_buffer_editor.clone()),
12965 None,
12966 true,
12967 window,
12968 cx,
12969 );
12970 let active_item = workspace
12971 .active_item(cx)
12972 .expect("should have an active item after adding the multi buffer");
12973 assert!(
12974 !active_item.is_singleton(cx),
12975 "A multi buffer was expected to active after adding"
12976 );
12977 active_item.item_id()
12978 })
12979 .unwrap();
12980 cx.executor().run_until_parked();
12981
12982 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12983 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12984 s.select_ranges(Some(1..2))
12985 });
12986 editor.open_excerpts(&OpenExcerpts, window, cx);
12987 });
12988 cx.executor().run_until_parked();
12989 let first_item_id = workspace
12990 .update(cx, |workspace, window, cx| {
12991 let active_item = workspace
12992 .active_item(cx)
12993 .expect("should have an active item after navigating into the 1st buffer");
12994 let first_item_id = active_item.item_id();
12995 assert_ne!(
12996 first_item_id, multibuffer_item_id,
12997 "Should navigate into the 1st buffer and activate it"
12998 );
12999 assert!(
13000 active_item.is_singleton(cx),
13001 "New active item should be a singleton buffer"
13002 );
13003 assert_eq!(
13004 active_item
13005 .act_as::<Editor>(cx)
13006 .expect("should have navigated into an editor for the 1st buffer")
13007 .read(cx)
13008 .text(cx),
13009 sample_text_1
13010 );
13011
13012 workspace
13013 .go_back(workspace.active_pane().downgrade(), window, cx)
13014 .detach_and_log_err(cx);
13015
13016 first_item_id
13017 })
13018 .unwrap();
13019 cx.executor().run_until_parked();
13020 workspace
13021 .update(cx, |workspace, _, cx| {
13022 let active_item = workspace
13023 .active_item(cx)
13024 .expect("should have an active item after navigating back");
13025 assert_eq!(
13026 active_item.item_id(),
13027 multibuffer_item_id,
13028 "Should navigate back to the multi buffer"
13029 );
13030 assert!(!active_item.is_singleton(cx));
13031 })
13032 .unwrap();
13033
13034 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13035 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13036 s.select_ranges(Some(39..40))
13037 });
13038 editor.open_excerpts(&OpenExcerpts, window, cx);
13039 });
13040 cx.executor().run_until_parked();
13041 let second_item_id = workspace
13042 .update(cx, |workspace, window, cx| {
13043 let active_item = workspace
13044 .active_item(cx)
13045 .expect("should have an active item after navigating into the 2nd buffer");
13046 let second_item_id = active_item.item_id();
13047 assert_ne!(
13048 second_item_id, multibuffer_item_id,
13049 "Should navigate away from the multibuffer"
13050 );
13051 assert_ne!(
13052 second_item_id, first_item_id,
13053 "Should navigate into the 2nd buffer and activate it"
13054 );
13055 assert!(
13056 active_item.is_singleton(cx),
13057 "New active item should be a singleton buffer"
13058 );
13059 assert_eq!(
13060 active_item
13061 .act_as::<Editor>(cx)
13062 .expect("should have navigated into an editor")
13063 .read(cx)
13064 .text(cx),
13065 sample_text_2
13066 );
13067
13068 workspace
13069 .go_back(workspace.active_pane().downgrade(), window, cx)
13070 .detach_and_log_err(cx);
13071
13072 second_item_id
13073 })
13074 .unwrap();
13075 cx.executor().run_until_parked();
13076 workspace
13077 .update(cx, |workspace, _, cx| {
13078 let active_item = workspace
13079 .active_item(cx)
13080 .expect("should have an active item after navigating back from the 2nd buffer");
13081 assert_eq!(
13082 active_item.item_id(),
13083 multibuffer_item_id,
13084 "Should navigate back from the 2nd buffer to the multi buffer"
13085 );
13086 assert!(!active_item.is_singleton(cx));
13087 })
13088 .unwrap();
13089
13090 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13091 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13092 s.select_ranges(Some(70..70))
13093 });
13094 editor.open_excerpts(&OpenExcerpts, window, cx);
13095 });
13096 cx.executor().run_until_parked();
13097 workspace
13098 .update(cx, |workspace, window, cx| {
13099 let active_item = workspace
13100 .active_item(cx)
13101 .expect("should have an active item after navigating into the 3rd buffer");
13102 let third_item_id = active_item.item_id();
13103 assert_ne!(
13104 third_item_id, multibuffer_item_id,
13105 "Should navigate into the 3rd buffer and activate it"
13106 );
13107 assert_ne!(third_item_id, first_item_id);
13108 assert_ne!(third_item_id, second_item_id);
13109 assert!(
13110 active_item.is_singleton(cx),
13111 "New active item should be a singleton buffer"
13112 );
13113 assert_eq!(
13114 active_item
13115 .act_as::<Editor>(cx)
13116 .expect("should have navigated into an editor")
13117 .read(cx)
13118 .text(cx),
13119 sample_text_3
13120 );
13121
13122 workspace
13123 .go_back(workspace.active_pane().downgrade(), window, cx)
13124 .detach_and_log_err(cx);
13125 })
13126 .unwrap();
13127 cx.executor().run_until_parked();
13128 workspace
13129 .update(cx, |workspace, _, cx| {
13130 let active_item = workspace
13131 .active_item(cx)
13132 .expect("should have an active item after navigating back from the 3rd buffer");
13133 assert_eq!(
13134 active_item.item_id(),
13135 multibuffer_item_id,
13136 "Should navigate back from the 3rd buffer to the multi buffer"
13137 );
13138 assert!(!active_item.is_singleton(cx));
13139 })
13140 .unwrap();
13141}
13142
13143#[gpui::test]
13144async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13145 init_test(cx, |_| {});
13146
13147 let mut cx = EditorTestContext::new(cx).await;
13148
13149 let diff_base = r#"
13150 use some::mod;
13151
13152 const A: u32 = 42;
13153
13154 fn main() {
13155 println!("hello");
13156
13157 println!("world");
13158 }
13159 "#
13160 .unindent();
13161
13162 cx.set_state(
13163 &r#"
13164 use some::modified;
13165
13166 ˇ
13167 fn main() {
13168 println!("hello there");
13169
13170 println!("around the");
13171 println!("world");
13172 }
13173 "#
13174 .unindent(),
13175 );
13176
13177 cx.set_head_text(&diff_base);
13178 executor.run_until_parked();
13179
13180 cx.update_editor(|editor, window, cx| {
13181 editor.go_to_next_hunk(&GoToHunk, window, cx);
13182 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13183 });
13184 executor.run_until_parked();
13185 cx.assert_state_with_diff(
13186 r#"
13187 use some::modified;
13188
13189
13190 fn main() {
13191 - println!("hello");
13192 + ˇ println!("hello there");
13193
13194 println!("around the");
13195 println!("world");
13196 }
13197 "#
13198 .unindent(),
13199 );
13200
13201 cx.update_editor(|editor, window, cx| {
13202 for _ in 0..2 {
13203 editor.go_to_next_hunk(&GoToHunk, window, cx);
13204 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13205 }
13206 });
13207 executor.run_until_parked();
13208 cx.assert_state_with_diff(
13209 r#"
13210 - use some::mod;
13211 + ˇuse some::modified;
13212
13213
13214 fn main() {
13215 - println!("hello");
13216 + println!("hello there");
13217
13218 + println!("around the");
13219 println!("world");
13220 }
13221 "#
13222 .unindent(),
13223 );
13224
13225 cx.update_editor(|editor, window, cx| {
13226 editor.go_to_next_hunk(&GoToHunk, window, cx);
13227 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13228 });
13229 executor.run_until_parked();
13230 cx.assert_state_with_diff(
13231 r#"
13232 - use some::mod;
13233 + use some::modified;
13234
13235 - const A: u32 = 42;
13236 ˇ
13237 fn main() {
13238 - println!("hello");
13239 + println!("hello there");
13240
13241 + println!("around the");
13242 println!("world");
13243 }
13244 "#
13245 .unindent(),
13246 );
13247
13248 cx.update_editor(|editor, window, cx| {
13249 editor.cancel(&Cancel, window, cx);
13250 });
13251
13252 cx.assert_state_with_diff(
13253 r#"
13254 use some::modified;
13255
13256 ˇ
13257 fn main() {
13258 println!("hello there");
13259
13260 println!("around the");
13261 println!("world");
13262 }
13263 "#
13264 .unindent(),
13265 );
13266}
13267
13268#[gpui::test]
13269async fn test_diff_base_change_with_expanded_diff_hunks(
13270 executor: BackgroundExecutor,
13271 cx: &mut TestAppContext,
13272) {
13273 init_test(cx, |_| {});
13274
13275 let mut cx = EditorTestContext::new(cx).await;
13276
13277 let diff_base = r#"
13278 use some::mod1;
13279 use some::mod2;
13280
13281 const A: u32 = 42;
13282 const B: u32 = 42;
13283 const C: u32 = 42;
13284
13285 fn main() {
13286 println!("hello");
13287
13288 println!("world");
13289 }
13290 "#
13291 .unindent();
13292
13293 cx.set_state(
13294 &r#"
13295 use some::mod2;
13296
13297 const A: u32 = 42;
13298 const C: u32 = 42;
13299
13300 fn main(ˇ) {
13301 //println!("hello");
13302
13303 println!("world");
13304 //
13305 //
13306 }
13307 "#
13308 .unindent(),
13309 );
13310
13311 cx.set_head_text(&diff_base);
13312 executor.run_until_parked();
13313
13314 cx.update_editor(|editor, window, cx| {
13315 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13316 });
13317 executor.run_until_parked();
13318 cx.assert_state_with_diff(
13319 r#"
13320 - use some::mod1;
13321 use some::mod2;
13322
13323 const A: u32 = 42;
13324 - const B: u32 = 42;
13325 const C: u32 = 42;
13326
13327 fn main(ˇ) {
13328 - println!("hello");
13329 + //println!("hello");
13330
13331 println!("world");
13332 + //
13333 + //
13334 }
13335 "#
13336 .unindent(),
13337 );
13338
13339 cx.set_head_text("new diff base!");
13340 executor.run_until_parked();
13341 cx.assert_state_with_diff(
13342 r#"
13343 - new diff base!
13344 + use some::mod2;
13345 +
13346 + const A: u32 = 42;
13347 + const C: u32 = 42;
13348 +
13349 + fn main(ˇ) {
13350 + //println!("hello");
13351 +
13352 + println!("world");
13353 + //
13354 + //
13355 + }
13356 "#
13357 .unindent(),
13358 );
13359}
13360
13361#[gpui::test]
13362async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13363 init_test(cx, |_| {});
13364
13365 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13366 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13367 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13368 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13369 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13370 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13371
13372 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13373 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13374 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13375
13376 let multi_buffer = cx.new(|cx| {
13377 let mut multibuffer = MultiBuffer::new(ReadWrite);
13378 multibuffer.push_excerpts(
13379 buffer_1.clone(),
13380 [
13381 ExcerptRange {
13382 context: Point::new(0, 0)..Point::new(3, 0),
13383 primary: None,
13384 },
13385 ExcerptRange {
13386 context: Point::new(5, 0)..Point::new(7, 0),
13387 primary: None,
13388 },
13389 ExcerptRange {
13390 context: Point::new(9, 0)..Point::new(10, 3),
13391 primary: None,
13392 },
13393 ],
13394 cx,
13395 );
13396 multibuffer.push_excerpts(
13397 buffer_2.clone(),
13398 [
13399 ExcerptRange {
13400 context: Point::new(0, 0)..Point::new(3, 0),
13401 primary: None,
13402 },
13403 ExcerptRange {
13404 context: Point::new(5, 0)..Point::new(7, 0),
13405 primary: None,
13406 },
13407 ExcerptRange {
13408 context: Point::new(9, 0)..Point::new(10, 3),
13409 primary: None,
13410 },
13411 ],
13412 cx,
13413 );
13414 multibuffer.push_excerpts(
13415 buffer_3.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, 3),
13427 primary: None,
13428 },
13429 ],
13430 cx,
13431 );
13432 multibuffer
13433 });
13434
13435 let editor = cx.add_window(|window, cx| {
13436 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13437 });
13438 editor
13439 .update(cx, |editor, _window, cx| {
13440 for (buffer, diff_base) in [
13441 (buffer_1.clone(), file_1_old),
13442 (buffer_2.clone(), file_2_old),
13443 (buffer_3.clone(), file_3_old),
13444 ] {
13445 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13446 editor
13447 .buffer
13448 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13449 }
13450 })
13451 .unwrap();
13452
13453 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13454 cx.run_until_parked();
13455
13456 cx.assert_editor_state(
13457 &"
13458 ˇaaa
13459 ccc
13460 ddd
13461
13462 ggg
13463 hhh
13464
13465
13466 lll
13467 mmm
13468 NNN
13469
13470 qqq
13471 rrr
13472
13473 uuu
13474 111
13475 222
13476 333
13477
13478 666
13479 777
13480
13481 000
13482 !!!"
13483 .unindent(),
13484 );
13485
13486 cx.update_editor(|editor, window, cx| {
13487 editor.select_all(&SelectAll, window, cx);
13488 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13489 });
13490 cx.executor().run_until_parked();
13491
13492 cx.assert_state_with_diff(
13493 "
13494 «aaa
13495 - bbb
13496 ccc
13497 ddd
13498
13499 ggg
13500 hhh
13501
13502
13503 lll
13504 mmm
13505 - nnn
13506 + NNN
13507
13508 qqq
13509 rrr
13510
13511 uuu
13512 111
13513 222
13514 333
13515
13516 + 666
13517 777
13518
13519 000
13520 !!!ˇ»"
13521 .unindent(),
13522 );
13523}
13524
13525#[gpui::test]
13526async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13527 init_test(cx, |_| {});
13528
13529 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13530 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13531
13532 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13533 let multi_buffer = cx.new(|cx| {
13534 let mut multibuffer = MultiBuffer::new(ReadWrite);
13535 multibuffer.push_excerpts(
13536 buffer.clone(),
13537 [
13538 ExcerptRange {
13539 context: Point::new(0, 0)..Point::new(2, 0),
13540 primary: None,
13541 },
13542 ExcerptRange {
13543 context: Point::new(4, 0)..Point::new(7, 0),
13544 primary: None,
13545 },
13546 ExcerptRange {
13547 context: Point::new(9, 0)..Point::new(10, 0),
13548 primary: None,
13549 },
13550 ],
13551 cx,
13552 );
13553 multibuffer
13554 });
13555
13556 let editor = cx.add_window(|window, cx| {
13557 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13558 });
13559 editor
13560 .update(cx, |editor, _window, cx| {
13561 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13562 editor
13563 .buffer
13564 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13565 })
13566 .unwrap();
13567
13568 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13569 cx.run_until_parked();
13570
13571 cx.update_editor(|editor, window, cx| {
13572 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13573 });
13574 cx.executor().run_until_parked();
13575
13576 // When the start of a hunk coincides with the start of its excerpt,
13577 // the hunk is expanded. When the start of a a hunk is earlier than
13578 // the start of its excerpt, the hunk is not expanded.
13579 cx.assert_state_with_diff(
13580 "
13581 ˇaaa
13582 - bbb
13583 + BBB
13584
13585 - ddd
13586 - eee
13587 + DDD
13588 + EEE
13589 fff
13590
13591 iii
13592 "
13593 .unindent(),
13594 );
13595}
13596
13597#[gpui::test]
13598async fn test_edits_around_expanded_insertion_hunks(
13599 executor: BackgroundExecutor,
13600 cx: &mut TestAppContext,
13601) {
13602 init_test(cx, |_| {});
13603
13604 let mut cx = EditorTestContext::new(cx).await;
13605
13606 let diff_base = r#"
13607 use some::mod1;
13608 use some::mod2;
13609
13610 const A: u32 = 42;
13611
13612 fn main() {
13613 println!("hello");
13614
13615 println!("world");
13616 }
13617 "#
13618 .unindent();
13619 executor.run_until_parked();
13620 cx.set_state(
13621 &r#"
13622 use some::mod1;
13623 use some::mod2;
13624
13625 const A: u32 = 42;
13626 const B: u32 = 42;
13627 const C: u32 = 42;
13628 ˇ
13629
13630 fn main() {
13631 println!("hello");
13632
13633 println!("world");
13634 }
13635 "#
13636 .unindent(),
13637 );
13638
13639 cx.set_head_text(&diff_base);
13640 executor.run_until_parked();
13641
13642 cx.update_editor(|editor, window, cx| {
13643 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13644 });
13645 executor.run_until_parked();
13646
13647 cx.assert_state_with_diff(
13648 r#"
13649 use some::mod1;
13650 use some::mod2;
13651
13652 const A: u32 = 42;
13653 + const B: u32 = 42;
13654 + const C: u32 = 42;
13655 + ˇ
13656
13657 fn main() {
13658 println!("hello");
13659
13660 println!("world");
13661 }
13662 "#
13663 .unindent(),
13664 );
13665
13666 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13667 executor.run_until_parked();
13668
13669 cx.assert_state_with_diff(
13670 r#"
13671 use some::mod1;
13672 use some::mod2;
13673
13674 const A: u32 = 42;
13675 + const B: u32 = 42;
13676 + const C: u32 = 42;
13677 + const D: u32 = 42;
13678 + ˇ
13679
13680 fn main() {
13681 println!("hello");
13682
13683 println!("world");
13684 }
13685 "#
13686 .unindent(),
13687 );
13688
13689 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13690 executor.run_until_parked();
13691
13692 cx.assert_state_with_diff(
13693 r#"
13694 use some::mod1;
13695 use some::mod2;
13696
13697 const A: u32 = 42;
13698 + const B: u32 = 42;
13699 + const C: u32 = 42;
13700 + const D: u32 = 42;
13701 + const E: u32 = 42;
13702 + ˇ
13703
13704 fn main() {
13705 println!("hello");
13706
13707 println!("world");
13708 }
13709 "#
13710 .unindent(),
13711 );
13712
13713 cx.update_editor(|editor, window, cx| {
13714 editor.delete_line(&DeleteLine, window, cx);
13715 });
13716 executor.run_until_parked();
13717
13718 cx.assert_state_with_diff(
13719 r#"
13720 use some::mod1;
13721 use some::mod2;
13722
13723 const A: u32 = 42;
13724 + const B: u32 = 42;
13725 + const C: u32 = 42;
13726 + const D: u32 = 42;
13727 + const E: u32 = 42;
13728 ˇ
13729 fn main() {
13730 println!("hello");
13731
13732 println!("world");
13733 }
13734 "#
13735 .unindent(),
13736 );
13737
13738 cx.update_editor(|editor, window, cx| {
13739 editor.move_up(&MoveUp, window, cx);
13740 editor.delete_line(&DeleteLine, window, cx);
13741 editor.move_up(&MoveUp, window, cx);
13742 editor.delete_line(&DeleteLine, window, cx);
13743 editor.move_up(&MoveUp, window, cx);
13744 editor.delete_line(&DeleteLine, window, cx);
13745 });
13746 executor.run_until_parked();
13747 cx.assert_state_with_diff(
13748 r#"
13749 use some::mod1;
13750 use some::mod2;
13751
13752 const A: u32 = 42;
13753 + const B: u32 = 42;
13754 ˇ
13755 fn main() {
13756 println!("hello");
13757
13758 println!("world");
13759 }
13760 "#
13761 .unindent(),
13762 );
13763
13764 cx.update_editor(|editor, window, cx| {
13765 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13766 editor.delete_line(&DeleteLine, window, cx);
13767 });
13768 executor.run_until_parked();
13769 cx.assert_state_with_diff(
13770 r#"
13771 ˇ
13772 fn main() {
13773 println!("hello");
13774
13775 println!("world");
13776 }
13777 "#
13778 .unindent(),
13779 );
13780}
13781
13782#[gpui::test]
13783async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13784 init_test(cx, |_| {});
13785
13786 let mut cx = EditorTestContext::new(cx).await;
13787 cx.set_head_text(indoc! { "
13788 one
13789 two
13790 three
13791 four
13792 five
13793 "
13794 });
13795 cx.set_state(indoc! { "
13796 one
13797 ˇthree
13798 five
13799 "});
13800 cx.run_until_parked();
13801 cx.update_editor(|editor, window, cx| {
13802 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13803 });
13804 cx.assert_state_with_diff(
13805 indoc! { "
13806 one
13807 - two
13808 ˇthree
13809 - four
13810 five
13811 "}
13812 .to_string(),
13813 );
13814 cx.update_editor(|editor, window, cx| {
13815 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13816 });
13817
13818 cx.assert_state_with_diff(
13819 indoc! { "
13820 one
13821 ˇthree
13822 five
13823 "}
13824 .to_string(),
13825 );
13826
13827 cx.set_state(indoc! { "
13828 one
13829 ˇTWO
13830 three
13831 four
13832 five
13833 "});
13834 cx.run_until_parked();
13835 cx.update_editor(|editor, window, cx| {
13836 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13837 });
13838
13839 cx.assert_state_with_diff(
13840 indoc! { "
13841 one
13842 - two
13843 + ˇTWO
13844 three
13845 four
13846 five
13847 "}
13848 .to_string(),
13849 );
13850 cx.update_editor(|editor, window, cx| {
13851 editor.move_up(&Default::default(), window, cx);
13852 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13853 });
13854 cx.assert_state_with_diff(
13855 indoc! { "
13856 one
13857 ˇTWO
13858 three
13859 four
13860 five
13861 "}
13862 .to_string(),
13863 );
13864}
13865
13866#[gpui::test]
13867async fn test_edits_around_expanded_deletion_hunks(
13868 executor: BackgroundExecutor,
13869 cx: &mut TestAppContext,
13870) {
13871 init_test(cx, |_| {});
13872
13873 let mut cx = EditorTestContext::new(cx).await;
13874
13875 let diff_base = r#"
13876 use some::mod1;
13877 use some::mod2;
13878
13879 const A: u32 = 42;
13880 const B: u32 = 42;
13881 const C: u32 = 42;
13882
13883
13884 fn main() {
13885 println!("hello");
13886
13887 println!("world");
13888 }
13889 "#
13890 .unindent();
13891 executor.run_until_parked();
13892 cx.set_state(
13893 &r#"
13894 use some::mod1;
13895 use some::mod2;
13896
13897 ˇconst B: u32 = 42;
13898 const C: u32 = 42;
13899
13900
13901 fn main() {
13902 println!("hello");
13903
13904 println!("world");
13905 }
13906 "#
13907 .unindent(),
13908 );
13909
13910 cx.set_head_text(&diff_base);
13911 executor.run_until_parked();
13912
13913 cx.update_editor(|editor, window, cx| {
13914 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13915 });
13916 executor.run_until_parked();
13917
13918 cx.assert_state_with_diff(
13919 r#"
13920 use some::mod1;
13921 use some::mod2;
13922
13923 - const A: u32 = 42;
13924 ˇconst B: u32 = 42;
13925 const C: u32 = 42;
13926
13927
13928 fn main() {
13929 println!("hello");
13930
13931 println!("world");
13932 }
13933 "#
13934 .unindent(),
13935 );
13936
13937 cx.update_editor(|editor, window, cx| {
13938 editor.delete_line(&DeleteLine, window, cx);
13939 });
13940 executor.run_until_parked();
13941 cx.assert_state_with_diff(
13942 r#"
13943 use some::mod1;
13944 use some::mod2;
13945
13946 - const A: u32 = 42;
13947 - const B: u32 = 42;
13948 ˇconst C: u32 = 42;
13949
13950
13951 fn main() {
13952 println!("hello");
13953
13954 println!("world");
13955 }
13956 "#
13957 .unindent(),
13958 );
13959
13960 cx.update_editor(|editor, window, cx| {
13961 editor.delete_line(&DeleteLine, window, cx);
13962 });
13963 executor.run_until_parked();
13964 cx.assert_state_with_diff(
13965 r#"
13966 use some::mod1;
13967 use some::mod2;
13968
13969 - const A: u32 = 42;
13970 - const B: u32 = 42;
13971 - const C: u32 = 42;
13972 ˇ
13973
13974 fn main() {
13975 println!("hello");
13976
13977 println!("world");
13978 }
13979 "#
13980 .unindent(),
13981 );
13982
13983 cx.update_editor(|editor, window, cx| {
13984 editor.handle_input("replacement", window, cx);
13985 });
13986 executor.run_until_parked();
13987 cx.assert_state_with_diff(
13988 r#"
13989 use some::mod1;
13990 use some::mod2;
13991
13992 - const A: u32 = 42;
13993 - const B: u32 = 42;
13994 - const C: u32 = 42;
13995 -
13996 + replacementˇ
13997
13998 fn main() {
13999 println!("hello");
14000
14001 println!("world");
14002 }
14003 "#
14004 .unindent(),
14005 );
14006}
14007
14008#[gpui::test]
14009async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14010 init_test(cx, |_| {});
14011
14012 let mut cx = EditorTestContext::new(cx).await;
14013
14014 let base_text = r#"
14015 one
14016 two
14017 three
14018 four
14019 five
14020 "#
14021 .unindent();
14022 executor.run_until_parked();
14023 cx.set_state(
14024 &r#"
14025 one
14026 two
14027 fˇour
14028 five
14029 "#
14030 .unindent(),
14031 );
14032
14033 cx.set_head_text(&base_text);
14034 executor.run_until_parked();
14035
14036 cx.update_editor(|editor, window, cx| {
14037 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14038 });
14039 executor.run_until_parked();
14040
14041 cx.assert_state_with_diff(
14042 r#"
14043 one
14044 two
14045 - three
14046 fˇour
14047 five
14048 "#
14049 .unindent(),
14050 );
14051
14052 cx.update_editor(|editor, window, cx| {
14053 editor.backspace(&Backspace, window, cx);
14054 editor.backspace(&Backspace, window, cx);
14055 });
14056 executor.run_until_parked();
14057 cx.assert_state_with_diff(
14058 r#"
14059 one
14060 two
14061 - threeˇ
14062 - four
14063 + our
14064 five
14065 "#
14066 .unindent(),
14067 );
14068}
14069
14070#[gpui::test]
14071async fn test_edit_after_expanded_modification_hunk(
14072 executor: BackgroundExecutor,
14073 cx: &mut TestAppContext,
14074) {
14075 init_test(cx, |_| {});
14076
14077 let mut cx = EditorTestContext::new(cx).await;
14078
14079 let diff_base = r#"
14080 use some::mod1;
14081 use some::mod2;
14082
14083 const A: u32 = 42;
14084 const B: u32 = 42;
14085 const C: u32 = 42;
14086 const D: u32 = 42;
14087
14088
14089 fn main() {
14090 println!("hello");
14091
14092 println!("world");
14093 }"#
14094 .unindent();
14095
14096 cx.set_state(
14097 &r#"
14098 use some::mod1;
14099 use some::mod2;
14100
14101 const A: u32 = 42;
14102 const B: u32 = 42;
14103 const C: u32 = 43ˇ
14104 const D: u32 = 42;
14105
14106
14107 fn main() {
14108 println!("hello");
14109
14110 println!("world");
14111 }"#
14112 .unindent(),
14113 );
14114
14115 cx.set_head_text(&diff_base);
14116 executor.run_until_parked();
14117 cx.update_editor(|editor, window, cx| {
14118 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14119 });
14120 executor.run_until_parked();
14121
14122 cx.assert_state_with_diff(
14123 r#"
14124 use some::mod1;
14125 use some::mod2;
14126
14127 const A: u32 = 42;
14128 const B: u32 = 42;
14129 - const C: u32 = 42;
14130 + const C: u32 = 43ˇ
14131 const D: u32 = 42;
14132
14133
14134 fn main() {
14135 println!("hello");
14136
14137 println!("world");
14138 }"#
14139 .unindent(),
14140 );
14141
14142 cx.update_editor(|editor, window, cx| {
14143 editor.handle_input("\nnew_line\n", window, cx);
14144 });
14145 executor.run_until_parked();
14146
14147 cx.assert_state_with_diff(
14148 r#"
14149 use some::mod1;
14150 use some::mod2;
14151
14152 const A: u32 = 42;
14153 const B: u32 = 42;
14154 - const C: u32 = 42;
14155 + const C: u32 = 43
14156 + new_line
14157 + ˇ
14158 const D: u32 = 42;
14159
14160
14161 fn main() {
14162 println!("hello");
14163
14164 println!("world");
14165 }"#
14166 .unindent(),
14167 );
14168}
14169
14170#[gpui::test]
14171async fn test_stage_and_unstage_added_file_hunk(
14172 executor: BackgroundExecutor,
14173 cx: &mut TestAppContext,
14174) {
14175 init_test(cx, |_| {});
14176
14177 let mut cx = EditorTestContext::new(cx).await;
14178 cx.update_editor(|editor, _, cx| {
14179 editor.set_expand_all_diff_hunks(cx);
14180 });
14181
14182 let working_copy = r#"
14183 ˇfn main() {
14184 println!("hello, world!");
14185 }
14186 "#
14187 .unindent();
14188
14189 cx.set_state(&working_copy);
14190 executor.run_until_parked();
14191
14192 cx.assert_state_with_diff(
14193 r#"
14194 + ˇfn main() {
14195 + println!("hello, world!");
14196 + }
14197 "#
14198 .unindent(),
14199 );
14200 cx.assert_index_text(None);
14201
14202 cx.update_editor(|editor, window, cx| {
14203 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14204 });
14205 executor.run_until_parked();
14206 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14207 cx.assert_state_with_diff(
14208 r#"
14209 + ˇfn main() {
14210 + println!("hello, world!");
14211 + }
14212 "#
14213 .unindent(),
14214 );
14215
14216 cx.update_editor(|editor, window, cx| {
14217 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14218 });
14219 executor.run_until_parked();
14220 cx.assert_index_text(None);
14221}
14222
14223async fn setup_indent_guides_editor(
14224 text: &str,
14225 cx: &mut TestAppContext,
14226) -> (BufferId, EditorTestContext) {
14227 init_test(cx, |_| {});
14228
14229 let mut cx = EditorTestContext::new(cx).await;
14230
14231 let buffer_id = cx.update_editor(|editor, window, cx| {
14232 editor.set_text(text, window, cx);
14233 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14234
14235 buffer_ids[0]
14236 });
14237
14238 (buffer_id, cx)
14239}
14240
14241fn assert_indent_guides(
14242 range: Range<u32>,
14243 expected: Vec<IndentGuide>,
14244 active_indices: Option<Vec<usize>>,
14245 cx: &mut EditorTestContext,
14246) {
14247 let indent_guides = cx.update_editor(|editor, window, cx| {
14248 let snapshot = editor.snapshot(window, cx).display_snapshot;
14249 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14250 editor,
14251 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14252 true,
14253 &snapshot,
14254 cx,
14255 );
14256
14257 indent_guides.sort_by(|a, b| {
14258 a.depth.cmp(&b.depth).then(
14259 a.start_row
14260 .cmp(&b.start_row)
14261 .then(a.end_row.cmp(&b.end_row)),
14262 )
14263 });
14264 indent_guides
14265 });
14266
14267 if let Some(expected) = active_indices {
14268 let active_indices = cx.update_editor(|editor, window, cx| {
14269 let snapshot = editor.snapshot(window, cx).display_snapshot;
14270 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14271 });
14272
14273 assert_eq!(
14274 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14275 expected,
14276 "Active indent guide indices do not match"
14277 );
14278 }
14279
14280 assert_eq!(indent_guides, expected, "Indent guides do not match");
14281}
14282
14283fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14284 IndentGuide {
14285 buffer_id,
14286 start_row: MultiBufferRow(start_row),
14287 end_row: MultiBufferRow(end_row),
14288 depth,
14289 tab_size: 4,
14290 settings: IndentGuideSettings {
14291 enabled: true,
14292 line_width: 1,
14293 active_line_width: 1,
14294 ..Default::default()
14295 },
14296 }
14297}
14298
14299#[gpui::test]
14300async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14301 let (buffer_id, mut cx) = setup_indent_guides_editor(
14302 &"
14303 fn main() {
14304 let a = 1;
14305 }"
14306 .unindent(),
14307 cx,
14308 )
14309 .await;
14310
14311 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14312}
14313
14314#[gpui::test]
14315async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14316 let (buffer_id, mut cx) = setup_indent_guides_editor(
14317 &"
14318 fn main() {
14319 let a = 1;
14320 let b = 2;
14321 }"
14322 .unindent(),
14323 cx,
14324 )
14325 .await;
14326
14327 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14328}
14329
14330#[gpui::test]
14331async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14332 let (buffer_id, mut cx) = setup_indent_guides_editor(
14333 &"
14334 fn main() {
14335 let a = 1;
14336 if a == 3 {
14337 let b = 2;
14338 } else {
14339 let c = 3;
14340 }
14341 }"
14342 .unindent(),
14343 cx,
14344 )
14345 .await;
14346
14347 assert_indent_guides(
14348 0..8,
14349 vec![
14350 indent_guide(buffer_id, 1, 6, 0),
14351 indent_guide(buffer_id, 3, 3, 1),
14352 indent_guide(buffer_id, 5, 5, 1),
14353 ],
14354 None,
14355 &mut cx,
14356 );
14357}
14358
14359#[gpui::test]
14360async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14361 let (buffer_id, mut cx) = setup_indent_guides_editor(
14362 &"
14363 fn main() {
14364 let a = 1;
14365 let b = 2;
14366 let c = 3;
14367 }"
14368 .unindent(),
14369 cx,
14370 )
14371 .await;
14372
14373 assert_indent_guides(
14374 0..5,
14375 vec![
14376 indent_guide(buffer_id, 1, 3, 0),
14377 indent_guide(buffer_id, 2, 2, 1),
14378 ],
14379 None,
14380 &mut cx,
14381 );
14382}
14383
14384#[gpui::test]
14385async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14386 let (buffer_id, mut cx) = setup_indent_guides_editor(
14387 &"
14388 fn main() {
14389 let a = 1;
14390
14391 let c = 3;
14392 }"
14393 .unindent(),
14394 cx,
14395 )
14396 .await;
14397
14398 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14399}
14400
14401#[gpui::test]
14402async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14403 let (buffer_id, mut cx) = setup_indent_guides_editor(
14404 &"
14405 fn main() {
14406 let a = 1;
14407
14408 let c = 3;
14409
14410 if a == 3 {
14411 let b = 2;
14412 } else {
14413 let c = 3;
14414 }
14415 }"
14416 .unindent(),
14417 cx,
14418 )
14419 .await;
14420
14421 assert_indent_guides(
14422 0..11,
14423 vec![
14424 indent_guide(buffer_id, 1, 9, 0),
14425 indent_guide(buffer_id, 6, 6, 1),
14426 indent_guide(buffer_id, 8, 8, 1),
14427 ],
14428 None,
14429 &mut cx,
14430 );
14431}
14432
14433#[gpui::test]
14434async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14435 let (buffer_id, mut cx) = setup_indent_guides_editor(
14436 &"
14437 fn main() {
14438 let a = 1;
14439
14440 let c = 3;
14441
14442 if a == 3 {
14443 let b = 2;
14444 } else {
14445 let c = 3;
14446 }
14447 }"
14448 .unindent(),
14449 cx,
14450 )
14451 .await;
14452
14453 assert_indent_guides(
14454 1..11,
14455 vec![
14456 indent_guide(buffer_id, 1, 9, 0),
14457 indent_guide(buffer_id, 6, 6, 1),
14458 indent_guide(buffer_id, 8, 8, 1),
14459 ],
14460 None,
14461 &mut cx,
14462 );
14463}
14464
14465#[gpui::test]
14466async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14467 let (buffer_id, mut cx) = setup_indent_guides_editor(
14468 &"
14469 fn main() {
14470 let a = 1;
14471
14472 let c = 3;
14473
14474 if a == 3 {
14475 let b = 2;
14476 } else {
14477 let c = 3;
14478 }
14479 }"
14480 .unindent(),
14481 cx,
14482 )
14483 .await;
14484
14485 assert_indent_guides(
14486 1..10,
14487 vec![
14488 indent_guide(buffer_id, 1, 9, 0),
14489 indent_guide(buffer_id, 6, 6, 1),
14490 indent_guide(buffer_id, 8, 8, 1),
14491 ],
14492 None,
14493 &mut cx,
14494 );
14495}
14496
14497#[gpui::test]
14498async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14499 let (buffer_id, mut cx) = setup_indent_guides_editor(
14500 &"
14501 block1
14502 block2
14503 block3
14504 block4
14505 block2
14506 block1
14507 block1"
14508 .unindent(),
14509 cx,
14510 )
14511 .await;
14512
14513 assert_indent_guides(
14514 1..10,
14515 vec![
14516 indent_guide(buffer_id, 1, 4, 0),
14517 indent_guide(buffer_id, 2, 3, 1),
14518 indent_guide(buffer_id, 3, 3, 2),
14519 ],
14520 None,
14521 &mut cx,
14522 );
14523}
14524
14525#[gpui::test]
14526async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14527 let (buffer_id, mut cx) = setup_indent_guides_editor(
14528 &"
14529 block1
14530 block2
14531 block3
14532
14533 block1
14534 block1"
14535 .unindent(),
14536 cx,
14537 )
14538 .await;
14539
14540 assert_indent_guides(
14541 0..6,
14542 vec![
14543 indent_guide(buffer_id, 1, 2, 0),
14544 indent_guide(buffer_id, 2, 2, 1),
14545 ],
14546 None,
14547 &mut cx,
14548 );
14549}
14550
14551#[gpui::test]
14552async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14553 let (buffer_id, mut cx) = setup_indent_guides_editor(
14554 &"
14555 block1
14556
14557
14558
14559 block2
14560 "
14561 .unindent(),
14562 cx,
14563 )
14564 .await;
14565
14566 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14567}
14568
14569#[gpui::test]
14570async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14571 let (buffer_id, mut cx) = setup_indent_guides_editor(
14572 &"
14573 def a:
14574 \tb = 3
14575 \tif True:
14576 \t\tc = 4
14577 \t\td = 5
14578 \tprint(b)
14579 "
14580 .unindent(),
14581 cx,
14582 )
14583 .await;
14584
14585 assert_indent_guides(
14586 0..6,
14587 vec![
14588 indent_guide(buffer_id, 1, 6, 0),
14589 indent_guide(buffer_id, 3, 4, 1),
14590 ],
14591 None,
14592 &mut cx,
14593 );
14594}
14595
14596#[gpui::test]
14597async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14598 let (buffer_id, mut cx) = setup_indent_guides_editor(
14599 &"
14600 fn main() {
14601 let a = 1;
14602 }"
14603 .unindent(),
14604 cx,
14605 )
14606 .await;
14607
14608 cx.update_editor(|editor, window, cx| {
14609 editor.change_selections(None, window, cx, |s| {
14610 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14611 });
14612 });
14613
14614 assert_indent_guides(
14615 0..3,
14616 vec![indent_guide(buffer_id, 1, 1, 0)],
14617 Some(vec![0]),
14618 &mut cx,
14619 );
14620}
14621
14622#[gpui::test]
14623async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14624 let (buffer_id, mut cx) = setup_indent_guides_editor(
14625 &"
14626 fn main() {
14627 if 1 == 2 {
14628 let a = 1;
14629 }
14630 }"
14631 .unindent(),
14632 cx,
14633 )
14634 .await;
14635
14636 cx.update_editor(|editor, window, cx| {
14637 editor.change_selections(None, window, cx, |s| {
14638 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14639 });
14640 });
14641
14642 assert_indent_guides(
14643 0..4,
14644 vec![
14645 indent_guide(buffer_id, 1, 3, 0),
14646 indent_guide(buffer_id, 2, 2, 1),
14647 ],
14648 Some(vec![1]),
14649 &mut cx,
14650 );
14651
14652 cx.update_editor(|editor, window, cx| {
14653 editor.change_selections(None, window, cx, |s| {
14654 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14655 });
14656 });
14657
14658 assert_indent_guides(
14659 0..4,
14660 vec![
14661 indent_guide(buffer_id, 1, 3, 0),
14662 indent_guide(buffer_id, 2, 2, 1),
14663 ],
14664 Some(vec![1]),
14665 &mut cx,
14666 );
14667
14668 cx.update_editor(|editor, window, cx| {
14669 editor.change_selections(None, window, cx, |s| {
14670 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14671 });
14672 });
14673
14674 assert_indent_guides(
14675 0..4,
14676 vec![
14677 indent_guide(buffer_id, 1, 3, 0),
14678 indent_guide(buffer_id, 2, 2, 1),
14679 ],
14680 Some(vec![0]),
14681 &mut cx,
14682 );
14683}
14684
14685#[gpui::test]
14686async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
14687 let (buffer_id, mut cx) = setup_indent_guides_editor(
14688 &"
14689 fn main() {
14690 let a = 1;
14691
14692 let b = 2;
14693 }"
14694 .unindent(),
14695 cx,
14696 )
14697 .await;
14698
14699 cx.update_editor(|editor, window, cx| {
14700 editor.change_selections(None, window, cx, |s| {
14701 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14702 });
14703 });
14704
14705 assert_indent_guides(
14706 0..5,
14707 vec![indent_guide(buffer_id, 1, 3, 0)],
14708 Some(vec![0]),
14709 &mut cx,
14710 );
14711}
14712
14713#[gpui::test]
14714async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
14715 let (buffer_id, mut cx) = setup_indent_guides_editor(
14716 &"
14717 def m:
14718 a = 1
14719 pass"
14720 .unindent(),
14721 cx,
14722 )
14723 .await;
14724
14725 cx.update_editor(|editor, window, cx| {
14726 editor.change_selections(None, window, cx, |s| {
14727 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14728 });
14729 });
14730
14731 assert_indent_guides(
14732 0..3,
14733 vec![indent_guide(buffer_id, 1, 2, 0)],
14734 Some(vec![0]),
14735 &mut cx,
14736 );
14737}
14738
14739#[gpui::test]
14740async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
14741 init_test(cx, |_| {});
14742 let mut cx = EditorTestContext::new(cx).await;
14743 let text = indoc! {
14744 "
14745 impl A {
14746 fn b() {
14747 0;
14748 3;
14749 5;
14750 6;
14751 7;
14752 }
14753 }
14754 "
14755 };
14756 let base_text = indoc! {
14757 "
14758 impl A {
14759 fn b() {
14760 0;
14761 1;
14762 2;
14763 3;
14764 4;
14765 }
14766 fn c() {
14767 5;
14768 6;
14769 7;
14770 }
14771 }
14772 "
14773 };
14774
14775 cx.update_editor(|editor, window, cx| {
14776 editor.set_text(text, window, cx);
14777
14778 editor.buffer().update(cx, |multibuffer, cx| {
14779 let buffer = multibuffer.as_singleton().unwrap();
14780 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
14781
14782 multibuffer.set_all_diff_hunks_expanded(cx);
14783 multibuffer.add_diff(diff, cx);
14784
14785 buffer.read(cx).remote_id()
14786 })
14787 });
14788 cx.run_until_parked();
14789
14790 cx.assert_state_with_diff(
14791 indoc! { "
14792 impl A {
14793 fn b() {
14794 0;
14795 - 1;
14796 - 2;
14797 3;
14798 - 4;
14799 - }
14800 - fn c() {
14801 5;
14802 6;
14803 7;
14804 }
14805 }
14806 ˇ"
14807 }
14808 .to_string(),
14809 );
14810
14811 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14812 editor
14813 .snapshot(window, cx)
14814 .buffer_snapshot
14815 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14816 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14817 .collect::<Vec<_>>()
14818 });
14819 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14820 assert_eq!(
14821 actual_guides,
14822 vec![
14823 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14824 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14825 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14826 ]
14827 );
14828}
14829
14830#[gpui::test]
14831async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14832 init_test(cx, |_| {});
14833 let mut cx = EditorTestContext::new(cx).await;
14834
14835 let diff_base = r#"
14836 a
14837 b
14838 c
14839 "#
14840 .unindent();
14841
14842 cx.set_state(
14843 &r#"
14844 ˇA
14845 b
14846 C
14847 "#
14848 .unindent(),
14849 );
14850 cx.set_head_text(&diff_base);
14851 cx.update_editor(|editor, window, cx| {
14852 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14853 });
14854 executor.run_until_parked();
14855
14856 let both_hunks_expanded = r#"
14857 - a
14858 + ˇA
14859 b
14860 - c
14861 + C
14862 "#
14863 .unindent();
14864
14865 cx.assert_state_with_diff(both_hunks_expanded.clone());
14866
14867 let hunk_ranges = cx.update_editor(|editor, window, cx| {
14868 let snapshot = editor.snapshot(window, cx);
14869 let hunks = editor
14870 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
14871 .collect::<Vec<_>>();
14872 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
14873 let buffer_id = hunks[0].buffer_id;
14874 hunks
14875 .into_iter()
14876 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
14877 .collect::<Vec<_>>()
14878 });
14879 assert_eq!(hunk_ranges.len(), 2);
14880
14881 cx.update_editor(|editor, _, cx| {
14882 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
14883 });
14884 executor.run_until_parked();
14885
14886 let second_hunk_expanded = r#"
14887 ˇA
14888 b
14889 - c
14890 + C
14891 "#
14892 .unindent();
14893
14894 cx.assert_state_with_diff(second_hunk_expanded);
14895
14896 cx.update_editor(|editor, _, cx| {
14897 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
14898 });
14899 executor.run_until_parked();
14900
14901 cx.assert_state_with_diff(both_hunks_expanded.clone());
14902
14903 cx.update_editor(|editor, _, cx| {
14904 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
14905 });
14906 executor.run_until_parked();
14907
14908 let first_hunk_expanded = r#"
14909 - a
14910 + ˇA
14911 b
14912 C
14913 "#
14914 .unindent();
14915
14916 cx.assert_state_with_diff(first_hunk_expanded);
14917
14918 cx.update_editor(|editor, _, cx| {
14919 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
14920 });
14921 executor.run_until_parked();
14922
14923 cx.assert_state_with_diff(both_hunks_expanded);
14924
14925 cx.set_state(
14926 &r#"
14927 ˇA
14928 b
14929 "#
14930 .unindent(),
14931 );
14932 cx.run_until_parked();
14933
14934 // TODO this cursor position seems bad
14935 cx.assert_state_with_diff(
14936 r#"
14937 - ˇa
14938 + A
14939 b
14940 "#
14941 .unindent(),
14942 );
14943
14944 cx.update_editor(|editor, window, cx| {
14945 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14946 });
14947
14948 cx.assert_state_with_diff(
14949 r#"
14950 - ˇa
14951 + A
14952 b
14953 - c
14954 "#
14955 .unindent(),
14956 );
14957
14958 let hunk_ranges = cx.update_editor(|editor, window, cx| {
14959 let snapshot = editor.snapshot(window, cx);
14960 let hunks = editor
14961 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
14962 .collect::<Vec<_>>();
14963 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
14964 let buffer_id = hunks[0].buffer_id;
14965 hunks
14966 .into_iter()
14967 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
14968 .collect::<Vec<_>>()
14969 });
14970 assert_eq!(hunk_ranges.len(), 2);
14971
14972 cx.update_editor(|editor, _, cx| {
14973 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
14974 });
14975 executor.run_until_parked();
14976
14977 cx.assert_state_with_diff(
14978 r#"
14979 - ˇa
14980 + A
14981 b
14982 "#
14983 .unindent(),
14984 );
14985}
14986
14987#[gpui::test]
14988async fn test_display_diff_hunks(cx: &mut TestAppContext) {
14989 init_test(cx, |_| {});
14990
14991 let fs = FakeFs::new(cx.executor());
14992 fs.insert_tree(
14993 path!("/test"),
14994 json!({
14995 ".git": {},
14996 "file-1": "ONE\n",
14997 "file-2": "TWO\n",
14998 "file-3": "THREE\n",
14999 }),
15000 )
15001 .await;
15002
15003 fs.set_head_for_repo(
15004 path!("/test/.git").as_ref(),
15005 &[
15006 ("file-1".into(), "one\n".into()),
15007 ("file-2".into(), "two\n".into()),
15008 ("file-3".into(), "three\n".into()),
15009 ],
15010 );
15011
15012 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15013 let mut buffers = vec![];
15014 for i in 1..=3 {
15015 let buffer = project
15016 .update(cx, |project, cx| {
15017 let path = format!(path!("/test/file-{}"), i);
15018 project.open_local_buffer(path, cx)
15019 })
15020 .await
15021 .unwrap();
15022 buffers.push(buffer);
15023 }
15024
15025 let multibuffer = cx.new(|cx| {
15026 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15027 multibuffer.set_all_diff_hunks_expanded(cx);
15028 for buffer in &buffers {
15029 let snapshot = buffer.read(cx).snapshot();
15030 multibuffer.set_excerpts_for_path(
15031 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15032 buffer.clone(),
15033 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15034 DEFAULT_MULTIBUFFER_CONTEXT,
15035 cx,
15036 );
15037 }
15038 multibuffer
15039 });
15040
15041 let editor = cx.add_window(|window, cx| {
15042 Editor::new(
15043 EditorMode::Full,
15044 multibuffer,
15045 Some(project),
15046 true,
15047 window,
15048 cx,
15049 )
15050 });
15051 cx.run_until_parked();
15052
15053 let snapshot = editor
15054 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15055 .unwrap();
15056 let hunks = snapshot
15057 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15058 .map(|hunk| match hunk {
15059 DisplayDiffHunk::Unfolded {
15060 display_row_range, ..
15061 } => display_row_range,
15062 DisplayDiffHunk::Folded { .. } => unreachable!(),
15063 })
15064 .collect::<Vec<_>>();
15065 assert_eq!(
15066 hunks,
15067 [
15068 DisplayRow(3)..DisplayRow(5),
15069 DisplayRow(10)..DisplayRow(12),
15070 DisplayRow(17)..DisplayRow(19),
15071 ]
15072 );
15073}
15074
15075#[gpui::test]
15076async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15077 init_test(cx, |_| {});
15078
15079 let mut cx = EditorTestContext::new(cx).await;
15080 cx.set_head_text(indoc! { "
15081 one
15082 two
15083 three
15084 four
15085 five
15086 "
15087 });
15088 cx.set_index_text(indoc! { "
15089 one
15090 two
15091 three
15092 four
15093 five
15094 "
15095 });
15096 cx.set_state(indoc! {"
15097 one
15098 TWO
15099 ˇTHREE
15100 FOUR
15101 five
15102 "});
15103 cx.run_until_parked();
15104 cx.update_editor(|editor, window, cx| {
15105 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15106 });
15107 cx.run_until_parked();
15108 cx.assert_index_text(Some(indoc! {"
15109 one
15110 TWO
15111 THREE
15112 FOUR
15113 five
15114 "}));
15115 cx.set_state(indoc! { "
15116 one
15117 TWO
15118 ˇTHREE-HUNDRED
15119 FOUR
15120 five
15121 "});
15122 cx.run_until_parked();
15123 cx.update_editor(|editor, window, cx| {
15124 let snapshot = editor.snapshot(window, cx);
15125 let hunks = editor
15126 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15127 .collect::<Vec<_>>();
15128 assert_eq!(hunks.len(), 1);
15129 assert_eq!(
15130 hunks[0].status(),
15131 DiffHunkStatus {
15132 kind: DiffHunkStatusKind::Modified,
15133 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15134 }
15135 );
15136
15137 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15138 });
15139 cx.run_until_parked();
15140 cx.assert_index_text(Some(indoc! {"
15141 one
15142 TWO
15143 THREE-HUNDRED
15144 FOUR
15145 five
15146 "}));
15147}
15148
15149#[gpui::test]
15150fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15151 init_test(cx, |_| {});
15152
15153 let editor = cx.add_window(|window, cx| {
15154 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15155 build_editor(buffer, window, cx)
15156 });
15157
15158 let render_args = Arc::new(Mutex::new(None));
15159 let snapshot = editor
15160 .update(cx, |editor, window, cx| {
15161 let snapshot = editor.buffer().read(cx).snapshot(cx);
15162 let range =
15163 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15164
15165 struct RenderArgs {
15166 row: MultiBufferRow,
15167 folded: bool,
15168 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15169 }
15170
15171 let crease = Crease::inline(
15172 range,
15173 FoldPlaceholder::test(),
15174 {
15175 let toggle_callback = render_args.clone();
15176 move |row, folded, callback, _window, _cx| {
15177 *toggle_callback.lock() = Some(RenderArgs {
15178 row,
15179 folded,
15180 callback,
15181 });
15182 div()
15183 }
15184 },
15185 |_row, _folded, _window, _cx| div(),
15186 );
15187
15188 editor.insert_creases(Some(crease), cx);
15189 let snapshot = editor.snapshot(window, cx);
15190 let _div = snapshot.render_crease_toggle(
15191 MultiBufferRow(1),
15192 false,
15193 cx.entity().clone(),
15194 window,
15195 cx,
15196 );
15197 snapshot
15198 })
15199 .unwrap();
15200
15201 let render_args = render_args.lock().take().unwrap();
15202 assert_eq!(render_args.row, MultiBufferRow(1));
15203 assert!(!render_args.folded);
15204 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15205
15206 cx.update_window(*editor, |_, window, cx| {
15207 (render_args.callback)(true, window, cx)
15208 })
15209 .unwrap();
15210 let snapshot = editor
15211 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15212 .unwrap();
15213 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15214
15215 cx.update_window(*editor, |_, window, cx| {
15216 (render_args.callback)(false, window, cx)
15217 })
15218 .unwrap();
15219 let snapshot = editor
15220 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15221 .unwrap();
15222 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15223}
15224
15225#[gpui::test]
15226async fn test_input_text(cx: &mut TestAppContext) {
15227 init_test(cx, |_| {});
15228 let mut cx = EditorTestContext::new(cx).await;
15229
15230 cx.set_state(
15231 &r#"ˇone
15232 two
15233
15234 three
15235 fourˇ
15236 five
15237
15238 siˇx"#
15239 .unindent(),
15240 );
15241
15242 cx.dispatch_action(HandleInput(String::new()));
15243 cx.assert_editor_state(
15244 &r#"ˇone
15245 two
15246
15247 three
15248 fourˇ
15249 five
15250
15251 siˇx"#
15252 .unindent(),
15253 );
15254
15255 cx.dispatch_action(HandleInput("AAAA".to_string()));
15256 cx.assert_editor_state(
15257 &r#"AAAAˇone
15258 two
15259
15260 three
15261 fourAAAAˇ
15262 five
15263
15264 siAAAAˇx"#
15265 .unindent(),
15266 );
15267}
15268
15269#[gpui::test]
15270async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15271 init_test(cx, |_| {});
15272
15273 let mut cx = EditorTestContext::new(cx).await;
15274 cx.set_state(
15275 r#"let foo = 1;
15276let foo = 2;
15277let foo = 3;
15278let fooˇ = 4;
15279let foo = 5;
15280let foo = 6;
15281let foo = 7;
15282let foo = 8;
15283let foo = 9;
15284let foo = 10;
15285let foo = 11;
15286let foo = 12;
15287let foo = 13;
15288let foo = 14;
15289let foo = 15;"#,
15290 );
15291
15292 cx.update_editor(|e, window, cx| {
15293 assert_eq!(
15294 e.next_scroll_position,
15295 NextScrollCursorCenterTopBottom::Center,
15296 "Default next scroll direction is center",
15297 );
15298
15299 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15300 assert_eq!(
15301 e.next_scroll_position,
15302 NextScrollCursorCenterTopBottom::Top,
15303 "After center, next scroll direction should be top",
15304 );
15305
15306 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15307 assert_eq!(
15308 e.next_scroll_position,
15309 NextScrollCursorCenterTopBottom::Bottom,
15310 "After top, next scroll direction should be bottom",
15311 );
15312
15313 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15314 assert_eq!(
15315 e.next_scroll_position,
15316 NextScrollCursorCenterTopBottom::Center,
15317 "After bottom, scrolling should start over",
15318 );
15319
15320 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15321 assert_eq!(
15322 e.next_scroll_position,
15323 NextScrollCursorCenterTopBottom::Top,
15324 "Scrolling continues if retriggered fast enough"
15325 );
15326 });
15327
15328 cx.executor()
15329 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15330 cx.executor().run_until_parked();
15331 cx.update_editor(|e, _, _| {
15332 assert_eq!(
15333 e.next_scroll_position,
15334 NextScrollCursorCenterTopBottom::Center,
15335 "If scrolling is not triggered fast enough, it should reset"
15336 );
15337 });
15338}
15339
15340#[gpui::test]
15341async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15342 init_test(cx, |_| {});
15343 let mut cx = EditorLspTestContext::new_rust(
15344 lsp::ServerCapabilities {
15345 definition_provider: Some(lsp::OneOf::Left(true)),
15346 references_provider: Some(lsp::OneOf::Left(true)),
15347 ..lsp::ServerCapabilities::default()
15348 },
15349 cx,
15350 )
15351 .await;
15352
15353 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15354 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15355 move |params, _| async move {
15356 if empty_go_to_definition {
15357 Ok(None)
15358 } else {
15359 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15360 uri: params.text_document_position_params.text_document.uri,
15361 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15362 })))
15363 }
15364 },
15365 );
15366 let references =
15367 cx.lsp
15368 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15369 Ok(Some(vec![lsp::Location {
15370 uri: params.text_document_position.text_document.uri,
15371 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15372 }]))
15373 });
15374 (go_to_definition, references)
15375 };
15376
15377 cx.set_state(
15378 &r#"fn one() {
15379 let mut a = ˇtwo();
15380 }
15381
15382 fn two() {}"#
15383 .unindent(),
15384 );
15385 set_up_lsp_handlers(false, &mut cx);
15386 let navigated = cx
15387 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15388 .await
15389 .expect("Failed to navigate to definition");
15390 assert_eq!(
15391 navigated,
15392 Navigated::Yes,
15393 "Should have navigated to definition from the GetDefinition response"
15394 );
15395 cx.assert_editor_state(
15396 &r#"fn one() {
15397 let mut a = two();
15398 }
15399
15400 fn «twoˇ»() {}"#
15401 .unindent(),
15402 );
15403
15404 let editors = cx.update_workspace(|workspace, _, cx| {
15405 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15406 });
15407 cx.update_editor(|_, _, test_editor_cx| {
15408 assert_eq!(
15409 editors.len(),
15410 1,
15411 "Initially, only one, test, editor should be open in the workspace"
15412 );
15413 assert_eq!(
15414 test_editor_cx.entity(),
15415 editors.last().expect("Asserted len is 1").clone()
15416 );
15417 });
15418
15419 set_up_lsp_handlers(true, &mut cx);
15420 let navigated = cx
15421 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15422 .await
15423 .expect("Failed to navigate to lookup references");
15424 assert_eq!(
15425 navigated,
15426 Navigated::Yes,
15427 "Should have navigated to references as a fallback after empty GoToDefinition response"
15428 );
15429 // We should not change the selections in the existing file,
15430 // if opening another milti buffer with the references
15431 cx.assert_editor_state(
15432 &r#"fn one() {
15433 let mut a = two();
15434 }
15435
15436 fn «twoˇ»() {}"#
15437 .unindent(),
15438 );
15439 let editors = cx.update_workspace(|workspace, _, cx| {
15440 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15441 });
15442 cx.update_editor(|_, _, test_editor_cx| {
15443 assert_eq!(
15444 editors.len(),
15445 2,
15446 "After falling back to references search, we open a new editor with the results"
15447 );
15448 let references_fallback_text = editors
15449 .into_iter()
15450 .find(|new_editor| *new_editor != test_editor_cx.entity())
15451 .expect("Should have one non-test editor now")
15452 .read(test_editor_cx)
15453 .text(test_editor_cx);
15454 assert_eq!(
15455 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15456 "Should use the range from the references response and not the GoToDefinition one"
15457 );
15458 });
15459}
15460
15461#[gpui::test]
15462async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15463 init_test(cx, |_| {});
15464
15465 let language = Arc::new(Language::new(
15466 LanguageConfig::default(),
15467 Some(tree_sitter_rust::LANGUAGE.into()),
15468 ));
15469
15470 let text = r#"
15471 #[cfg(test)]
15472 mod tests() {
15473 #[test]
15474 fn runnable_1() {
15475 let a = 1;
15476 }
15477
15478 #[test]
15479 fn runnable_2() {
15480 let a = 1;
15481 let b = 2;
15482 }
15483 }
15484 "#
15485 .unindent();
15486
15487 let fs = FakeFs::new(cx.executor());
15488 fs.insert_file("/file.rs", Default::default()).await;
15489
15490 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15491 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15492 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15493 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15494 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15495
15496 let editor = cx.new_window_entity(|window, cx| {
15497 Editor::new(
15498 EditorMode::Full,
15499 multi_buffer,
15500 Some(project.clone()),
15501 true,
15502 window,
15503 cx,
15504 )
15505 });
15506
15507 editor.update_in(cx, |editor, window, cx| {
15508 let snapshot = editor.buffer().read(cx).snapshot(cx);
15509 editor.tasks.insert(
15510 (buffer.read(cx).remote_id(), 3),
15511 RunnableTasks {
15512 templates: vec![],
15513 offset: snapshot.anchor_before(43),
15514 column: 0,
15515 extra_variables: HashMap::default(),
15516 context_range: BufferOffset(43)..BufferOffset(85),
15517 },
15518 );
15519 editor.tasks.insert(
15520 (buffer.read(cx).remote_id(), 8),
15521 RunnableTasks {
15522 templates: vec![],
15523 offset: snapshot.anchor_before(86),
15524 column: 0,
15525 extra_variables: HashMap::default(),
15526 context_range: BufferOffset(86)..BufferOffset(191),
15527 },
15528 );
15529
15530 // Test finding task when cursor is inside function body
15531 editor.change_selections(None, window, cx, |s| {
15532 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15533 });
15534 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15535 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15536
15537 // Test finding task when cursor is on function name
15538 editor.change_selections(None, window, cx, |s| {
15539 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15540 });
15541 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15542 assert_eq!(row, 8, "Should find task when cursor is on function name");
15543 });
15544}
15545
15546#[gpui::test]
15547async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
15548 init_test(cx, |_| {});
15549
15550 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15551 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15552 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15553
15554 let fs = FakeFs::new(cx.executor());
15555 fs.insert_tree(
15556 path!("/a"),
15557 json!({
15558 "first.rs": sample_text_1,
15559 "second.rs": sample_text_2,
15560 "third.rs": sample_text_3,
15561 }),
15562 )
15563 .await;
15564 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15565 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15566 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15567 let worktree = project.update(cx, |project, cx| {
15568 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15569 assert_eq!(worktrees.len(), 1);
15570 worktrees.pop().unwrap()
15571 });
15572 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15573
15574 let buffer_1 = project
15575 .update(cx, |project, cx| {
15576 project.open_buffer((worktree_id, "first.rs"), cx)
15577 })
15578 .await
15579 .unwrap();
15580 let buffer_2 = project
15581 .update(cx, |project, cx| {
15582 project.open_buffer((worktree_id, "second.rs"), cx)
15583 })
15584 .await
15585 .unwrap();
15586 let buffer_3 = project
15587 .update(cx, |project, cx| {
15588 project.open_buffer((worktree_id, "third.rs"), cx)
15589 })
15590 .await
15591 .unwrap();
15592
15593 let multi_buffer = cx.new(|cx| {
15594 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15595 multi_buffer.push_excerpts(
15596 buffer_1.clone(),
15597 [
15598 ExcerptRange {
15599 context: Point::new(0, 0)..Point::new(3, 0),
15600 primary: None,
15601 },
15602 ExcerptRange {
15603 context: Point::new(5, 0)..Point::new(7, 0),
15604 primary: None,
15605 },
15606 ExcerptRange {
15607 context: Point::new(9, 0)..Point::new(10, 4),
15608 primary: None,
15609 },
15610 ],
15611 cx,
15612 );
15613 multi_buffer.push_excerpts(
15614 buffer_2.clone(),
15615 [
15616 ExcerptRange {
15617 context: Point::new(0, 0)..Point::new(3, 0),
15618 primary: None,
15619 },
15620 ExcerptRange {
15621 context: Point::new(5, 0)..Point::new(7, 0),
15622 primary: None,
15623 },
15624 ExcerptRange {
15625 context: Point::new(9, 0)..Point::new(10, 4),
15626 primary: None,
15627 },
15628 ],
15629 cx,
15630 );
15631 multi_buffer.push_excerpts(
15632 buffer_3.clone(),
15633 [
15634 ExcerptRange {
15635 context: Point::new(0, 0)..Point::new(3, 0),
15636 primary: None,
15637 },
15638 ExcerptRange {
15639 context: Point::new(5, 0)..Point::new(7, 0),
15640 primary: None,
15641 },
15642 ExcerptRange {
15643 context: Point::new(9, 0)..Point::new(10, 4),
15644 primary: None,
15645 },
15646 ],
15647 cx,
15648 );
15649 multi_buffer
15650 });
15651 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15652 Editor::new(
15653 EditorMode::Full,
15654 multi_buffer,
15655 Some(project.clone()),
15656 true,
15657 window,
15658 cx,
15659 )
15660 });
15661
15662 let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
15663 assert_eq!(
15664 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15665 full_text,
15666 );
15667
15668 multi_buffer_editor.update(cx, |editor, cx| {
15669 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15670 });
15671 assert_eq!(
15672 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15673 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15674 "After folding the first buffer, its text should not be displayed"
15675 );
15676
15677 multi_buffer_editor.update(cx, |editor, cx| {
15678 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15679 });
15680 assert_eq!(
15681 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15682 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15683 "After folding the second buffer, its text should not be displayed"
15684 );
15685
15686 multi_buffer_editor.update(cx, |editor, cx| {
15687 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15688 });
15689 assert_eq!(
15690 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15691 "\n\n\n\n\n",
15692 "After folding the third buffer, its text should not be displayed"
15693 );
15694
15695 // Emulate selection inside the fold logic, that should work
15696 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15697 editor
15698 .snapshot(window, cx)
15699 .next_line_boundary(Point::new(0, 4));
15700 });
15701
15702 multi_buffer_editor.update(cx, |editor, cx| {
15703 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15704 });
15705 assert_eq!(
15706 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15707 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15708 "After unfolding the second buffer, its text should be displayed"
15709 );
15710
15711 multi_buffer_editor.update(cx, |editor, cx| {
15712 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15713 });
15714 assert_eq!(
15715 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15716 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15717 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15718 );
15719
15720 multi_buffer_editor.update(cx, |editor, cx| {
15721 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15722 });
15723 assert_eq!(
15724 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15725 full_text,
15726 "After unfolding the all buffers, all original text should be displayed"
15727 );
15728}
15729
15730#[gpui::test]
15731async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
15732 init_test(cx, |_| {});
15733
15734 let sample_text_1 = "1111\n2222\n3333".to_string();
15735 let sample_text_2 = "4444\n5555\n6666".to_string();
15736 let sample_text_3 = "7777\n8888\n9999".to_string();
15737
15738 let fs = FakeFs::new(cx.executor());
15739 fs.insert_tree(
15740 path!("/a"),
15741 json!({
15742 "first.rs": sample_text_1,
15743 "second.rs": sample_text_2,
15744 "third.rs": sample_text_3,
15745 }),
15746 )
15747 .await;
15748 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15749 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15750 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15751 let worktree = project.update(cx, |project, cx| {
15752 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15753 assert_eq!(worktrees.len(), 1);
15754 worktrees.pop().unwrap()
15755 });
15756 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15757
15758 let buffer_1 = project
15759 .update(cx, |project, cx| {
15760 project.open_buffer((worktree_id, "first.rs"), cx)
15761 })
15762 .await
15763 .unwrap();
15764 let buffer_2 = project
15765 .update(cx, |project, cx| {
15766 project.open_buffer((worktree_id, "second.rs"), cx)
15767 })
15768 .await
15769 .unwrap();
15770 let buffer_3 = project
15771 .update(cx, |project, cx| {
15772 project.open_buffer((worktree_id, "third.rs"), cx)
15773 })
15774 .await
15775 .unwrap();
15776
15777 let multi_buffer = cx.new(|cx| {
15778 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15779 multi_buffer.push_excerpts(
15780 buffer_1.clone(),
15781 [ExcerptRange {
15782 context: Point::new(0, 0)..Point::new(3, 0),
15783 primary: None,
15784 }],
15785 cx,
15786 );
15787 multi_buffer.push_excerpts(
15788 buffer_2.clone(),
15789 [ExcerptRange {
15790 context: Point::new(0, 0)..Point::new(3, 0),
15791 primary: None,
15792 }],
15793 cx,
15794 );
15795 multi_buffer.push_excerpts(
15796 buffer_3.clone(),
15797 [ExcerptRange {
15798 context: Point::new(0, 0)..Point::new(3, 0),
15799 primary: None,
15800 }],
15801 cx,
15802 );
15803 multi_buffer
15804 });
15805
15806 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15807 Editor::new(
15808 EditorMode::Full,
15809 multi_buffer,
15810 Some(project.clone()),
15811 true,
15812 window,
15813 cx,
15814 )
15815 });
15816
15817 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
15818 assert_eq!(
15819 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15820 full_text,
15821 );
15822
15823 multi_buffer_editor.update(cx, |editor, cx| {
15824 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15825 });
15826 assert_eq!(
15827 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15828 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
15829 "After folding the first buffer, its text should not be displayed"
15830 );
15831
15832 multi_buffer_editor.update(cx, |editor, cx| {
15833 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15834 });
15835
15836 assert_eq!(
15837 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15838 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
15839 "After folding the second buffer, its text should not be displayed"
15840 );
15841
15842 multi_buffer_editor.update(cx, |editor, cx| {
15843 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15844 });
15845 assert_eq!(
15846 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15847 "\n\n\n\n\n",
15848 "After folding the third buffer, its text should not be displayed"
15849 );
15850
15851 multi_buffer_editor.update(cx, |editor, cx| {
15852 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15853 });
15854 assert_eq!(
15855 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15856 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
15857 "After unfolding the second buffer, its text should be displayed"
15858 );
15859
15860 multi_buffer_editor.update(cx, |editor, cx| {
15861 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15862 });
15863 assert_eq!(
15864 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15865 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
15866 "After unfolding the first buffer, its text should be displayed"
15867 );
15868
15869 multi_buffer_editor.update(cx, |editor, cx| {
15870 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15871 });
15872 assert_eq!(
15873 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15874 full_text,
15875 "After unfolding all buffers, all original text should be displayed"
15876 );
15877}
15878
15879#[gpui::test]
15880async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) {
15881 init_test(cx, |_| {});
15882
15883 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15884
15885 let fs = FakeFs::new(cx.executor());
15886 fs.insert_tree(
15887 path!("/a"),
15888 json!({
15889 "main.rs": sample_text,
15890 }),
15891 )
15892 .await;
15893 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15894 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15895 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15896 let worktree = project.update(cx, |project, cx| {
15897 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15898 assert_eq!(worktrees.len(), 1);
15899 worktrees.pop().unwrap()
15900 });
15901 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15902
15903 let buffer_1 = project
15904 .update(cx, |project, cx| {
15905 project.open_buffer((worktree_id, "main.rs"), cx)
15906 })
15907 .await
15908 .unwrap();
15909
15910 let multi_buffer = cx.new(|cx| {
15911 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15912 multi_buffer.push_excerpts(
15913 buffer_1.clone(),
15914 [ExcerptRange {
15915 context: Point::new(0, 0)
15916 ..Point::new(
15917 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
15918 0,
15919 ),
15920 primary: None,
15921 }],
15922 cx,
15923 );
15924 multi_buffer
15925 });
15926 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15927 Editor::new(
15928 EditorMode::Full,
15929 multi_buffer,
15930 Some(project.clone()),
15931 true,
15932 window,
15933 cx,
15934 )
15935 });
15936
15937 let selection_range = Point::new(1, 0)..Point::new(2, 0);
15938 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15939 enum TestHighlight {}
15940 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
15941 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
15942 editor.highlight_text::<TestHighlight>(
15943 vec![highlight_range.clone()],
15944 HighlightStyle::color(Hsla::green()),
15945 cx,
15946 );
15947 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
15948 });
15949
15950 let full_text = format!("\n\n\n{sample_text}\n");
15951 assert_eq!(
15952 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15953 full_text,
15954 );
15955}
15956
15957#[gpui::test]
15958async fn test_inline_completion_text(cx: &mut TestAppContext) {
15959 init_test(cx, |_| {});
15960
15961 // Simple insertion
15962 assert_highlighted_edits(
15963 "Hello, world!",
15964 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
15965 true,
15966 cx,
15967 |highlighted_edits, cx| {
15968 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
15969 assert_eq!(highlighted_edits.highlights.len(), 1);
15970 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
15971 assert_eq!(
15972 highlighted_edits.highlights[0].1.background_color,
15973 Some(cx.theme().status().created_background)
15974 );
15975 },
15976 )
15977 .await;
15978
15979 // Replacement
15980 assert_highlighted_edits(
15981 "This is a test.",
15982 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
15983 false,
15984 cx,
15985 |highlighted_edits, cx| {
15986 assert_eq!(highlighted_edits.text, "That is a test.");
15987 assert_eq!(highlighted_edits.highlights.len(), 1);
15988 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
15989 assert_eq!(
15990 highlighted_edits.highlights[0].1.background_color,
15991 Some(cx.theme().status().created_background)
15992 );
15993 },
15994 )
15995 .await;
15996
15997 // Multiple edits
15998 assert_highlighted_edits(
15999 "Hello, world!",
16000 vec![
16001 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16002 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16003 ],
16004 false,
16005 cx,
16006 |highlighted_edits, cx| {
16007 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16008 assert_eq!(highlighted_edits.highlights.len(), 2);
16009 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16010 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16011 assert_eq!(
16012 highlighted_edits.highlights[0].1.background_color,
16013 Some(cx.theme().status().created_background)
16014 );
16015 assert_eq!(
16016 highlighted_edits.highlights[1].1.background_color,
16017 Some(cx.theme().status().created_background)
16018 );
16019 },
16020 )
16021 .await;
16022
16023 // Multiple lines with edits
16024 assert_highlighted_edits(
16025 "First line\nSecond line\nThird line\nFourth line",
16026 vec![
16027 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16028 (
16029 Point::new(2, 0)..Point::new(2, 10),
16030 "New third line".to_string(),
16031 ),
16032 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16033 ],
16034 false,
16035 cx,
16036 |highlighted_edits, cx| {
16037 assert_eq!(
16038 highlighted_edits.text,
16039 "Second modified\nNew third line\nFourth updated line"
16040 );
16041 assert_eq!(highlighted_edits.highlights.len(), 3);
16042 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16043 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16044 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16045 for highlight in &highlighted_edits.highlights {
16046 assert_eq!(
16047 highlight.1.background_color,
16048 Some(cx.theme().status().created_background)
16049 );
16050 }
16051 },
16052 )
16053 .await;
16054}
16055
16056#[gpui::test]
16057async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16058 init_test(cx, |_| {});
16059
16060 // Deletion
16061 assert_highlighted_edits(
16062 "Hello, world!",
16063 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16064 true,
16065 cx,
16066 |highlighted_edits, cx| {
16067 assert_eq!(highlighted_edits.text, "Hello, world!");
16068 assert_eq!(highlighted_edits.highlights.len(), 1);
16069 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16070 assert_eq!(
16071 highlighted_edits.highlights[0].1.background_color,
16072 Some(cx.theme().status().deleted_background)
16073 );
16074 },
16075 )
16076 .await;
16077
16078 // Insertion
16079 assert_highlighted_edits(
16080 "Hello, world!",
16081 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16082 true,
16083 cx,
16084 |highlighted_edits, cx| {
16085 assert_eq!(highlighted_edits.highlights.len(), 1);
16086 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16087 assert_eq!(
16088 highlighted_edits.highlights[0].1.background_color,
16089 Some(cx.theme().status().created_background)
16090 );
16091 },
16092 )
16093 .await;
16094}
16095
16096async fn assert_highlighted_edits(
16097 text: &str,
16098 edits: Vec<(Range<Point>, String)>,
16099 include_deletions: bool,
16100 cx: &mut TestAppContext,
16101 assertion_fn: impl Fn(HighlightedText, &App),
16102) {
16103 let window = cx.add_window(|window, cx| {
16104 let buffer = MultiBuffer::build_simple(text, cx);
16105 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16106 });
16107 let cx = &mut VisualTestContext::from_window(*window, cx);
16108
16109 let (buffer, snapshot) = window
16110 .update(cx, |editor, _window, cx| {
16111 (
16112 editor.buffer().clone(),
16113 editor.buffer().read(cx).snapshot(cx),
16114 )
16115 })
16116 .unwrap();
16117
16118 let edits = edits
16119 .into_iter()
16120 .map(|(range, edit)| {
16121 (
16122 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16123 edit,
16124 )
16125 })
16126 .collect::<Vec<_>>();
16127
16128 let text_anchor_edits = edits
16129 .clone()
16130 .into_iter()
16131 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16132 .collect::<Vec<_>>();
16133
16134 let edit_preview = window
16135 .update(cx, |_, _window, cx| {
16136 buffer
16137 .read(cx)
16138 .as_singleton()
16139 .unwrap()
16140 .read(cx)
16141 .preview_edits(text_anchor_edits.into(), cx)
16142 })
16143 .unwrap()
16144 .await;
16145
16146 cx.update(|_window, cx| {
16147 let highlighted_edits = inline_completion_edit_text(
16148 &snapshot.as_singleton().unwrap().2,
16149 &edits,
16150 &edit_preview,
16151 include_deletions,
16152 cx,
16153 );
16154 assertion_fn(highlighted_edits, cx)
16155 });
16156}
16157
16158#[gpui::test]
16159async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16160 init_test(cx, |_| {});
16161 let capabilities = lsp::ServerCapabilities {
16162 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16163 prepare_provider: Some(true),
16164 work_done_progress_options: Default::default(),
16165 })),
16166 ..Default::default()
16167 };
16168 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16169
16170 cx.set_state(indoc! {"
16171 struct Fˇoo {}
16172 "});
16173
16174 cx.update_editor(|editor, _, cx| {
16175 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16176 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16177 editor.highlight_background::<DocumentHighlightRead>(
16178 &[highlight_range],
16179 |c| c.editor_document_highlight_read_background,
16180 cx,
16181 );
16182 });
16183
16184 let mut prepare_rename_handler =
16185 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16186 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16187 start: lsp::Position {
16188 line: 0,
16189 character: 7,
16190 },
16191 end: lsp::Position {
16192 line: 0,
16193 character: 10,
16194 },
16195 })))
16196 });
16197 let prepare_rename_task = cx
16198 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16199 .expect("Prepare rename was not started");
16200 prepare_rename_handler.next().await.unwrap();
16201 prepare_rename_task.await.expect("Prepare rename failed");
16202
16203 let mut rename_handler =
16204 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16205 let edit = lsp::TextEdit {
16206 range: lsp::Range {
16207 start: lsp::Position {
16208 line: 0,
16209 character: 7,
16210 },
16211 end: lsp::Position {
16212 line: 0,
16213 character: 10,
16214 },
16215 },
16216 new_text: "FooRenamed".to_string(),
16217 };
16218 Ok(Some(lsp::WorkspaceEdit::new(
16219 // Specify the same edit twice
16220 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16221 )))
16222 });
16223 let rename_task = cx
16224 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16225 .expect("Confirm rename was not started");
16226 rename_handler.next().await.unwrap();
16227 rename_task.await.expect("Confirm rename failed");
16228 cx.run_until_parked();
16229
16230 // Despite two edits, only one is actually applied as those are identical
16231 cx.assert_editor_state(indoc! {"
16232 struct FooRenamedˇ {}
16233 "});
16234}
16235
16236#[gpui::test]
16237async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16238 init_test(cx, |_| {});
16239 // These capabilities indicate that the server does not support prepare rename.
16240 let capabilities = lsp::ServerCapabilities {
16241 rename_provider: Some(lsp::OneOf::Left(true)),
16242 ..Default::default()
16243 };
16244 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16245
16246 cx.set_state(indoc! {"
16247 struct Fˇoo {}
16248 "});
16249
16250 cx.update_editor(|editor, _window, cx| {
16251 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16252 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16253 editor.highlight_background::<DocumentHighlightRead>(
16254 &[highlight_range],
16255 |c| c.editor_document_highlight_read_background,
16256 cx,
16257 );
16258 });
16259
16260 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16261 .expect("Prepare rename was not started")
16262 .await
16263 .expect("Prepare rename failed");
16264
16265 let mut rename_handler =
16266 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16267 let edit = lsp::TextEdit {
16268 range: lsp::Range {
16269 start: lsp::Position {
16270 line: 0,
16271 character: 7,
16272 },
16273 end: lsp::Position {
16274 line: 0,
16275 character: 10,
16276 },
16277 },
16278 new_text: "FooRenamed".to_string(),
16279 };
16280 Ok(Some(lsp::WorkspaceEdit::new(
16281 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16282 )))
16283 });
16284 let rename_task = cx
16285 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16286 .expect("Confirm rename was not started");
16287 rename_handler.next().await.unwrap();
16288 rename_task.await.expect("Confirm rename failed");
16289 cx.run_until_parked();
16290
16291 // Correct range is renamed, as `surrounding_word` is used to find it.
16292 cx.assert_editor_state(indoc! {"
16293 struct FooRenamedˇ {}
16294 "});
16295}
16296
16297#[gpui::test]
16298async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16299 init_test(cx, |_| {});
16300 let mut cx = EditorTestContext::new(cx).await;
16301
16302 let language = Arc::new(
16303 Language::new(
16304 LanguageConfig::default(),
16305 Some(tree_sitter_html::LANGUAGE.into()),
16306 )
16307 .with_brackets_query(
16308 r#"
16309 ("<" @open "/>" @close)
16310 ("</" @open ">" @close)
16311 ("<" @open ">" @close)
16312 ("\"" @open "\"" @close)
16313 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16314 "#,
16315 )
16316 .unwrap(),
16317 );
16318 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16319
16320 cx.set_state(indoc! {"
16321 <span>ˇ</span>
16322 "});
16323 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16324 cx.assert_editor_state(indoc! {"
16325 <span>
16326 ˇ
16327 </span>
16328 "});
16329
16330 cx.set_state(indoc! {"
16331 <span><span></span>ˇ</span>
16332 "});
16333 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16334 cx.assert_editor_state(indoc! {"
16335 <span><span></span>
16336 ˇ</span>
16337 "});
16338
16339 cx.set_state(indoc! {"
16340 <span>ˇ
16341 </span>
16342 "});
16343 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16344 cx.assert_editor_state(indoc! {"
16345 <span>
16346 ˇ
16347 </span>
16348 "});
16349}
16350
16351fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16352 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16353 point..point
16354}
16355
16356fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16357 let (text, ranges) = marked_text_ranges(marked_text, true);
16358 assert_eq!(editor.text(cx), text);
16359 assert_eq!(
16360 editor.selections.ranges(cx),
16361 ranges,
16362 "Assert selections are {}",
16363 marked_text
16364 );
16365}
16366
16367pub fn handle_signature_help_request(
16368 cx: &mut EditorLspTestContext,
16369 mocked_response: lsp::SignatureHelp,
16370) -> impl Future<Output = ()> {
16371 let mut request =
16372 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16373 let mocked_response = mocked_response.clone();
16374 async move { Ok(Some(mocked_response)) }
16375 });
16376
16377 async move {
16378 request.next().await;
16379 }
16380}
16381
16382/// Handle completion request passing a marked string specifying where the completion
16383/// should be triggered from using '|' character, what range should be replaced, and what completions
16384/// should be returned using '<' and '>' to delimit the range
16385pub fn handle_completion_request(
16386 cx: &mut EditorLspTestContext,
16387 marked_string: &str,
16388 completions: Vec<&'static str>,
16389 counter: Arc<AtomicUsize>,
16390) -> impl Future<Output = ()> {
16391 let complete_from_marker: TextRangeMarker = '|'.into();
16392 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16393 let (_, mut marked_ranges) = marked_text_ranges_by(
16394 marked_string,
16395 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16396 );
16397
16398 let complete_from_position =
16399 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16400 let replace_range =
16401 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16402
16403 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16404 let completions = completions.clone();
16405 counter.fetch_add(1, atomic::Ordering::Release);
16406 async move {
16407 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16408 assert_eq!(
16409 params.text_document_position.position,
16410 complete_from_position
16411 );
16412 Ok(Some(lsp::CompletionResponse::Array(
16413 completions
16414 .iter()
16415 .map(|completion_text| lsp::CompletionItem {
16416 label: completion_text.to_string(),
16417 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16418 range: replace_range,
16419 new_text: completion_text.to_string(),
16420 })),
16421 ..Default::default()
16422 })
16423 .collect(),
16424 )))
16425 }
16426 });
16427
16428 async move {
16429 request.next().await;
16430 }
16431}
16432
16433fn handle_resolve_completion_request(
16434 cx: &mut EditorLspTestContext,
16435 edits: Option<Vec<(&'static str, &'static str)>>,
16436) -> impl Future<Output = ()> {
16437 let edits = edits.map(|edits| {
16438 edits
16439 .iter()
16440 .map(|(marked_string, new_text)| {
16441 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16442 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16443 lsp::TextEdit::new(replace_range, new_text.to_string())
16444 })
16445 .collect::<Vec<_>>()
16446 });
16447
16448 let mut request =
16449 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16450 let edits = edits.clone();
16451 async move {
16452 Ok(lsp::CompletionItem {
16453 additional_text_edits: edits,
16454 ..Default::default()
16455 })
16456 }
16457 });
16458
16459 async move {
16460 request.next().await;
16461 }
16462}
16463
16464pub(crate) fn update_test_language_settings(
16465 cx: &mut TestAppContext,
16466 f: impl Fn(&mut AllLanguageSettingsContent),
16467) {
16468 cx.update(|cx| {
16469 SettingsStore::update_global(cx, |store, cx| {
16470 store.update_user_settings::<AllLanguageSettings>(cx, f);
16471 });
16472 });
16473}
16474
16475pub(crate) fn update_test_project_settings(
16476 cx: &mut TestAppContext,
16477 f: impl Fn(&mut ProjectSettings),
16478) {
16479 cx.update(|cx| {
16480 SettingsStore::update_global(cx, |store, cx| {
16481 store.update_user_settings::<ProjectSettings>(cx, f);
16482 });
16483 });
16484}
16485
16486pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16487 cx.update(|cx| {
16488 assets::Assets.load_test_fonts(cx);
16489 let store = SettingsStore::test(cx);
16490 cx.set_global(store);
16491 theme::init(theme::LoadThemes::JustBase, cx);
16492 release_channel::init(SemanticVersion::default(), cx);
16493 client::init_settings(cx);
16494 language::init(cx);
16495 Project::init_settings(cx);
16496 workspace::init_settings(cx);
16497 crate::init(cx);
16498 });
16499
16500 update_test_language_settings(cx, f);
16501}
16502
16503#[track_caller]
16504fn assert_hunk_revert(
16505 not_reverted_text_with_selections: &str,
16506 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
16507 expected_reverted_text_with_selections: &str,
16508 base_text: &str,
16509 cx: &mut EditorLspTestContext,
16510) {
16511 cx.set_state(not_reverted_text_with_selections);
16512 cx.set_head_text(base_text);
16513 cx.clear_index_text();
16514 cx.executor().run_until_parked();
16515
16516 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16517 let snapshot = editor.snapshot(window, cx);
16518 let reverted_hunk_statuses = snapshot
16519 .buffer_snapshot
16520 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16521 .map(|hunk| hunk.status())
16522 .collect::<Vec<_>>();
16523
16524 editor.git_restore(&Default::default(), window, cx);
16525 reverted_hunk_statuses
16526 });
16527 cx.executor().run_until_parked();
16528 cx.assert_editor_state(expected_reverted_text_with_selections);
16529 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16530}