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 futures::StreamExt;
11use gpui::{
12 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
13 WindowBounds, WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
23 Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::IndentGuide;
27use parking_lot::Mutex;
28use pretty_assertions::{assert_eq, assert_ne};
29use project::{buffer_store::BufferChangeSet, FakeFs};
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use std::{
37 iter,
38 sync::atomic::{self, AtomicUsize},
39};
40use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
41use unindent::Unindent;
42use util::{
43 assert_set_eq,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
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 model = cx.entity().clone();
66 cx.subscribe_in(
67 &model,
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 { level: 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 { level: 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 { level: 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 };
1515
1516 let move_to_end = MoveToEndOfLine {
1517 stop_at_soft_wraps: true,
1518 };
1519
1520 let editor = cx.add_window(|window, cx| {
1521 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1522 build_editor(buffer, window, cx)
1523 });
1524 _ = editor.update(cx, |editor, window, cx| {
1525 editor.change_selections(None, window, cx, |s| {
1526 s.select_display_ranges([
1527 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1528 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1529 ]);
1530 });
1531 });
1532
1533 _ = editor.update(cx, |editor, window, cx| {
1534 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1535 assert_eq!(
1536 editor.selections.display_ranges(cx),
1537 &[
1538 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1539 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1540 ]
1541 );
1542 });
1543
1544 _ = editor.update(cx, |editor, window, cx| {
1545 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1546 assert_eq!(
1547 editor.selections.display_ranges(cx),
1548 &[
1549 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1550 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1551 ]
1552 );
1553 });
1554
1555 _ = editor.update(cx, |editor, window, cx| {
1556 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1557 assert_eq!(
1558 editor.selections.display_ranges(cx),
1559 &[
1560 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1561 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1562 ]
1563 );
1564 });
1565
1566 _ = editor.update(cx, |editor, window, cx| {
1567 editor.move_to_end_of_line(&move_to_end, window, cx);
1568 assert_eq!(
1569 editor.selections.display_ranges(cx),
1570 &[
1571 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1572 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1573 ]
1574 );
1575 });
1576
1577 // Moving to the end of line again is a no-op.
1578 _ = editor.update(cx, |editor, window, cx| {
1579 editor.move_to_end_of_line(&move_to_end, window, cx);
1580 assert_eq!(
1581 editor.selections.display_ranges(cx),
1582 &[
1583 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1584 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1585 ]
1586 );
1587 });
1588
1589 _ = editor.update(cx, |editor, window, cx| {
1590 editor.move_left(&MoveLeft, window, cx);
1591 editor.select_to_beginning_of_line(
1592 &SelectToBeginningOfLine {
1593 stop_at_soft_wraps: true,
1594 },
1595 window,
1596 cx,
1597 );
1598 assert_eq!(
1599 editor.selections.display_ranges(cx),
1600 &[
1601 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1602 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1603 ]
1604 );
1605 });
1606
1607 _ = editor.update(cx, |editor, window, cx| {
1608 editor.select_to_beginning_of_line(
1609 &SelectToBeginningOfLine {
1610 stop_at_soft_wraps: true,
1611 },
1612 window,
1613 cx,
1614 );
1615 assert_eq!(
1616 editor.selections.display_ranges(cx),
1617 &[
1618 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1619 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1620 ]
1621 );
1622 });
1623
1624 _ = editor.update(cx, |editor, window, cx| {
1625 editor.select_to_beginning_of_line(
1626 &SelectToBeginningOfLine {
1627 stop_at_soft_wraps: true,
1628 },
1629 window,
1630 cx,
1631 );
1632 assert_eq!(
1633 editor.selections.display_ranges(cx),
1634 &[
1635 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1636 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1637 ]
1638 );
1639 });
1640
1641 _ = editor.update(cx, |editor, window, cx| {
1642 editor.select_to_end_of_line(
1643 &SelectToEndOfLine {
1644 stop_at_soft_wraps: true,
1645 },
1646 window,
1647 cx,
1648 );
1649 assert_eq!(
1650 editor.selections.display_ranges(cx),
1651 &[
1652 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1653 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1654 ]
1655 );
1656 });
1657
1658 _ = editor.update(cx, |editor, window, cx| {
1659 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1660 assert_eq!(editor.display_text(cx), "ab\n de");
1661 assert_eq!(
1662 editor.selections.display_ranges(cx),
1663 &[
1664 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1665 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1666 ]
1667 );
1668 });
1669
1670 _ = editor.update(cx, |editor, window, cx| {
1671 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
1672 assert_eq!(editor.display_text(cx), "\n");
1673 assert_eq!(
1674 editor.selections.display_ranges(cx),
1675 &[
1676 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1677 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1678 ]
1679 );
1680 });
1681}
1682
1683#[gpui::test]
1684fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1685 init_test(cx, |_| {});
1686 let move_to_beg = MoveToBeginningOfLine {
1687 stop_at_soft_wraps: false,
1688 };
1689
1690 let move_to_end = MoveToEndOfLine {
1691 stop_at_soft_wraps: false,
1692 };
1693
1694 let editor = cx.add_window(|window, cx| {
1695 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1696 build_editor(buffer, window, cx)
1697 });
1698
1699 _ = editor.update(cx, |editor, window, cx| {
1700 editor.set_wrap_width(Some(140.0.into()), cx);
1701
1702 // We expect the following lines after wrapping
1703 // ```
1704 // thequickbrownfox
1705 // jumpedoverthelazydo
1706 // gs
1707 // ```
1708 // The final `gs` was soft-wrapped onto a new line.
1709 assert_eq!(
1710 "thequickbrownfox\njumpedoverthelaz\nydogs",
1711 editor.display_text(cx),
1712 );
1713
1714 // First, let's assert behavior on the first line, that was not soft-wrapped.
1715 // Start the cursor at the `k` on the first line
1716 editor.change_selections(None, window, cx, |s| {
1717 s.select_display_ranges([
1718 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1719 ]);
1720 });
1721
1722 // Moving to the beginning of the line should put us at the beginning of the line.
1723 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1724 assert_eq!(
1725 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1726 editor.selections.display_ranges(cx)
1727 );
1728
1729 // Moving to the end of the line should put us at the end of the line.
1730 editor.move_to_end_of_line(&move_to_end, window, cx);
1731 assert_eq!(
1732 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1733 editor.selections.display_ranges(cx)
1734 );
1735
1736 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1737 // Start the cursor at the last line (`y` that was wrapped to a new line)
1738 editor.change_selections(None, window, cx, |s| {
1739 s.select_display_ranges([
1740 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1741 ]);
1742 });
1743
1744 // Moving to the beginning of the line should put us at the start of the second line of
1745 // display text, i.e., the `j`.
1746 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1747 assert_eq!(
1748 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1749 editor.selections.display_ranges(cx)
1750 );
1751
1752 // Moving to the beginning of the line again should be a no-op.
1753 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1754 assert_eq!(
1755 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1756 editor.selections.display_ranges(cx)
1757 );
1758
1759 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1760 // next display line.
1761 editor.move_to_end_of_line(&move_to_end, window, cx);
1762 assert_eq!(
1763 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1764 editor.selections.display_ranges(cx)
1765 );
1766
1767 // Moving to the end of the line again should be a no-op.
1768 editor.move_to_end_of_line(&move_to_end, window, cx);
1769 assert_eq!(
1770 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1771 editor.selections.display_ranges(cx)
1772 );
1773 });
1774}
1775
1776#[gpui::test]
1777fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1778 init_test(cx, |_| {});
1779
1780 let editor = cx.add_window(|window, cx| {
1781 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1782 build_editor(buffer, window, cx)
1783 });
1784 _ = editor.update(cx, |editor, window, cx| {
1785 editor.change_selections(None, window, cx, |s| {
1786 s.select_display_ranges([
1787 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1788 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1789 ])
1790 });
1791
1792 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1793 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1794
1795 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1796 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1797
1798 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1799 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1800
1801 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1802 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1803
1804 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1805 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1806
1807 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1808 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1809
1810 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1811 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1812
1813 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1814 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1815
1816 editor.move_right(&MoveRight, window, cx);
1817 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1818 assert_selection_ranges(
1819 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1820 editor,
1821 cx,
1822 );
1823
1824 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1825 assert_selection_ranges(
1826 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1827 editor,
1828 cx,
1829 );
1830
1831 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1832 assert_selection_ranges(
1833 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1834 editor,
1835 cx,
1836 );
1837 });
1838}
1839
1840#[gpui::test]
1841fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1842 init_test(cx, |_| {});
1843
1844 let editor = cx.add_window(|window, cx| {
1845 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1846 build_editor(buffer, window, cx)
1847 });
1848
1849 _ = editor.update(cx, |editor, window, cx| {
1850 editor.set_wrap_width(Some(140.0.into()), cx);
1851 assert_eq!(
1852 editor.display_text(cx),
1853 "use one::{\n two::three::\n four::five\n};"
1854 );
1855
1856 editor.change_selections(None, window, cx, |s| {
1857 s.select_display_ranges([
1858 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1859 ]);
1860 });
1861
1862 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1863 assert_eq!(
1864 editor.selections.display_ranges(cx),
1865 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1866 );
1867
1868 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1869 assert_eq!(
1870 editor.selections.display_ranges(cx),
1871 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1872 );
1873
1874 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1875 assert_eq!(
1876 editor.selections.display_ranges(cx),
1877 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1878 );
1879
1880 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1881 assert_eq!(
1882 editor.selections.display_ranges(cx),
1883 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1884 );
1885
1886 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1887 assert_eq!(
1888 editor.selections.display_ranges(cx),
1889 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1890 );
1891
1892 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1893 assert_eq!(
1894 editor.selections.display_ranges(cx),
1895 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1896 );
1897 });
1898}
1899
1900#[gpui::test]
1901async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1902 init_test(cx, |_| {});
1903 let mut cx = EditorTestContext::new(cx).await;
1904
1905 let line_height = cx.editor(|editor, window, _| {
1906 editor
1907 .style()
1908 .unwrap()
1909 .text
1910 .line_height_in_pixels(window.rem_size())
1911 });
1912 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1913
1914 cx.set_state(
1915 &r#"ˇone
1916 two
1917
1918 three
1919 fourˇ
1920 five
1921
1922 six"#
1923 .unindent(),
1924 );
1925
1926 cx.update_editor(|editor, window, cx| {
1927 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1928 });
1929 cx.assert_editor_state(
1930 &r#"one
1931 two
1932 ˇ
1933 three
1934 four
1935 five
1936 ˇ
1937 six"#
1938 .unindent(),
1939 );
1940
1941 cx.update_editor(|editor, window, cx| {
1942 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1943 });
1944 cx.assert_editor_state(
1945 &r#"one
1946 two
1947
1948 three
1949 four
1950 five
1951 ˇ
1952 sixˇ"#
1953 .unindent(),
1954 );
1955
1956 cx.update_editor(|editor, window, cx| {
1957 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
1958 });
1959 cx.assert_editor_state(
1960 &r#"one
1961 two
1962
1963 three
1964 four
1965 five
1966
1967 sixˇ"#
1968 .unindent(),
1969 );
1970
1971 cx.update_editor(|editor, window, cx| {
1972 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1973 });
1974 cx.assert_editor_state(
1975 &r#"one
1976 two
1977
1978 three
1979 four
1980 five
1981 ˇ
1982 six"#
1983 .unindent(),
1984 );
1985
1986 cx.update_editor(|editor, window, cx| {
1987 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
1988 });
1989 cx.assert_editor_state(
1990 &r#"one
1991 two
1992 ˇ
1993 three
1994 four
1995 five
1996
1997 six"#
1998 .unindent(),
1999 );
2000
2001 cx.update_editor(|editor, window, cx| {
2002 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2003 });
2004 cx.assert_editor_state(
2005 &r#"ˇone
2006 two
2007
2008 three
2009 four
2010 five
2011
2012 six"#
2013 .unindent(),
2014 );
2015}
2016
2017#[gpui::test]
2018async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
2019 init_test(cx, |_| {});
2020 let mut cx = EditorTestContext::new(cx).await;
2021 let line_height = cx.editor(|editor, window, _| {
2022 editor
2023 .style()
2024 .unwrap()
2025 .text
2026 .line_height_in_pixels(window.rem_size())
2027 });
2028 let window = cx.window;
2029 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2030
2031 cx.set_state(
2032 r#"ˇone
2033 two
2034 three
2035 four
2036 five
2037 six
2038 seven
2039 eight
2040 nine
2041 ten
2042 "#,
2043 );
2044
2045 cx.update_editor(|editor, window, cx| {
2046 assert_eq!(
2047 editor.snapshot(window, cx).scroll_position(),
2048 gpui::Point::new(0., 0.)
2049 );
2050 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2051 assert_eq!(
2052 editor.snapshot(window, cx).scroll_position(),
2053 gpui::Point::new(0., 3.)
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., 6.)
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., 3.)
2064 );
2065
2066 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2067 assert_eq!(
2068 editor.snapshot(window, cx).scroll_position(),
2069 gpui::Point::new(0., 1.)
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., 3.)
2075 );
2076 });
2077}
2078
2079#[gpui::test]
2080async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2081 init_test(cx, |_| {});
2082 let mut cx = EditorTestContext::new(cx).await;
2083
2084 let line_height = cx.update_editor(|editor, window, cx| {
2085 editor.set_vertical_scroll_margin(2, cx);
2086 editor
2087 .style()
2088 .unwrap()
2089 .text
2090 .line_height_in_pixels(window.rem_size())
2091 });
2092 let window = cx.window;
2093 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2094
2095 cx.set_state(
2096 r#"ˇone
2097 two
2098 three
2099 four
2100 five
2101 six
2102 seven
2103 eight
2104 nine
2105 ten
2106 "#,
2107 );
2108 cx.update_editor(|editor, window, cx| {
2109 assert_eq!(
2110 editor.snapshot(window, cx).scroll_position(),
2111 gpui::Point::new(0., 0.0)
2112 );
2113 });
2114
2115 // Add a cursor below the visible area. Since both cursors cannot fit
2116 // on screen, the editor autoscrolls to reveal the newest cursor, and
2117 // allows the vertical scroll margin below that cursor.
2118 cx.update_editor(|editor, window, cx| {
2119 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2120 selections.select_ranges([
2121 Point::new(0, 0)..Point::new(0, 0),
2122 Point::new(6, 0)..Point::new(6, 0),
2123 ]);
2124 })
2125 });
2126 cx.update_editor(|editor, window, cx| {
2127 assert_eq!(
2128 editor.snapshot(window, cx).scroll_position(),
2129 gpui::Point::new(0., 3.0)
2130 );
2131 });
2132
2133 // Move down. The editor cursor scrolls down to track the newest cursor.
2134 cx.update_editor(|editor, window, cx| {
2135 editor.move_down(&Default::default(), window, cx);
2136 });
2137 cx.update_editor(|editor, window, cx| {
2138 assert_eq!(
2139 editor.snapshot(window, cx).scroll_position(),
2140 gpui::Point::new(0., 4.0)
2141 );
2142 });
2143
2144 // Add a cursor above the visible area. Since both cursors fit on screen,
2145 // the editor scrolls to show both.
2146 cx.update_editor(|editor, window, cx| {
2147 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2148 selections.select_ranges([
2149 Point::new(1, 0)..Point::new(1, 0),
2150 Point::new(6, 0)..Point::new(6, 0),
2151 ]);
2152 })
2153 });
2154 cx.update_editor(|editor, window, cx| {
2155 assert_eq!(
2156 editor.snapshot(window, cx).scroll_position(),
2157 gpui::Point::new(0., 1.0)
2158 );
2159 });
2160}
2161
2162#[gpui::test]
2163async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2164 init_test(cx, |_| {});
2165 let mut cx = EditorTestContext::new(cx).await;
2166
2167 let line_height = cx.editor(|editor, window, _cx| {
2168 editor
2169 .style()
2170 .unwrap()
2171 .text
2172 .line_height_in_pixels(window.rem_size())
2173 });
2174 let window = cx.window;
2175 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2176 cx.set_state(
2177 &r#"
2178 ˇone
2179 two
2180 threeˇ
2181 four
2182 five
2183 six
2184 seven
2185 eight
2186 nine
2187 ten
2188 "#
2189 .unindent(),
2190 );
2191
2192 cx.update_editor(|editor, window, cx| {
2193 editor.move_page_down(&MovePageDown::default(), window, cx)
2194 });
2195 cx.assert_editor_state(
2196 &r#"
2197 one
2198 two
2199 three
2200 ˇfour
2201 five
2202 sixˇ
2203 seven
2204 eight
2205 nine
2206 ten
2207 "#
2208 .unindent(),
2209 );
2210
2211 cx.update_editor(|editor, window, cx| {
2212 editor.move_page_down(&MovePageDown::default(), window, cx)
2213 });
2214 cx.assert_editor_state(
2215 &r#"
2216 one
2217 two
2218 three
2219 four
2220 five
2221 six
2222 ˇseven
2223 eight
2224 nineˇ
2225 ten
2226 "#
2227 .unindent(),
2228 );
2229
2230 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2231 cx.assert_editor_state(
2232 &r#"
2233 one
2234 two
2235 three
2236 ˇfour
2237 five
2238 sixˇ
2239 seven
2240 eight
2241 nine
2242 ten
2243 "#
2244 .unindent(),
2245 );
2246
2247 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2248 cx.assert_editor_state(
2249 &r#"
2250 ˇone
2251 two
2252 threeˇ
2253 four
2254 five
2255 six
2256 seven
2257 eight
2258 nine
2259 ten
2260 "#
2261 .unindent(),
2262 );
2263
2264 // Test select collapsing
2265 cx.update_editor(|editor, window, cx| {
2266 editor.move_page_down(&MovePageDown::default(), window, cx);
2267 editor.move_page_down(&MovePageDown::default(), window, cx);
2268 editor.move_page_down(&MovePageDown::default(), window, cx);
2269 });
2270 cx.assert_editor_state(
2271 &r#"
2272 one
2273 two
2274 three
2275 four
2276 five
2277 six
2278 seven
2279 eight
2280 nine
2281 ˇten
2282 ˇ"#
2283 .unindent(),
2284 );
2285}
2286
2287#[gpui::test]
2288async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2289 init_test(cx, |_| {});
2290 let mut cx = EditorTestContext::new(cx).await;
2291 cx.set_state("one «two threeˇ» four");
2292 cx.update_editor(|editor, window, cx| {
2293 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, window, cx);
2294 assert_eq!(editor.text(cx), " four");
2295 });
2296}
2297
2298#[gpui::test]
2299fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2300 init_test(cx, |_| {});
2301
2302 let editor = cx.add_window(|window, cx| {
2303 let buffer = MultiBuffer::build_simple("one two three four", cx);
2304 build_editor(buffer.clone(), window, cx)
2305 });
2306
2307 _ = editor.update(cx, |editor, window, cx| {
2308 editor.change_selections(None, window, cx, |s| {
2309 s.select_display_ranges([
2310 // an empty selection - the preceding word fragment is deleted
2311 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2312 // characters selected - they are deleted
2313 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2314 ])
2315 });
2316 editor.delete_to_previous_word_start(
2317 &DeleteToPreviousWordStart {
2318 ignore_newlines: false,
2319 },
2320 window,
2321 cx,
2322 );
2323 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2324 });
2325
2326 _ = editor.update(cx, |editor, window, cx| {
2327 editor.change_selections(None, window, cx, |s| {
2328 s.select_display_ranges([
2329 // an empty selection - the following word fragment is deleted
2330 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2331 // characters selected - they are deleted
2332 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2333 ])
2334 });
2335 editor.delete_to_next_word_end(
2336 &DeleteToNextWordEnd {
2337 ignore_newlines: false,
2338 },
2339 window,
2340 cx,
2341 );
2342 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2343 });
2344}
2345
2346#[gpui::test]
2347fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2348 init_test(cx, |_| {});
2349
2350 let editor = cx.add_window(|window, cx| {
2351 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2352 build_editor(buffer.clone(), window, cx)
2353 });
2354 let del_to_prev_word_start = DeleteToPreviousWordStart {
2355 ignore_newlines: false,
2356 };
2357 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2358 ignore_newlines: true,
2359 };
2360
2361 _ = editor.update(cx, |editor, window, cx| {
2362 editor.change_selections(None, window, cx, |s| {
2363 s.select_display_ranges([
2364 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2365 ])
2366 });
2367 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2368 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2369 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2370 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2371 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2372 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2373 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2374 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2375 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2376 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2377 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2378 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2379 });
2380}
2381
2382#[gpui::test]
2383fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2384 init_test(cx, |_| {});
2385
2386 let editor = cx.add_window(|window, cx| {
2387 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2388 build_editor(buffer.clone(), window, cx)
2389 });
2390 let del_to_next_word_end = DeleteToNextWordEnd {
2391 ignore_newlines: false,
2392 };
2393 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2394 ignore_newlines: true,
2395 };
2396
2397 _ = editor.update(cx, |editor, window, cx| {
2398 editor.change_selections(None, window, cx, |s| {
2399 s.select_display_ranges([
2400 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2401 ])
2402 });
2403 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2404 assert_eq!(
2405 editor.buffer.read(cx).read(cx).text(),
2406 "one\n two\nthree\n four"
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 "\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 "two\nthree\n four"
2417 );
2418 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2419 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2420 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2421 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2422 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2423 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2424 });
2425}
2426
2427#[gpui::test]
2428fn test_newline(cx: &mut TestAppContext) {
2429 init_test(cx, |_| {});
2430
2431 let editor = cx.add_window(|window, cx| {
2432 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2433 build_editor(buffer.clone(), window, cx)
2434 });
2435
2436 _ = editor.update(cx, |editor, window, cx| {
2437 editor.change_selections(None, window, cx, |s| {
2438 s.select_display_ranges([
2439 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2440 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2441 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2442 ])
2443 });
2444
2445 editor.newline(&Newline, window, cx);
2446 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2447 });
2448}
2449
2450#[gpui::test]
2451fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2452 init_test(cx, |_| {});
2453
2454 let editor = cx.add_window(|window, cx| {
2455 let buffer = MultiBuffer::build_simple(
2456 "
2457 a
2458 b(
2459 X
2460 )
2461 c(
2462 X
2463 )
2464 "
2465 .unindent()
2466 .as_str(),
2467 cx,
2468 );
2469 let mut editor = build_editor(buffer.clone(), window, cx);
2470 editor.change_selections(None, window, cx, |s| {
2471 s.select_ranges([
2472 Point::new(2, 4)..Point::new(2, 5),
2473 Point::new(5, 4)..Point::new(5, 5),
2474 ])
2475 });
2476 editor
2477 });
2478
2479 _ = editor.update(cx, |editor, window, cx| {
2480 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2481 editor.buffer.update(cx, |buffer, cx| {
2482 buffer.edit(
2483 [
2484 (Point::new(1, 2)..Point::new(3, 0), ""),
2485 (Point::new(4, 2)..Point::new(6, 0), ""),
2486 ],
2487 None,
2488 cx,
2489 );
2490 assert_eq!(
2491 buffer.read(cx).text(),
2492 "
2493 a
2494 b()
2495 c()
2496 "
2497 .unindent()
2498 );
2499 });
2500 assert_eq!(
2501 editor.selections.ranges(cx),
2502 &[
2503 Point::new(1, 2)..Point::new(1, 2),
2504 Point::new(2, 2)..Point::new(2, 2),
2505 ],
2506 );
2507
2508 editor.newline(&Newline, window, cx);
2509 assert_eq!(
2510 editor.text(cx),
2511 "
2512 a
2513 b(
2514 )
2515 c(
2516 )
2517 "
2518 .unindent()
2519 );
2520
2521 // The selections are moved after the inserted newlines
2522 assert_eq!(
2523 editor.selections.ranges(cx),
2524 &[
2525 Point::new(2, 0)..Point::new(2, 0),
2526 Point::new(4, 0)..Point::new(4, 0),
2527 ],
2528 );
2529 });
2530}
2531
2532#[gpui::test]
2533async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2534 init_test(cx, |settings| {
2535 settings.defaults.tab_size = NonZeroU32::new(4)
2536 });
2537
2538 let language = Arc::new(
2539 Language::new(
2540 LanguageConfig::default(),
2541 Some(tree_sitter_rust::LANGUAGE.into()),
2542 )
2543 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2544 .unwrap(),
2545 );
2546
2547 let mut cx = EditorTestContext::new(cx).await;
2548 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2549 cx.set_state(indoc! {"
2550 const a: ˇA = (
2551 (ˇ
2552 «const_functionˇ»(ˇ),
2553 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2554 )ˇ
2555 ˇ);ˇ
2556 "});
2557
2558 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2559 cx.assert_editor_state(indoc! {"
2560 ˇ
2561 const a: A = (
2562 ˇ
2563 (
2564 ˇ
2565 ˇ
2566 const_function(),
2567 ˇ
2568 ˇ
2569 ˇ
2570 ˇ
2571 something_else,
2572 ˇ
2573 )
2574 ˇ
2575 ˇ
2576 );
2577 "});
2578}
2579
2580#[gpui::test]
2581async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2582 init_test(cx, |settings| {
2583 settings.defaults.tab_size = NonZeroU32::new(4)
2584 });
2585
2586 let language = Arc::new(
2587 Language::new(
2588 LanguageConfig::default(),
2589 Some(tree_sitter_rust::LANGUAGE.into()),
2590 )
2591 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2592 .unwrap(),
2593 );
2594
2595 let mut cx = EditorTestContext::new(cx).await;
2596 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2597 cx.set_state(indoc! {"
2598 const a: ˇA = (
2599 (ˇ
2600 «const_functionˇ»(ˇ),
2601 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2602 )ˇ
2603 ˇ);ˇ
2604 "});
2605
2606 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2607 cx.assert_editor_state(indoc! {"
2608 const a: A = (
2609 ˇ
2610 (
2611 ˇ
2612 const_function(),
2613 ˇ
2614 ˇ
2615 something_else,
2616 ˇ
2617 ˇ
2618 ˇ
2619 ˇ
2620 )
2621 ˇ
2622 );
2623 ˇ
2624 ˇ
2625 "});
2626}
2627
2628#[gpui::test]
2629async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2630 init_test(cx, |settings| {
2631 settings.defaults.tab_size = NonZeroU32::new(4)
2632 });
2633
2634 let language = Arc::new(Language::new(
2635 LanguageConfig {
2636 line_comments: vec!["//".into()],
2637 ..LanguageConfig::default()
2638 },
2639 None,
2640 ));
2641 {
2642 let mut cx = EditorTestContext::new(cx).await;
2643 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2644 cx.set_state(indoc! {"
2645 // Fooˇ
2646 "});
2647
2648 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2649 cx.assert_editor_state(indoc! {"
2650 // Foo
2651 //ˇ
2652 "});
2653 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2654 cx.set_state(indoc! {"
2655 ˇ// Foo
2656 "});
2657 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2658 cx.assert_editor_state(indoc! {"
2659
2660 ˇ// Foo
2661 "});
2662 }
2663 // Ensure that comment continuations can be disabled.
2664 update_test_language_settings(cx, |settings| {
2665 settings.defaults.extend_comment_on_newline = Some(false);
2666 });
2667 let mut cx = EditorTestContext::new(cx).await;
2668 cx.set_state(indoc! {"
2669 // Fooˇ
2670 "});
2671 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2672 cx.assert_editor_state(indoc! {"
2673 // Foo
2674 ˇ
2675 "});
2676}
2677
2678#[gpui::test]
2679fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2680 init_test(cx, |_| {});
2681
2682 let editor = cx.add_window(|window, cx| {
2683 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2684 let mut editor = build_editor(buffer.clone(), window, cx);
2685 editor.change_selections(None, window, cx, |s| {
2686 s.select_ranges([3..4, 11..12, 19..20])
2687 });
2688 editor
2689 });
2690
2691 _ = editor.update(cx, |editor, window, cx| {
2692 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2693 editor.buffer.update(cx, |buffer, cx| {
2694 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2695 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2696 });
2697 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2698
2699 editor.insert("Z", window, cx);
2700 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2701
2702 // The selections are moved after the inserted characters
2703 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2704 });
2705}
2706
2707#[gpui::test]
2708async fn test_tab(cx: &mut gpui::TestAppContext) {
2709 init_test(cx, |settings| {
2710 settings.defaults.tab_size = NonZeroU32::new(3)
2711 });
2712
2713 let mut cx = EditorTestContext::new(cx).await;
2714 cx.set_state(indoc! {"
2715 ˇabˇc
2716 ˇ🏀ˇ🏀ˇefg
2717 dˇ
2718 "});
2719 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2720 cx.assert_editor_state(indoc! {"
2721 ˇab ˇc
2722 ˇ🏀 ˇ🏀 ˇefg
2723 d ˇ
2724 "});
2725
2726 cx.set_state(indoc! {"
2727 a
2728 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2729 "});
2730 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2731 cx.assert_editor_state(indoc! {"
2732 a
2733 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2734 "});
2735}
2736
2737#[gpui::test]
2738async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2739 init_test(cx, |_| {});
2740
2741 let mut cx = EditorTestContext::new(cx).await;
2742 let language = Arc::new(
2743 Language::new(
2744 LanguageConfig::default(),
2745 Some(tree_sitter_rust::LANGUAGE.into()),
2746 )
2747 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2748 .unwrap(),
2749 );
2750 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2751
2752 // cursors that are already at the suggested indent level insert
2753 // a soft tab. cursors that are to the left of the suggested indent
2754 // auto-indent their line.
2755 cx.set_state(indoc! {"
2756 ˇ
2757 const a: B = (
2758 c(
2759 d(
2760 ˇ
2761 )
2762 ˇ
2763 ˇ )
2764 );
2765 "});
2766 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2767 cx.assert_editor_state(indoc! {"
2768 ˇ
2769 const a: B = (
2770 c(
2771 d(
2772 ˇ
2773 )
2774 ˇ
2775 ˇ)
2776 );
2777 "});
2778
2779 // handle auto-indent when there are multiple cursors on the same line
2780 cx.set_state(indoc! {"
2781 const a: B = (
2782 c(
2783 ˇ ˇ
2784 ˇ )
2785 );
2786 "});
2787 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2788 cx.assert_editor_state(indoc! {"
2789 const a: B = (
2790 c(
2791 ˇ
2792 ˇ)
2793 );
2794 "});
2795}
2796
2797#[gpui::test]
2798async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2799 init_test(cx, |settings| {
2800 settings.defaults.tab_size = NonZeroU32::new(4)
2801 });
2802
2803 let language = Arc::new(
2804 Language::new(
2805 LanguageConfig::default(),
2806 Some(tree_sitter_rust::LANGUAGE.into()),
2807 )
2808 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2809 .unwrap(),
2810 );
2811
2812 let mut cx = EditorTestContext::new(cx).await;
2813 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2814 cx.set_state(indoc! {"
2815 fn a() {
2816 if b {
2817 \t ˇc
2818 }
2819 }
2820 "});
2821
2822 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2823 cx.assert_editor_state(indoc! {"
2824 fn a() {
2825 if b {
2826 ˇc
2827 }
2828 }
2829 "});
2830}
2831
2832#[gpui::test]
2833async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2834 init_test(cx, |settings| {
2835 settings.defaults.tab_size = NonZeroU32::new(4);
2836 });
2837
2838 let mut cx = EditorTestContext::new(cx).await;
2839
2840 cx.set_state(indoc! {"
2841 «oneˇ» «twoˇ»
2842 three
2843 four
2844 "});
2845 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2846 cx.assert_editor_state(indoc! {"
2847 «oneˇ» «twoˇ»
2848 three
2849 four
2850 "});
2851
2852 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2853 cx.assert_editor_state(indoc! {"
2854 «oneˇ» «twoˇ»
2855 three
2856 four
2857 "});
2858
2859 // select across line ending
2860 cx.set_state(indoc! {"
2861 one two
2862 t«hree
2863 ˇ» four
2864 "});
2865 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2866 cx.assert_editor_state(indoc! {"
2867 one two
2868 t«hree
2869 ˇ» four
2870 "});
2871
2872 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2873 cx.assert_editor_state(indoc! {"
2874 one two
2875 t«hree
2876 ˇ» four
2877 "});
2878
2879 // Ensure that indenting/outdenting works when the cursor is at column 0.
2880 cx.set_state(indoc! {"
2881 one two
2882 ˇthree
2883 four
2884 "});
2885 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2886 cx.assert_editor_state(indoc! {"
2887 one two
2888 ˇthree
2889 four
2890 "});
2891
2892 cx.set_state(indoc! {"
2893 one two
2894 ˇ three
2895 four
2896 "});
2897 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2898 cx.assert_editor_state(indoc! {"
2899 one two
2900 ˇthree
2901 four
2902 "});
2903}
2904
2905#[gpui::test]
2906async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2907 init_test(cx, |settings| {
2908 settings.defaults.hard_tabs = Some(true);
2909 });
2910
2911 let mut cx = EditorTestContext::new(cx).await;
2912
2913 // select two ranges on one line
2914 cx.set_state(indoc! {"
2915 «oneˇ» «twoˇ»
2916 three
2917 four
2918 "});
2919 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2920 cx.assert_editor_state(indoc! {"
2921 \t«oneˇ» «twoˇ»
2922 three
2923 four
2924 "});
2925 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2926 cx.assert_editor_state(indoc! {"
2927 \t\t«oneˇ» «twoˇ»
2928 three
2929 four
2930 "});
2931 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2932 cx.assert_editor_state(indoc! {"
2933 \t«oneˇ» «twoˇ»
2934 three
2935 four
2936 "});
2937 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2938 cx.assert_editor_state(indoc! {"
2939 «oneˇ» «twoˇ»
2940 three
2941 four
2942 "});
2943
2944 // select across a line ending
2945 cx.set_state(indoc! {"
2946 one two
2947 t«hree
2948 ˇ»four
2949 "});
2950 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2951 cx.assert_editor_state(indoc! {"
2952 one two
2953 \tt«hree
2954 ˇ»four
2955 "});
2956 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2957 cx.assert_editor_state(indoc! {"
2958 one two
2959 \t\tt«hree
2960 ˇ»four
2961 "});
2962 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2963 cx.assert_editor_state(indoc! {"
2964 one two
2965 \tt«hree
2966 ˇ»four
2967 "});
2968 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 one two
2971 t«hree
2972 ˇ»four
2973 "});
2974
2975 // Ensure that indenting/outdenting works when the cursor is at column 0.
2976 cx.set_state(indoc! {"
2977 one two
2978 ˇthree
2979 four
2980 "});
2981 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2982 cx.assert_editor_state(indoc! {"
2983 one two
2984 ˇthree
2985 four
2986 "});
2987 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2988 cx.assert_editor_state(indoc! {"
2989 one two
2990 \tˇthree
2991 four
2992 "});
2993 cx.update_editor(|e, window, cx| e.tab_prev(&TabPrev, window, cx));
2994 cx.assert_editor_state(indoc! {"
2995 one two
2996 ˇthree
2997 four
2998 "});
2999}
3000
3001#[gpui::test]
3002fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3003 init_test(cx, |settings| {
3004 settings.languages.extend([
3005 (
3006 "TOML".into(),
3007 LanguageSettingsContent {
3008 tab_size: NonZeroU32::new(2),
3009 ..Default::default()
3010 },
3011 ),
3012 (
3013 "Rust".into(),
3014 LanguageSettingsContent {
3015 tab_size: NonZeroU32::new(4),
3016 ..Default::default()
3017 },
3018 ),
3019 ]);
3020 });
3021
3022 let toml_language = Arc::new(Language::new(
3023 LanguageConfig {
3024 name: "TOML".into(),
3025 ..Default::default()
3026 },
3027 None,
3028 ));
3029 let rust_language = Arc::new(Language::new(
3030 LanguageConfig {
3031 name: "Rust".into(),
3032 ..Default::default()
3033 },
3034 None,
3035 ));
3036
3037 let toml_buffer =
3038 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3039 let rust_buffer =
3040 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3041 let multibuffer = cx.new(|cx| {
3042 let mut multibuffer = MultiBuffer::new(ReadWrite);
3043 multibuffer.push_excerpts(
3044 toml_buffer.clone(),
3045 [ExcerptRange {
3046 context: Point::new(0, 0)..Point::new(2, 0),
3047 primary: None,
3048 }],
3049 cx,
3050 );
3051 multibuffer.push_excerpts(
3052 rust_buffer.clone(),
3053 [ExcerptRange {
3054 context: Point::new(0, 0)..Point::new(1, 0),
3055 primary: None,
3056 }],
3057 cx,
3058 );
3059 multibuffer
3060 });
3061
3062 cx.add_window(|window, cx| {
3063 let mut editor = build_editor(multibuffer, window, cx);
3064
3065 assert_eq!(
3066 editor.text(cx),
3067 indoc! {"
3068 a = 1
3069 b = 2
3070
3071 const c: usize = 3;
3072 "}
3073 );
3074
3075 select_ranges(
3076 &mut editor,
3077 indoc! {"
3078 «aˇ» = 1
3079 b = 2
3080
3081 «const c:ˇ» usize = 3;
3082 "},
3083 window,
3084 cx,
3085 );
3086
3087 editor.tab(&Tab, window, cx);
3088 assert_text_with_selections(
3089 &mut editor,
3090 indoc! {"
3091 «aˇ» = 1
3092 b = 2
3093
3094 «const c:ˇ» usize = 3;
3095 "},
3096 cx,
3097 );
3098 editor.tab_prev(&TabPrev, window, cx);
3099 assert_text_with_selections(
3100 &mut editor,
3101 indoc! {"
3102 «aˇ» = 1
3103 b = 2
3104
3105 «const c:ˇ» usize = 3;
3106 "},
3107 cx,
3108 );
3109
3110 editor
3111 });
3112}
3113
3114#[gpui::test]
3115async fn test_backspace(cx: &mut gpui::TestAppContext) {
3116 init_test(cx, |_| {});
3117
3118 let mut cx = EditorTestContext::new(cx).await;
3119
3120 // Basic backspace
3121 cx.set_state(indoc! {"
3122 onˇe two three
3123 fou«rˇ» five six
3124 seven «ˇeight nine
3125 »ten
3126 "});
3127 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3128 cx.assert_editor_state(indoc! {"
3129 oˇe two three
3130 fouˇ five six
3131 seven ˇten
3132 "});
3133
3134 // Test backspace inside and around indents
3135 cx.set_state(indoc! {"
3136 zero
3137 ˇone
3138 ˇtwo
3139 ˇ ˇ ˇ three
3140 ˇ ˇ four
3141 "});
3142 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3143 cx.assert_editor_state(indoc! {"
3144 zero
3145 ˇone
3146 ˇtwo
3147 ˇ threeˇ four
3148 "});
3149
3150 // Test backspace with line_mode set to true
3151 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3152 cx.set_state(indoc! {"
3153 The ˇquick ˇbrown
3154 fox jumps over
3155 the lazy dog
3156 ˇThe qu«ick bˇ»rown"});
3157 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3158 cx.assert_editor_state(indoc! {"
3159 ˇfox jumps over
3160 the lazy dogˇ"});
3161}
3162
3163#[gpui::test]
3164async fn test_delete(cx: &mut gpui::TestAppContext) {
3165 init_test(cx, |_| {});
3166
3167 let mut cx = EditorTestContext::new(cx).await;
3168 cx.set_state(indoc! {"
3169 onˇe two three
3170 fou«rˇ» five six
3171 seven «ˇeight nine
3172 »ten
3173 "});
3174 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3175 cx.assert_editor_state(indoc! {"
3176 onˇ two three
3177 fouˇ five six
3178 seven ˇten
3179 "});
3180
3181 // Test backspace with line_mode set to true
3182 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3183 cx.set_state(indoc! {"
3184 The ˇquick ˇbrown
3185 fox «ˇjum»ps over
3186 the lazy dog
3187 ˇThe qu«ick bˇ»rown"});
3188 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3189 cx.assert_editor_state("ˇthe lazy dogˇ");
3190}
3191
3192#[gpui::test]
3193fn test_delete_line(cx: &mut TestAppContext) {
3194 init_test(cx, |_| {});
3195
3196 let editor = cx.add_window(|window, cx| {
3197 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3198 build_editor(buffer, window, cx)
3199 });
3200 _ = editor.update(cx, |editor, window, cx| {
3201 editor.change_selections(None, window, cx, |s| {
3202 s.select_display_ranges([
3203 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3204 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3205 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3206 ])
3207 });
3208 editor.delete_line(&DeleteLine, window, cx);
3209 assert_eq!(editor.display_text(cx), "ghi");
3210 assert_eq!(
3211 editor.selections.display_ranges(cx),
3212 vec![
3213 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3214 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3215 ]
3216 );
3217 });
3218
3219 let editor = cx.add_window(|window, cx| {
3220 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3221 build_editor(buffer, window, cx)
3222 });
3223 _ = editor.update(cx, |editor, window, cx| {
3224 editor.change_selections(None, window, cx, |s| {
3225 s.select_display_ranges([
3226 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3227 ])
3228 });
3229 editor.delete_line(&DeleteLine, window, cx);
3230 assert_eq!(editor.display_text(cx), "ghi\n");
3231 assert_eq!(
3232 editor.selections.display_ranges(cx),
3233 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3234 );
3235 });
3236}
3237
3238#[gpui::test]
3239fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3240 init_test(cx, |_| {});
3241
3242 cx.add_window(|window, cx| {
3243 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3244 let mut editor = build_editor(buffer.clone(), window, cx);
3245 let buffer = buffer.read(cx).as_singleton().unwrap();
3246
3247 assert_eq!(
3248 editor.selections.ranges::<Point>(cx),
3249 &[Point::new(0, 0)..Point::new(0, 0)]
3250 );
3251
3252 // When on single line, replace newline at end by space
3253 editor.join_lines(&JoinLines, window, cx);
3254 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3255 assert_eq!(
3256 editor.selections.ranges::<Point>(cx),
3257 &[Point::new(0, 3)..Point::new(0, 3)]
3258 );
3259
3260 // When multiple lines are selected, remove newlines that are spanned by the selection
3261 editor.change_selections(None, window, cx, |s| {
3262 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3263 });
3264 editor.join_lines(&JoinLines, window, cx);
3265 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3266 assert_eq!(
3267 editor.selections.ranges::<Point>(cx),
3268 &[Point::new(0, 11)..Point::new(0, 11)]
3269 );
3270
3271 // Undo should be transactional
3272 editor.undo(&Undo, window, cx);
3273 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3274 assert_eq!(
3275 editor.selections.ranges::<Point>(cx),
3276 &[Point::new(0, 5)..Point::new(2, 2)]
3277 );
3278
3279 // When joining an empty line don't insert a space
3280 editor.change_selections(None, window, cx, |s| {
3281 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3282 });
3283 editor.join_lines(&JoinLines, window, cx);
3284 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3285 assert_eq!(
3286 editor.selections.ranges::<Point>(cx),
3287 [Point::new(2, 3)..Point::new(2, 3)]
3288 );
3289
3290 // We can remove trailing newlines
3291 editor.join_lines(&JoinLines, window, cx);
3292 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3293 assert_eq!(
3294 editor.selections.ranges::<Point>(cx),
3295 [Point::new(2, 3)..Point::new(2, 3)]
3296 );
3297
3298 // We don't blow up on the last line
3299 editor.join_lines(&JoinLines, window, cx);
3300 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3301 assert_eq!(
3302 editor.selections.ranges::<Point>(cx),
3303 [Point::new(2, 3)..Point::new(2, 3)]
3304 );
3305
3306 // reset to test indentation
3307 editor.buffer.update(cx, |buffer, cx| {
3308 buffer.edit(
3309 [
3310 (Point::new(1, 0)..Point::new(1, 2), " "),
3311 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3312 ],
3313 None,
3314 cx,
3315 )
3316 });
3317
3318 // We remove any leading spaces
3319 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3320 editor.change_selections(None, window, cx, |s| {
3321 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3322 });
3323 editor.join_lines(&JoinLines, window, cx);
3324 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3325
3326 // We don't insert a space for a line containing only spaces
3327 editor.join_lines(&JoinLines, window, cx);
3328 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3329
3330 // We ignore any leading tabs
3331 editor.join_lines(&JoinLines, window, cx);
3332 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3333
3334 editor
3335 });
3336}
3337
3338#[gpui::test]
3339fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3340 init_test(cx, |_| {});
3341
3342 cx.add_window(|window, cx| {
3343 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3344 let mut editor = build_editor(buffer.clone(), window, cx);
3345 let buffer = buffer.read(cx).as_singleton().unwrap();
3346
3347 editor.change_selections(None, window, cx, |s| {
3348 s.select_ranges([
3349 Point::new(0, 2)..Point::new(1, 1),
3350 Point::new(1, 2)..Point::new(1, 2),
3351 Point::new(3, 1)..Point::new(3, 2),
3352 ])
3353 });
3354
3355 editor.join_lines(&JoinLines, window, cx);
3356 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3357
3358 assert_eq!(
3359 editor.selections.ranges::<Point>(cx),
3360 [
3361 Point::new(0, 7)..Point::new(0, 7),
3362 Point::new(1, 3)..Point::new(1, 3)
3363 ]
3364 );
3365 editor
3366 });
3367}
3368
3369#[gpui::test]
3370async fn test_join_lines_with_git_diff_base(
3371 executor: BackgroundExecutor,
3372 cx: &mut gpui::TestAppContext,
3373) {
3374 init_test(cx, |_| {});
3375
3376 let mut cx = EditorTestContext::new(cx).await;
3377
3378 let diff_base = r#"
3379 Line 0
3380 Line 1
3381 Line 2
3382 Line 3
3383 "#
3384 .unindent();
3385
3386 cx.set_state(
3387 &r#"
3388 ˇLine 0
3389 Line 1
3390 Line 2
3391 Line 3
3392 "#
3393 .unindent(),
3394 );
3395
3396 cx.set_diff_base(&diff_base);
3397 executor.run_until_parked();
3398
3399 // Join lines
3400 cx.update_editor(|editor, window, cx| {
3401 editor.join_lines(&JoinLines, window, cx);
3402 });
3403 executor.run_until_parked();
3404
3405 cx.assert_editor_state(
3406 &r#"
3407 Line 0ˇ Line 1
3408 Line 2
3409 Line 3
3410 "#
3411 .unindent(),
3412 );
3413 // Join again
3414 cx.update_editor(|editor, window, cx| {
3415 editor.join_lines(&JoinLines, window, cx);
3416 });
3417 executor.run_until_parked();
3418
3419 cx.assert_editor_state(
3420 &r#"
3421 Line 0 Line 1ˇ Line 2
3422 Line 3
3423 "#
3424 .unindent(),
3425 );
3426}
3427
3428#[gpui::test]
3429async fn test_custom_newlines_cause_no_false_positive_diffs(
3430 executor: BackgroundExecutor,
3431 cx: &mut gpui::TestAppContext,
3432) {
3433 init_test(cx, |_| {});
3434 let mut cx = EditorTestContext::new(cx).await;
3435 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3436 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3437 executor.run_until_parked();
3438
3439 cx.update_editor(|editor, window, cx| {
3440 let snapshot = editor.snapshot(window, cx);
3441 assert_eq!(
3442 snapshot
3443 .buffer_snapshot
3444 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3445 .collect::<Vec<_>>(),
3446 Vec::new(),
3447 "Should not have any diffs for files with custom newlines"
3448 );
3449 });
3450}
3451
3452#[gpui::test]
3453async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3454 init_test(cx, |_| {});
3455
3456 let mut cx = EditorTestContext::new(cx).await;
3457
3458 // Test sort_lines_case_insensitive()
3459 cx.set_state(indoc! {"
3460 «z
3461 y
3462 x
3463 Z
3464 Y
3465 Xˇ»
3466 "});
3467 cx.update_editor(|e, window, cx| {
3468 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3469 });
3470 cx.assert_editor_state(indoc! {"
3471 «x
3472 X
3473 y
3474 Y
3475 z
3476 Zˇ»
3477 "});
3478
3479 // Test reverse_lines()
3480 cx.set_state(indoc! {"
3481 «5
3482 4
3483 3
3484 2
3485 1ˇ»
3486 "});
3487 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3488 cx.assert_editor_state(indoc! {"
3489 «1
3490 2
3491 3
3492 4
3493 5ˇ»
3494 "});
3495
3496 // Skip testing shuffle_line()
3497
3498 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3499 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3500
3501 // Don't manipulate when cursor is on single line, but expand the selection
3502 cx.set_state(indoc! {"
3503 ddˇdd
3504 ccc
3505 bb
3506 a
3507 "});
3508 cx.update_editor(|e, window, cx| {
3509 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3510 });
3511 cx.assert_editor_state(indoc! {"
3512 «ddddˇ»
3513 ccc
3514 bb
3515 a
3516 "});
3517
3518 // Basic manipulate case
3519 // Start selection moves to column 0
3520 // End of selection shrinks to fit shorter line
3521 cx.set_state(indoc! {"
3522 dd«d
3523 ccc
3524 bb
3525 aaaaaˇ»
3526 "});
3527 cx.update_editor(|e, window, cx| {
3528 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3529 });
3530 cx.assert_editor_state(indoc! {"
3531 «aaaaa
3532 bb
3533 ccc
3534 dddˇ»
3535 "});
3536
3537 // Manipulate case with newlines
3538 cx.set_state(indoc! {"
3539 dd«d
3540 ccc
3541
3542 bb
3543 aaaaa
3544
3545 ˇ»
3546 "});
3547 cx.update_editor(|e, window, cx| {
3548 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3549 });
3550 cx.assert_editor_state(indoc! {"
3551 «
3552
3553 aaaaa
3554 bb
3555 ccc
3556 dddˇ»
3557
3558 "});
3559
3560 // Adding new line
3561 cx.set_state(indoc! {"
3562 aa«a
3563 bbˇ»b
3564 "});
3565 cx.update_editor(|e, window, cx| {
3566 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3567 });
3568 cx.assert_editor_state(indoc! {"
3569 «aaa
3570 bbb
3571 added_lineˇ»
3572 "});
3573
3574 // Removing line
3575 cx.set_state(indoc! {"
3576 aa«a
3577 bbbˇ»
3578 "});
3579 cx.update_editor(|e, window, cx| {
3580 e.manipulate_lines(window, cx, |lines| {
3581 lines.pop();
3582 })
3583 });
3584 cx.assert_editor_state(indoc! {"
3585 «aaaˇ»
3586 "});
3587
3588 // Removing all lines
3589 cx.set_state(indoc! {"
3590 aa«a
3591 bbbˇ»
3592 "});
3593 cx.update_editor(|e, window, cx| {
3594 e.manipulate_lines(window, cx, |lines| {
3595 lines.drain(..);
3596 })
3597 });
3598 cx.assert_editor_state(indoc! {"
3599 ˇ
3600 "});
3601}
3602
3603#[gpui::test]
3604async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3605 init_test(cx, |_| {});
3606
3607 let mut cx = EditorTestContext::new(cx).await;
3608
3609 // Consider continuous selection as single selection
3610 cx.set_state(indoc! {"
3611 Aaa«aa
3612 cˇ»c«c
3613 bb
3614 aaaˇ»aa
3615 "});
3616 cx.update_editor(|e, window, cx| {
3617 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3618 });
3619 cx.assert_editor_state(indoc! {"
3620 «Aaaaa
3621 ccc
3622 bb
3623 aaaaaˇ»
3624 "});
3625
3626 cx.set_state(indoc! {"
3627 Aaa«aa
3628 cˇ»c«c
3629 bb
3630 aaaˇ»aa
3631 "});
3632 cx.update_editor(|e, window, cx| {
3633 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3634 });
3635 cx.assert_editor_state(indoc! {"
3636 «Aaaaa
3637 ccc
3638 bbˇ»
3639 "});
3640
3641 // Consider non continuous selection as distinct dedup operations
3642 cx.set_state(indoc! {"
3643 «aaaaa
3644 bb
3645 aaaaa
3646 aaaaaˇ»
3647
3648 aaa«aaˇ»
3649 "});
3650 cx.update_editor(|e, window, cx| {
3651 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3652 });
3653 cx.assert_editor_state(indoc! {"
3654 «aaaaa
3655 bbˇ»
3656
3657 «aaaaaˇ»
3658 "});
3659}
3660
3661#[gpui::test]
3662async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3663 init_test(cx, |_| {});
3664
3665 let mut cx = EditorTestContext::new(cx).await;
3666
3667 cx.set_state(indoc! {"
3668 «Aaa
3669 aAa
3670 Aaaˇ»
3671 "});
3672 cx.update_editor(|e, window, cx| {
3673 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3674 });
3675 cx.assert_editor_state(indoc! {"
3676 «Aaa
3677 aAaˇ»
3678 "});
3679
3680 cx.set_state(indoc! {"
3681 «Aaa
3682 aAa
3683 aaAˇ»
3684 "});
3685 cx.update_editor(|e, window, cx| {
3686 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3687 });
3688 cx.assert_editor_state(indoc! {"
3689 «Aaaˇ»
3690 "});
3691}
3692
3693#[gpui::test]
3694async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3695 init_test(cx, |_| {});
3696
3697 let mut cx = EditorTestContext::new(cx).await;
3698
3699 // Manipulate with multiple selections on a single line
3700 cx.set_state(indoc! {"
3701 dd«dd
3702 cˇ»c«c
3703 bb
3704 aaaˇ»aa
3705 "});
3706 cx.update_editor(|e, window, cx| {
3707 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3708 });
3709 cx.assert_editor_state(indoc! {"
3710 «aaaaa
3711 bb
3712 ccc
3713 ddddˇ»
3714 "});
3715
3716 // Manipulate with multiple disjoin selections
3717 cx.set_state(indoc! {"
3718 5«
3719 4
3720 3
3721 2
3722 1ˇ»
3723
3724 dd«dd
3725 ccc
3726 bb
3727 aaaˇ»aa
3728 "});
3729 cx.update_editor(|e, window, cx| {
3730 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3731 });
3732 cx.assert_editor_state(indoc! {"
3733 «1
3734 2
3735 3
3736 4
3737 5ˇ»
3738
3739 «aaaaa
3740 bb
3741 ccc
3742 ddddˇ»
3743 "});
3744
3745 // Adding lines on each selection
3746 cx.set_state(indoc! {"
3747 2«
3748 1ˇ»
3749
3750 bb«bb
3751 aaaˇ»aa
3752 "});
3753 cx.update_editor(|e, window, cx| {
3754 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3755 });
3756 cx.assert_editor_state(indoc! {"
3757 «2
3758 1
3759 added lineˇ»
3760
3761 «bbbb
3762 aaaaa
3763 added lineˇ»
3764 "});
3765
3766 // Removing lines on each selection
3767 cx.set_state(indoc! {"
3768 2«
3769 1ˇ»
3770
3771 bb«bb
3772 aaaˇ»aa
3773 "});
3774 cx.update_editor(|e, window, cx| {
3775 e.manipulate_lines(window, cx, |lines| {
3776 lines.pop();
3777 })
3778 });
3779 cx.assert_editor_state(indoc! {"
3780 «2ˇ»
3781
3782 «bbbbˇ»
3783 "});
3784}
3785
3786#[gpui::test]
3787async fn test_manipulate_text(cx: &mut TestAppContext) {
3788 init_test(cx, |_| {});
3789
3790 let mut cx = EditorTestContext::new(cx).await;
3791
3792 // Test convert_to_upper_case()
3793 cx.set_state(indoc! {"
3794 «hello worldˇ»
3795 "});
3796 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3797 cx.assert_editor_state(indoc! {"
3798 «HELLO WORLDˇ»
3799 "});
3800
3801 // Test convert_to_lower_case()
3802 cx.set_state(indoc! {"
3803 «HELLO WORLDˇ»
3804 "});
3805 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3806 cx.assert_editor_state(indoc! {"
3807 «hello worldˇ»
3808 "});
3809
3810 // Test multiple line, single selection case
3811 cx.set_state(indoc! {"
3812 «The quick brown
3813 fox jumps over
3814 the lazy dogˇ»
3815 "});
3816 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3817 cx.assert_editor_state(indoc! {"
3818 «The Quick Brown
3819 Fox Jumps Over
3820 The Lazy Dogˇ»
3821 "});
3822
3823 // Test multiple line, single selection case
3824 cx.set_state(indoc! {"
3825 «The quick brown
3826 fox jumps over
3827 the lazy dogˇ»
3828 "});
3829 cx.update_editor(|e, window, cx| {
3830 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3831 });
3832 cx.assert_editor_state(indoc! {"
3833 «TheQuickBrown
3834 FoxJumpsOver
3835 TheLazyDogˇ»
3836 "});
3837
3838 // From here on out, test more complex cases of manipulate_text()
3839
3840 // Test no selection case - should affect words cursors are in
3841 // Cursor at beginning, middle, and end of word
3842 cx.set_state(indoc! {"
3843 ˇhello big beauˇtiful worldˇ
3844 "});
3845 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3846 cx.assert_editor_state(indoc! {"
3847 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3848 "});
3849
3850 // Test multiple selections on a single line and across multiple lines
3851 cx.set_state(indoc! {"
3852 «Theˇ» quick «brown
3853 foxˇ» jumps «overˇ»
3854 the «lazyˇ» dog
3855 "});
3856 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3857 cx.assert_editor_state(indoc! {"
3858 «THEˇ» quick «BROWN
3859 FOXˇ» jumps «OVERˇ»
3860 the «LAZYˇ» dog
3861 "});
3862
3863 // Test case where text length grows
3864 cx.set_state(indoc! {"
3865 «tschüߡ»
3866 "});
3867 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3868 cx.assert_editor_state(indoc! {"
3869 «TSCHÜSSˇ»
3870 "});
3871
3872 // Test to make sure we don't crash when text shrinks
3873 cx.set_state(indoc! {"
3874 aaa_bbbˇ
3875 "});
3876 cx.update_editor(|e, window, cx| {
3877 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3878 });
3879 cx.assert_editor_state(indoc! {"
3880 «aaaBbbˇ»
3881 "});
3882
3883 // Test to make sure we all aware of the fact that each word can grow and shrink
3884 // Final selections should be aware of this fact
3885 cx.set_state(indoc! {"
3886 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3887 "});
3888 cx.update_editor(|e, window, cx| {
3889 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3890 });
3891 cx.assert_editor_state(indoc! {"
3892 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3893 "});
3894
3895 cx.set_state(indoc! {"
3896 «hElLo, WoRld!ˇ»
3897 "});
3898 cx.update_editor(|e, window, cx| {
3899 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
3900 });
3901 cx.assert_editor_state(indoc! {"
3902 «HeLlO, wOrLD!ˇ»
3903 "});
3904}
3905
3906#[gpui::test]
3907fn test_duplicate_line(cx: &mut TestAppContext) {
3908 init_test(cx, |_| {});
3909
3910 let editor = cx.add_window(|window, cx| {
3911 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3912 build_editor(buffer, window, cx)
3913 });
3914 _ = editor.update(cx, |editor, window, cx| {
3915 editor.change_selections(None, window, cx, |s| {
3916 s.select_display_ranges([
3917 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3918 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3919 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3920 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3921 ])
3922 });
3923 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3924 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3925 assert_eq!(
3926 editor.selections.display_ranges(cx),
3927 vec![
3928 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3929 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3930 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3931 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3932 ]
3933 );
3934 });
3935
3936 let editor = cx.add_window(|window, cx| {
3937 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3938 build_editor(buffer, window, cx)
3939 });
3940 _ = editor.update(cx, |editor, window, cx| {
3941 editor.change_selections(None, window, cx, |s| {
3942 s.select_display_ranges([
3943 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3944 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3945 ])
3946 });
3947 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
3948 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3949 assert_eq!(
3950 editor.selections.display_ranges(cx),
3951 vec![
3952 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3953 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3954 ]
3955 );
3956 });
3957
3958 // With `move_upwards` the selections stay in place, except for
3959 // the lines inserted above them
3960 let editor = cx.add_window(|window, cx| {
3961 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3962 build_editor(buffer, window, cx)
3963 });
3964 _ = editor.update(cx, |editor, window, cx| {
3965 editor.change_selections(None, window, cx, |s| {
3966 s.select_display_ranges([
3967 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3968 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3969 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3970 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3971 ])
3972 });
3973 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3974 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3975 assert_eq!(
3976 editor.selections.display_ranges(cx),
3977 vec![
3978 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3979 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3980 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3981 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3982 ]
3983 );
3984 });
3985
3986 let editor = cx.add_window(|window, cx| {
3987 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3988 build_editor(buffer, window, cx)
3989 });
3990 _ = editor.update(cx, |editor, window, cx| {
3991 editor.change_selections(None, window, cx, |s| {
3992 s.select_display_ranges([
3993 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3994 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3995 ])
3996 });
3997 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
3998 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3999 assert_eq!(
4000 editor.selections.display_ranges(cx),
4001 vec![
4002 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4003 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4004 ]
4005 );
4006 });
4007
4008 let editor = cx.add_window(|window, cx| {
4009 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4010 build_editor(buffer, window, cx)
4011 });
4012 _ = editor.update(cx, |editor, window, cx| {
4013 editor.change_selections(None, window, cx, |s| {
4014 s.select_display_ranges([
4015 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4016 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4017 ])
4018 });
4019 editor.duplicate_selection(&DuplicateSelection, window, cx);
4020 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4021 assert_eq!(
4022 editor.selections.display_ranges(cx),
4023 vec![
4024 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4025 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4026 ]
4027 );
4028 });
4029}
4030
4031#[gpui::test]
4032fn test_move_line_up_down(cx: &mut TestAppContext) {
4033 init_test(cx, |_| {});
4034
4035 let editor = cx.add_window(|window, cx| {
4036 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4037 build_editor(buffer, window, cx)
4038 });
4039 _ = editor.update(cx, |editor, window, cx| {
4040 editor.fold_creases(
4041 vec![
4042 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4043 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4044 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4045 ],
4046 true,
4047 window,
4048 cx,
4049 );
4050 editor.change_selections(None, window, cx, |s| {
4051 s.select_display_ranges([
4052 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4053 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4054 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4055 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4056 ])
4057 });
4058 assert_eq!(
4059 editor.display_text(cx),
4060 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4061 );
4062
4063 editor.move_line_up(&MoveLineUp, window, cx);
4064 assert_eq!(
4065 editor.display_text(cx),
4066 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4067 );
4068 assert_eq!(
4069 editor.selections.display_ranges(cx),
4070 vec![
4071 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4072 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4073 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4074 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4075 ]
4076 );
4077 });
4078
4079 _ = editor.update(cx, |editor, window, cx| {
4080 editor.move_line_down(&MoveLineDown, window, cx);
4081 assert_eq!(
4082 editor.display_text(cx),
4083 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4084 );
4085 assert_eq!(
4086 editor.selections.display_ranges(cx),
4087 vec![
4088 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4089 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4090 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4091 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4092 ]
4093 );
4094 });
4095
4096 _ = editor.update(cx, |editor, window, cx| {
4097 editor.move_line_down(&MoveLineDown, window, cx);
4098 assert_eq!(
4099 editor.display_text(cx),
4100 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4101 );
4102 assert_eq!(
4103 editor.selections.display_ranges(cx),
4104 vec![
4105 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4106 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4107 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4108 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4109 ]
4110 );
4111 });
4112
4113 _ = editor.update(cx, |editor, window, cx| {
4114 editor.move_line_up(&MoveLineUp, window, cx);
4115 assert_eq!(
4116 editor.display_text(cx),
4117 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4118 );
4119 assert_eq!(
4120 editor.selections.display_ranges(cx),
4121 vec![
4122 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4123 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4124 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4125 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4126 ]
4127 );
4128 });
4129}
4130
4131#[gpui::test]
4132fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4133 init_test(cx, |_| {});
4134
4135 let editor = cx.add_window(|window, cx| {
4136 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4137 build_editor(buffer, window, cx)
4138 });
4139 _ = editor.update(cx, |editor, window, cx| {
4140 let snapshot = editor.buffer.read(cx).snapshot(cx);
4141 editor.insert_blocks(
4142 [BlockProperties {
4143 style: BlockStyle::Fixed,
4144 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4145 height: 1,
4146 render: Arc::new(|_| div().into_any()),
4147 priority: 0,
4148 }],
4149 Some(Autoscroll::fit()),
4150 cx,
4151 );
4152 editor.change_selections(None, window, cx, |s| {
4153 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4154 });
4155 editor.move_line_down(&MoveLineDown, window, cx);
4156 });
4157}
4158
4159#[gpui::test]
4160async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4161 init_test(cx, |_| {});
4162
4163 let mut cx = EditorTestContext::new(cx).await;
4164 cx.set_state(
4165 &"
4166 ˇzero
4167 one
4168 two
4169 three
4170 four
4171 five
4172 "
4173 .unindent(),
4174 );
4175
4176 // Create a four-line block that replaces three lines of text.
4177 cx.update_editor(|editor, window, cx| {
4178 let snapshot = editor.snapshot(window, cx);
4179 let snapshot = &snapshot.buffer_snapshot;
4180 let placement = BlockPlacement::Replace(
4181 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4182 );
4183 editor.insert_blocks(
4184 [BlockProperties {
4185 placement,
4186 height: 4,
4187 style: BlockStyle::Sticky,
4188 render: Arc::new(|_| gpui::div().into_any_element()),
4189 priority: 0,
4190 }],
4191 None,
4192 cx,
4193 );
4194 });
4195
4196 // Move down so that the cursor touches the block.
4197 cx.update_editor(|editor, window, cx| {
4198 editor.move_down(&Default::default(), window, cx);
4199 });
4200 cx.assert_editor_state(
4201 &"
4202 zero
4203 «one
4204 two
4205 threeˇ»
4206 four
4207 five
4208 "
4209 .unindent(),
4210 );
4211
4212 // Move down past the block.
4213 cx.update_editor(|editor, window, cx| {
4214 editor.move_down(&Default::default(), window, cx);
4215 });
4216 cx.assert_editor_state(
4217 &"
4218 zero
4219 one
4220 two
4221 three
4222 ˇfour
4223 five
4224 "
4225 .unindent(),
4226 );
4227}
4228
4229#[gpui::test]
4230fn test_transpose(cx: &mut TestAppContext) {
4231 init_test(cx, |_| {});
4232
4233 _ = cx.add_window(|window, cx| {
4234 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4235 editor.set_style(EditorStyle::default(), window, cx);
4236 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4237 editor.transpose(&Default::default(), window, cx);
4238 assert_eq!(editor.text(cx), "bac");
4239 assert_eq!(editor.selections.ranges(cx), [2..2]);
4240
4241 editor.transpose(&Default::default(), window, cx);
4242 assert_eq!(editor.text(cx), "bca");
4243 assert_eq!(editor.selections.ranges(cx), [3..3]);
4244
4245 editor.transpose(&Default::default(), window, cx);
4246 assert_eq!(editor.text(cx), "bac");
4247 assert_eq!(editor.selections.ranges(cx), [3..3]);
4248
4249 editor
4250 });
4251
4252 _ = cx.add_window(|window, cx| {
4253 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4254 editor.set_style(EditorStyle::default(), window, cx);
4255 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4256 editor.transpose(&Default::default(), window, cx);
4257 assert_eq!(editor.text(cx), "acb\nde");
4258 assert_eq!(editor.selections.ranges(cx), [3..3]);
4259
4260 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4261 editor.transpose(&Default::default(), window, cx);
4262 assert_eq!(editor.text(cx), "acbd\ne");
4263 assert_eq!(editor.selections.ranges(cx), [5..5]);
4264
4265 editor.transpose(&Default::default(), window, cx);
4266 assert_eq!(editor.text(cx), "acbde\n");
4267 assert_eq!(editor.selections.ranges(cx), [6..6]);
4268
4269 editor.transpose(&Default::default(), window, cx);
4270 assert_eq!(editor.text(cx), "acbd\ne");
4271 assert_eq!(editor.selections.ranges(cx), [6..6]);
4272
4273 editor
4274 });
4275
4276 _ = cx.add_window(|window, cx| {
4277 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4278 editor.set_style(EditorStyle::default(), window, cx);
4279 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4280 editor.transpose(&Default::default(), window, cx);
4281 assert_eq!(editor.text(cx), "bacd\ne");
4282 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4283
4284 editor.transpose(&Default::default(), window, cx);
4285 assert_eq!(editor.text(cx), "bcade\n");
4286 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4287
4288 editor.transpose(&Default::default(), window, cx);
4289 assert_eq!(editor.text(cx), "bcda\ne");
4290 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4291
4292 editor.transpose(&Default::default(), window, cx);
4293 assert_eq!(editor.text(cx), "bcade\n");
4294 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4295
4296 editor.transpose(&Default::default(), window, cx);
4297 assert_eq!(editor.text(cx), "bcaed\n");
4298 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4299
4300 editor
4301 });
4302
4303 _ = cx.add_window(|window, cx| {
4304 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4305 editor.set_style(EditorStyle::default(), window, cx);
4306 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4307 editor.transpose(&Default::default(), window, cx);
4308 assert_eq!(editor.text(cx), "🏀🍐✋");
4309 assert_eq!(editor.selections.ranges(cx), [8..8]);
4310
4311 editor.transpose(&Default::default(), window, cx);
4312 assert_eq!(editor.text(cx), "🏀✋🍐");
4313 assert_eq!(editor.selections.ranges(cx), [11..11]);
4314
4315 editor.transpose(&Default::default(), window, cx);
4316 assert_eq!(editor.text(cx), "🏀🍐✋");
4317 assert_eq!(editor.selections.ranges(cx), [11..11]);
4318
4319 editor
4320 });
4321}
4322
4323#[gpui::test]
4324async fn test_rewrap(cx: &mut TestAppContext) {
4325 init_test(cx, |_| {});
4326
4327 let mut cx = EditorTestContext::new(cx).await;
4328
4329 let language_with_c_comments = Arc::new(Language::new(
4330 LanguageConfig {
4331 line_comments: vec!["// ".into()],
4332 ..LanguageConfig::default()
4333 },
4334 None,
4335 ));
4336 let language_with_pound_comments = Arc::new(Language::new(
4337 LanguageConfig {
4338 line_comments: vec!["# ".into()],
4339 ..LanguageConfig::default()
4340 },
4341 None,
4342 ));
4343 let markdown_language = Arc::new(Language::new(
4344 LanguageConfig {
4345 name: "Markdown".into(),
4346 ..LanguageConfig::default()
4347 },
4348 None,
4349 ));
4350 let language_with_doc_comments = Arc::new(Language::new(
4351 LanguageConfig {
4352 line_comments: vec!["// ".into(), "/// ".into()],
4353 ..LanguageConfig::default()
4354 },
4355 Some(tree_sitter_rust::LANGUAGE.into()),
4356 ));
4357
4358 let plaintext_language = Arc::new(Language::new(
4359 LanguageConfig {
4360 name: "Plain Text".into(),
4361 ..LanguageConfig::default()
4362 },
4363 None,
4364 ));
4365
4366 assert_rewrap(
4367 indoc! {"
4368 // ˇ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.
4369 "},
4370 indoc! {"
4371 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4372 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4373 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4374 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4375 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4376 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4377 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4378 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4379 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4380 // porttitor id. Aliquam id accumsan eros.
4381 "},
4382 language_with_c_comments.clone(),
4383 &mut cx,
4384 );
4385
4386 // Test that rewrapping works inside of a selection
4387 assert_rewrap(
4388 indoc! {"
4389 «// 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.ˇ»
4390 "},
4391 indoc! {"
4392 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4393 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4394 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4395 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4396 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4397 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4398 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4399 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4400 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4401 // porttitor id. Aliquam id accumsan eros.ˇ»
4402 "},
4403 language_with_c_comments.clone(),
4404 &mut cx,
4405 );
4406
4407 // Test that cursors that expand to the same region are collapsed.
4408 assert_rewrap(
4409 indoc! {"
4410 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4411 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4412 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4413 // ˇ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.
4414 "},
4415 indoc! {"
4416 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4417 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4418 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4419 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4420 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4421 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4422 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4423 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4424 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4425 // porttitor id. Aliquam id accumsan eros.
4426 "},
4427 language_with_c_comments.clone(),
4428 &mut cx,
4429 );
4430
4431 // Test that non-contiguous selections are treated separately.
4432 assert_rewrap(
4433 indoc! {"
4434 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4435 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4436 //
4437 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4438 // ˇ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.
4439 "},
4440 indoc! {"
4441 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4442 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4443 // auctor, eu lacinia sapien scelerisque.
4444 //
4445 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4446 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4447 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4448 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4449 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4450 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4451 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4452 "},
4453 language_with_c_comments.clone(),
4454 &mut cx,
4455 );
4456
4457 // Test that different comment prefixes are supported.
4458 assert_rewrap(
4459 indoc! {"
4460 # ˇ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.
4461 "},
4462 indoc! {"
4463 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4464 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4465 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4466 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4467 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4468 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4469 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4470 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4471 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4472 # accumsan eros.
4473 "},
4474 language_with_pound_comments.clone(),
4475 &mut cx,
4476 );
4477
4478 // Test that rewrapping is ignored outside of comments in most languages.
4479 assert_rewrap(
4480 indoc! {"
4481 /// Adds two numbers.
4482 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4483 fn add(a: u32, b: u32) -> u32 {
4484 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ˇ
4485 }
4486 "},
4487 indoc! {"
4488 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4489 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4490 fn add(a: u32, b: u32) -> u32 {
4491 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ˇ
4492 }
4493 "},
4494 language_with_doc_comments.clone(),
4495 &mut cx,
4496 );
4497
4498 // Test that rewrapping works in Markdown and Plain Text languages.
4499 assert_rewrap(
4500 indoc! {"
4501 # Hello
4502
4503 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.
4504 "},
4505 indoc! {"
4506 # Hello
4507
4508 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4509 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4510 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4511 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4512 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4513 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4514 Integer sit amet scelerisque nisi.
4515 "},
4516 markdown_language,
4517 &mut cx,
4518 );
4519
4520 assert_rewrap(
4521 indoc! {"
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 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4526 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4527 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4528 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4529 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4530 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4531 Integer sit amet scelerisque nisi.
4532 "},
4533 plaintext_language,
4534 &mut cx,
4535 );
4536
4537 // Test rewrapping unaligned comments in a selection.
4538 assert_rewrap(
4539 indoc! {"
4540 fn foo() {
4541 if true {
4542 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4543 // Praesent semper egestas tellus id dignissim.ˇ»
4544 do_something();
4545 } else {
4546 //
4547 }
4548 }
4549 "},
4550 indoc! {"
4551 fn foo() {
4552 if true {
4553 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4554 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4555 // egestas tellus id dignissim.ˇ»
4556 do_something();
4557 } else {
4558 //
4559 }
4560 }
4561 "},
4562 language_with_doc_comments.clone(),
4563 &mut cx,
4564 );
4565
4566 assert_rewrap(
4567 indoc! {"
4568 fn foo() {
4569 if true {
4570 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4571 // Praesent semper egestas tellus id dignissim.»
4572 do_something();
4573 } else {
4574 //
4575 }
4576
4577 }
4578 "},
4579 indoc! {"
4580 fn foo() {
4581 if true {
4582 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4583 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4584 // egestas tellus id dignissim.»
4585 do_something();
4586 } else {
4587 //
4588 }
4589
4590 }
4591 "},
4592 language_with_doc_comments.clone(),
4593 &mut cx,
4594 );
4595
4596 #[track_caller]
4597 fn assert_rewrap(
4598 unwrapped_text: &str,
4599 wrapped_text: &str,
4600 language: Arc<Language>,
4601 cx: &mut EditorTestContext,
4602 ) {
4603 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4604 cx.set_state(unwrapped_text);
4605 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4606 cx.assert_editor_state(wrapped_text);
4607 }
4608}
4609
4610#[gpui::test]
4611async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4612 init_test(cx, |_| {});
4613
4614 let mut cx = EditorTestContext::new(cx).await;
4615
4616 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4617 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4618 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4619
4620 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4621 cx.set_state("two ˇfour ˇsix ˇ");
4622 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4623 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4624
4625 // Paste again but with only two cursors. Since the number of cursors doesn't
4626 // match the number of slices in the clipboard, the entire clipboard text
4627 // is pasted at each cursor.
4628 cx.set_state("ˇtwo one✅ four three six five ˇ");
4629 cx.update_editor(|e, window, cx| {
4630 e.handle_input("( ", window, cx);
4631 e.paste(&Paste, window, cx);
4632 e.handle_input(") ", window, cx);
4633 });
4634 cx.assert_editor_state(
4635 &([
4636 "( one✅ ",
4637 "three ",
4638 "five ) ˇtwo one✅ four three six five ( one✅ ",
4639 "three ",
4640 "five ) ˇ",
4641 ]
4642 .join("\n")),
4643 );
4644
4645 // Cut with three selections, one of which is full-line.
4646 cx.set_state(indoc! {"
4647 1«2ˇ»3
4648 4ˇ567
4649 «8ˇ»9"});
4650 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4651 cx.assert_editor_state(indoc! {"
4652 1ˇ3
4653 ˇ9"});
4654
4655 // Paste with three selections, noticing how the copied selection that was full-line
4656 // gets inserted before the second cursor.
4657 cx.set_state(indoc! {"
4658 1ˇ3
4659 9ˇ
4660 «oˇ»ne"});
4661 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4662 cx.assert_editor_state(indoc! {"
4663 12ˇ3
4664 4567
4665 9ˇ
4666 8ˇne"});
4667
4668 // Copy with a single cursor only, which writes the whole line into the clipboard.
4669 cx.set_state(indoc! {"
4670 The quick brown
4671 fox juˇmps over
4672 the lazy dog"});
4673 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4674 assert_eq!(
4675 cx.read_from_clipboard()
4676 .and_then(|item| item.text().as_deref().map(str::to_string)),
4677 Some("fox jumps over\n".to_string())
4678 );
4679
4680 // Paste with three selections, noticing how the copied full-line selection is inserted
4681 // before the empty selections but replaces the selection that is non-empty.
4682 cx.set_state(indoc! {"
4683 Tˇhe quick brown
4684 «foˇ»x jumps over
4685 tˇhe lazy dog"});
4686 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4687 cx.assert_editor_state(indoc! {"
4688 fox jumps over
4689 Tˇhe quick brown
4690 fox jumps over
4691 ˇx jumps over
4692 fox jumps over
4693 tˇhe lazy dog"});
4694}
4695
4696#[gpui::test]
4697async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4698 init_test(cx, |_| {});
4699
4700 let mut cx = EditorTestContext::new(cx).await;
4701 let language = Arc::new(Language::new(
4702 LanguageConfig::default(),
4703 Some(tree_sitter_rust::LANGUAGE.into()),
4704 ));
4705 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4706
4707 // Cut an indented block, without the leading whitespace.
4708 cx.set_state(indoc! {"
4709 const a: B = (
4710 c(),
4711 «d(
4712 e,
4713 f
4714 )ˇ»
4715 );
4716 "});
4717 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4718 cx.assert_editor_state(indoc! {"
4719 const a: B = (
4720 c(),
4721 ˇ
4722 );
4723 "});
4724
4725 // Paste it at the same position.
4726 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4727 cx.assert_editor_state(indoc! {"
4728 const a: B = (
4729 c(),
4730 d(
4731 e,
4732 f
4733 )ˇ
4734 );
4735 "});
4736
4737 // Paste it at a line with a lower indent level.
4738 cx.set_state(indoc! {"
4739 ˇ
4740 const a: B = (
4741 c(),
4742 );
4743 "});
4744 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4745 cx.assert_editor_state(indoc! {"
4746 d(
4747 e,
4748 f
4749 )ˇ
4750 const a: B = (
4751 c(),
4752 );
4753 "});
4754
4755 // Cut an indented block, with the leading whitespace.
4756 cx.set_state(indoc! {"
4757 const a: B = (
4758 c(),
4759 « d(
4760 e,
4761 f
4762 )
4763 ˇ»);
4764 "});
4765 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4766 cx.assert_editor_state(indoc! {"
4767 const a: B = (
4768 c(),
4769 ˇ);
4770 "});
4771
4772 // Paste it at the same position.
4773 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4774 cx.assert_editor_state(indoc! {"
4775 const a: B = (
4776 c(),
4777 d(
4778 e,
4779 f
4780 )
4781 ˇ);
4782 "});
4783
4784 // Paste it at a line with a higher indent level.
4785 cx.set_state(indoc! {"
4786 const a: B = (
4787 c(),
4788 d(
4789 e,
4790 fˇ
4791 )
4792 );
4793 "});
4794 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4795 cx.assert_editor_state(indoc! {"
4796 const a: B = (
4797 c(),
4798 d(
4799 e,
4800 f d(
4801 e,
4802 f
4803 )
4804 ˇ
4805 )
4806 );
4807 "});
4808}
4809
4810#[gpui::test]
4811fn test_select_all(cx: &mut TestAppContext) {
4812 init_test(cx, |_| {});
4813
4814 let editor = cx.add_window(|window, cx| {
4815 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4816 build_editor(buffer, window, cx)
4817 });
4818 _ = editor.update(cx, |editor, window, cx| {
4819 editor.select_all(&SelectAll, window, cx);
4820 assert_eq!(
4821 editor.selections.display_ranges(cx),
4822 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4823 );
4824 });
4825}
4826
4827#[gpui::test]
4828fn test_select_line(cx: &mut TestAppContext) {
4829 init_test(cx, |_| {});
4830
4831 let editor = cx.add_window(|window, cx| {
4832 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4833 build_editor(buffer, window, cx)
4834 });
4835 _ = editor.update(cx, |editor, window, cx| {
4836 editor.change_selections(None, window, cx, |s| {
4837 s.select_display_ranges([
4838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4839 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4840 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4841 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4842 ])
4843 });
4844 editor.select_line(&SelectLine, window, cx);
4845 assert_eq!(
4846 editor.selections.display_ranges(cx),
4847 vec![
4848 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4849 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4850 ]
4851 );
4852 });
4853
4854 _ = editor.update(cx, |editor, window, cx| {
4855 editor.select_line(&SelectLine, window, cx);
4856 assert_eq!(
4857 editor.selections.display_ranges(cx),
4858 vec![
4859 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4860 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4861 ]
4862 );
4863 });
4864
4865 _ = editor.update(cx, |editor, window, cx| {
4866 editor.select_line(&SelectLine, window, cx);
4867 assert_eq!(
4868 editor.selections.display_ranges(cx),
4869 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4870 );
4871 });
4872}
4873
4874#[gpui::test]
4875fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4876 init_test(cx, |_| {});
4877
4878 let editor = cx.add_window(|window, cx| {
4879 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4880 build_editor(buffer, window, cx)
4881 });
4882 _ = editor.update(cx, |editor, window, cx| {
4883 editor.fold_creases(
4884 vec![
4885 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4886 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4887 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4888 ],
4889 true,
4890 window,
4891 cx,
4892 );
4893 editor.change_selections(None, window, cx, |s| {
4894 s.select_display_ranges([
4895 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4896 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4897 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4898 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4899 ])
4900 });
4901 assert_eq!(
4902 editor.display_text(cx),
4903 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4904 );
4905 });
4906
4907 _ = editor.update(cx, |editor, window, cx| {
4908 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
4909 assert_eq!(
4910 editor.display_text(cx),
4911 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4912 );
4913 assert_eq!(
4914 editor.selections.display_ranges(cx),
4915 [
4916 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4917 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4918 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4919 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4920 ]
4921 );
4922 });
4923
4924 _ = editor.update(cx, |editor, window, cx| {
4925 editor.change_selections(None, window, cx, |s| {
4926 s.select_display_ranges([
4927 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4928 ])
4929 });
4930 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
4931 assert_eq!(
4932 editor.display_text(cx),
4933 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4934 );
4935 assert_eq!(
4936 editor.selections.display_ranges(cx),
4937 [
4938 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4939 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4940 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4941 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4942 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4943 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4944 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4945 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4946 ]
4947 );
4948 });
4949}
4950
4951#[gpui::test]
4952async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4953 init_test(cx, |_| {});
4954
4955 let mut cx = EditorTestContext::new(cx).await;
4956
4957 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4958 cx.set_state(indoc!(
4959 r#"abc
4960 defˇghi
4961
4962 jk
4963 nlmo
4964 "#
4965 ));
4966
4967 cx.update_editor(|editor, window, cx| {
4968 editor.add_selection_above(&Default::default(), window, cx);
4969 });
4970
4971 cx.assert_editor_state(indoc!(
4972 r#"abcˇ
4973 defˇghi
4974
4975 jk
4976 nlmo
4977 "#
4978 ));
4979
4980 cx.update_editor(|editor, window, cx| {
4981 editor.add_selection_above(&Default::default(), window, cx);
4982 });
4983
4984 cx.assert_editor_state(indoc!(
4985 r#"abcˇ
4986 defˇghi
4987
4988 jk
4989 nlmo
4990 "#
4991 ));
4992
4993 cx.update_editor(|editor, window, cx| {
4994 editor.add_selection_below(&Default::default(), window, cx);
4995 });
4996
4997 cx.assert_editor_state(indoc!(
4998 r#"abc
4999 defˇghi
5000
5001 jk
5002 nlmo
5003 "#
5004 ));
5005
5006 cx.update_editor(|editor, window, cx| {
5007 editor.undo_selection(&Default::default(), window, cx);
5008 });
5009
5010 cx.assert_editor_state(indoc!(
5011 r#"abcˇ
5012 defˇghi
5013
5014 jk
5015 nlmo
5016 "#
5017 ));
5018
5019 cx.update_editor(|editor, window, cx| {
5020 editor.redo_selection(&Default::default(), window, cx);
5021 });
5022
5023 cx.assert_editor_state(indoc!(
5024 r#"abc
5025 defˇghi
5026
5027 jk
5028 nlmo
5029 "#
5030 ));
5031
5032 cx.update_editor(|editor, window, cx| {
5033 editor.add_selection_below(&Default::default(), window, cx);
5034 });
5035
5036 cx.assert_editor_state(indoc!(
5037 r#"abc
5038 defˇghi
5039
5040 jk
5041 nlmˇo
5042 "#
5043 ));
5044
5045 cx.update_editor(|editor, window, cx| {
5046 editor.add_selection_below(&Default::default(), window, cx);
5047 });
5048
5049 cx.assert_editor_state(indoc!(
5050 r#"abc
5051 defˇghi
5052
5053 jk
5054 nlmˇo
5055 "#
5056 ));
5057
5058 // change selections
5059 cx.set_state(indoc!(
5060 r#"abc
5061 def«ˇg»hi
5062
5063 jk
5064 nlmo
5065 "#
5066 ));
5067
5068 cx.update_editor(|editor, window, cx| {
5069 editor.add_selection_below(&Default::default(), window, cx);
5070 });
5071
5072 cx.assert_editor_state(indoc!(
5073 r#"abc
5074 def«ˇg»hi
5075
5076 jk
5077 nlm«ˇo»
5078 "#
5079 ));
5080
5081 cx.update_editor(|editor, window, cx| {
5082 editor.add_selection_below(&Default::default(), window, cx);
5083 });
5084
5085 cx.assert_editor_state(indoc!(
5086 r#"abc
5087 def«ˇg»hi
5088
5089 jk
5090 nlm«ˇo»
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«ˇg»hi
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«ˇg»hi
5114
5115 jk
5116 nlmo
5117 "#
5118 ));
5119
5120 // Change selections again
5121 cx.set_state(indoc!(
5122 r#"a«bc
5123 defgˇ»hi
5124
5125 jk
5126 nlmo
5127 "#
5128 ));
5129
5130 cx.update_editor(|editor, window, cx| {
5131 editor.add_selection_below(&Default::default(), window, cx);
5132 });
5133
5134 cx.assert_editor_state(indoc!(
5135 r#"a«bcˇ»
5136 d«efgˇ»hi
5137
5138 j«kˇ»
5139 nlmo
5140 "#
5141 ));
5142
5143 cx.update_editor(|editor, window, cx| {
5144 editor.add_selection_below(&Default::default(), window, cx);
5145 });
5146 cx.assert_editor_state(indoc!(
5147 r#"a«bcˇ»
5148 d«efgˇ»hi
5149
5150 j«kˇ»
5151 n«lmoˇ»
5152 "#
5153 ));
5154 cx.update_editor(|editor, window, cx| {
5155 editor.add_selection_above(&Default::default(), window, cx);
5156 });
5157
5158 cx.assert_editor_state(indoc!(
5159 r#"a«bcˇ»
5160 d«efgˇ»hi
5161
5162 j«kˇ»
5163 nlmo
5164 "#
5165 ));
5166
5167 // Change selections again
5168 cx.set_state(indoc!(
5169 r#"abc
5170 d«ˇefghi
5171
5172 jk
5173 nlm»o
5174 "#
5175 ));
5176
5177 cx.update_editor(|editor, window, cx| {
5178 editor.add_selection_above(&Default::default(), window, cx);
5179 });
5180
5181 cx.assert_editor_state(indoc!(
5182 r#"a«ˇbc»
5183 d«ˇef»ghi
5184
5185 j«ˇk»
5186 n«ˇlm»o
5187 "#
5188 ));
5189
5190 cx.update_editor(|editor, window, cx| {
5191 editor.add_selection_below(&Default::default(), window, cx);
5192 });
5193
5194 cx.assert_editor_state(indoc!(
5195 r#"abc
5196 d«ˇef»ghi
5197
5198 j«ˇk»
5199 n«ˇlm»o
5200 "#
5201 ));
5202}
5203
5204#[gpui::test]
5205async fn test_select_next(cx: &mut gpui::TestAppContext) {
5206 init_test(cx, |_| {});
5207
5208 let mut cx = EditorTestContext::new(cx).await;
5209 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5210
5211 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5212 .unwrap();
5213 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5214
5215 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5216 .unwrap();
5217 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5218
5219 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5220 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5221
5222 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5223 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5224
5225 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5226 .unwrap();
5227 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5228
5229 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5230 .unwrap();
5231 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5232}
5233
5234#[gpui::test]
5235async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5236 init_test(cx, |_| {});
5237
5238 let mut cx = EditorTestContext::new(cx).await;
5239
5240 // Test caret-only selections
5241 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5242
5243 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5244 .unwrap();
5245 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5246
5247 // Test left-to-right selections
5248 cx.set_state("abc\n«abcˇ»\nabc");
5249
5250 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5251 .unwrap();
5252 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5253
5254 // Test right-to-left selections
5255 cx.set_state("abc\n«ˇabc»\nabc");
5256
5257 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5258 .unwrap();
5259 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5260}
5261
5262#[gpui::test]
5263async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5264 init_test(cx, |_| {});
5265
5266 let mut cx = EditorTestContext::new(cx).await;
5267 cx.set_state(
5268 r#"let foo = 2;
5269lˇet foo = 2;
5270let fooˇ = 2;
5271let foo = 2;
5272let foo = ˇ2;"#,
5273 );
5274
5275 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5276 .unwrap();
5277 cx.assert_editor_state(
5278 r#"let foo = 2;
5279«letˇ» foo = 2;
5280let «fooˇ» = 2;
5281let foo = 2;
5282let foo = «2ˇ»;"#,
5283 );
5284
5285 // noop for multiple selections with different contents
5286 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5287 .unwrap();
5288 cx.assert_editor_state(
5289 r#"let foo = 2;
5290«letˇ» foo = 2;
5291let «fooˇ» = 2;
5292let foo = 2;
5293let foo = «2ˇ»;"#,
5294 );
5295}
5296
5297#[gpui::test]
5298async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5299 init_test(cx, |_| {});
5300
5301 let mut cx =
5302 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5303
5304 cx.assert_editor_state(indoc! {"
5305 ˇbbb
5306 ccc
5307
5308 bbb
5309 ccc
5310 "});
5311 cx.dispatch_action(SelectPrevious::default());
5312 cx.assert_editor_state(indoc! {"
5313 «bbbˇ»
5314 ccc
5315
5316 bbb
5317 ccc
5318 "});
5319 cx.dispatch_action(SelectPrevious::default());
5320 cx.assert_editor_state(indoc! {"
5321 «bbbˇ»
5322 ccc
5323
5324 «bbbˇ»
5325 ccc
5326 "});
5327}
5328
5329#[gpui::test]
5330async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5331 init_test(cx, |_| {});
5332
5333 let mut cx = EditorTestContext::new(cx).await;
5334 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5335
5336 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5337 .unwrap();
5338 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5339
5340 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5341 .unwrap();
5342 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5343
5344 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5345 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5346
5347 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5348 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5349
5350 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5351 .unwrap();
5352 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5353
5354 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5355 .unwrap();
5356 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5357
5358 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5359 .unwrap();
5360 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5361}
5362
5363#[gpui::test]
5364async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5365 init_test(cx, |_| {});
5366
5367 let mut cx = EditorTestContext::new(cx).await;
5368 cx.set_state(
5369 r#"let foo = 2;
5370lˇet foo = 2;
5371let fooˇ = 2;
5372let foo = 2;
5373let foo = ˇ2;"#,
5374 );
5375
5376 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5377 .unwrap();
5378 cx.assert_editor_state(
5379 r#"let foo = 2;
5380«letˇ» foo = 2;
5381let «fooˇ» = 2;
5382let foo = 2;
5383let foo = «2ˇ»;"#,
5384 );
5385
5386 // noop for multiple selections with different contents
5387 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5388 .unwrap();
5389 cx.assert_editor_state(
5390 r#"let foo = 2;
5391«letˇ» foo = 2;
5392let «fooˇ» = 2;
5393let foo = 2;
5394let foo = «2ˇ»;"#,
5395 );
5396}
5397
5398#[gpui::test]
5399async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5400 init_test(cx, |_| {});
5401
5402 let mut cx = EditorTestContext::new(cx).await;
5403 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5404
5405 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5406 .unwrap();
5407 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5408
5409 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5410 .unwrap();
5411 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5412
5413 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5414 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5415
5416 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5417 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5418
5419 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5420 .unwrap();
5421 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5422
5423 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5424 .unwrap();
5425 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5426}
5427
5428#[gpui::test]
5429async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5430 init_test(cx, |_| {});
5431
5432 let language = Arc::new(Language::new(
5433 LanguageConfig::default(),
5434 Some(tree_sitter_rust::LANGUAGE.into()),
5435 ));
5436
5437 let text = r#"
5438 use mod1::mod2::{mod3, mod4};
5439
5440 fn fn_1(param1: bool, param2: &str) {
5441 let var1 = "text";
5442 }
5443 "#
5444 .unindent();
5445
5446 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5447 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5448 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5449
5450 editor
5451 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5452 .await;
5453
5454 editor.update_in(cx, |editor, window, cx| {
5455 editor.change_selections(None, window, cx, |s| {
5456 s.select_display_ranges([
5457 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5458 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5459 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5460 ]);
5461 });
5462 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5463 });
5464 editor.update(cx, |editor, cx| {
5465 assert_text_with_selections(
5466 editor,
5467 indoc! {r#"
5468 use mod1::mod2::{mod3, «mod4ˇ»};
5469
5470 fn fn_1«ˇ(param1: bool, param2: &str)» {
5471 let var1 = "«textˇ»";
5472 }
5473 "#},
5474 cx,
5475 );
5476 });
5477
5478 editor.update_in(cx, |editor, window, cx| {
5479 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5480 });
5481 editor.update(cx, |editor, cx| {
5482 assert_text_with_selections(
5483 editor,
5484 indoc! {r#"
5485 use mod1::mod2::«{mod3, mod4}ˇ»;
5486
5487 «ˇfn fn_1(param1: bool, param2: &str) {
5488 let var1 = "text";
5489 }»
5490 "#},
5491 cx,
5492 );
5493 });
5494
5495 editor.update_in(cx, |editor, window, cx| {
5496 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5497 });
5498 assert_eq!(
5499 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5500 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5501 );
5502
5503 // Trying to expand the selected syntax node one more time has no effect.
5504 editor.update_in(cx, |editor, window, cx| {
5505 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5506 });
5507 assert_eq!(
5508 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5509 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5510 );
5511
5512 editor.update_in(cx, |editor, window, cx| {
5513 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5514 });
5515 editor.update(cx, |editor, cx| {
5516 assert_text_with_selections(
5517 editor,
5518 indoc! {r#"
5519 use mod1::mod2::«{mod3, mod4}ˇ»;
5520
5521 «ˇfn fn_1(param1: bool, param2: &str) {
5522 let var1 = "text";
5523 }»
5524 "#},
5525 cx,
5526 );
5527 });
5528
5529 editor.update_in(cx, |editor, window, cx| {
5530 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5531 });
5532 editor.update(cx, |editor, cx| {
5533 assert_text_with_selections(
5534 editor,
5535 indoc! {r#"
5536 use mod1::mod2::{mod3, «mod4ˇ»};
5537
5538 fn fn_1«ˇ(param1: bool, param2: &str)» {
5539 let var1 = "«textˇ»";
5540 }
5541 "#},
5542 cx,
5543 );
5544 });
5545
5546 editor.update_in(cx, |editor, window, cx| {
5547 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5548 });
5549 editor.update(cx, |editor, cx| {
5550 assert_text_with_selections(
5551 editor,
5552 indoc! {r#"
5553 use mod1::mod2::{mod3, mo«ˇ»d4};
5554
5555 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5556 let var1 = "te«ˇ»xt";
5557 }
5558 "#},
5559 cx,
5560 );
5561 });
5562
5563 // Trying to shrink the selected syntax node one more time has no effect.
5564 editor.update_in(cx, |editor, window, cx| {
5565 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5566 });
5567 editor.update_in(cx, |editor, _, cx| {
5568 assert_text_with_selections(
5569 editor,
5570 indoc! {r#"
5571 use mod1::mod2::{mod3, mo«ˇ»d4};
5572
5573 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5574 let var1 = "te«ˇ»xt";
5575 }
5576 "#},
5577 cx,
5578 );
5579 });
5580
5581 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5582 // a fold.
5583 editor.update_in(cx, |editor, window, cx| {
5584 editor.fold_creases(
5585 vec![
5586 Crease::simple(
5587 Point::new(0, 21)..Point::new(0, 24),
5588 FoldPlaceholder::test(),
5589 ),
5590 Crease::simple(
5591 Point::new(3, 20)..Point::new(3, 22),
5592 FoldPlaceholder::test(),
5593 ),
5594 ],
5595 true,
5596 window,
5597 cx,
5598 );
5599 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5600 });
5601 editor.update(cx, |editor, cx| {
5602 assert_text_with_selections(
5603 editor,
5604 indoc! {r#"
5605 use mod1::mod2::«{mod3, mod4}ˇ»;
5606
5607 fn fn_1«ˇ(param1: bool, param2: &str)» {
5608 «let var1 = "text";ˇ»
5609 }
5610 "#},
5611 cx,
5612 );
5613 });
5614}
5615
5616#[gpui::test]
5617async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
5618 init_test(cx, |_| {});
5619
5620 let base_text = r#"
5621 impl A {
5622 // this is an unstaged comment
5623
5624 fn b() {
5625 c();
5626 }
5627
5628 // this is another unstaged comment
5629
5630 fn d() {
5631 // e
5632 // f
5633 }
5634 }
5635
5636 fn g() {
5637 // h
5638 }
5639 "#
5640 .unindent();
5641
5642 let text = r#"
5643 ˇimpl A {
5644
5645 fn b() {
5646 c();
5647 }
5648
5649 fn d() {
5650 // e
5651 // f
5652 }
5653 }
5654
5655 fn g() {
5656 // h
5657 }
5658 "#
5659 .unindent();
5660
5661 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5662 cx.set_state(&text);
5663 cx.set_diff_base(&base_text);
5664 cx.update_editor(|editor, window, cx| {
5665 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5666 });
5667
5668 cx.assert_state_with_diff(
5669 "
5670 ˇimpl A {
5671 - // this is an unstaged comment
5672
5673 fn b() {
5674 c();
5675 }
5676
5677 - // this is another unstaged comment
5678 -
5679 fn d() {
5680 // e
5681 // f
5682 }
5683 }
5684
5685 fn g() {
5686 // h
5687 }
5688 "
5689 .unindent(),
5690 );
5691
5692 let expected_display_text = "
5693 impl A {
5694 // this is an unstaged comment
5695
5696 fn b() {
5697 ⋯
5698 }
5699
5700 // this is another unstaged comment
5701
5702 fn d() {
5703 ⋯
5704 }
5705 }
5706
5707 fn g() {
5708 ⋯
5709 }
5710 "
5711 .unindent();
5712
5713 cx.update_editor(|editor, window, cx| {
5714 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5715 assert_eq!(editor.display_text(cx), expected_display_text);
5716 });
5717}
5718
5719#[gpui::test]
5720async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5721 init_test(cx, |_| {});
5722
5723 let language = Arc::new(
5724 Language::new(
5725 LanguageConfig {
5726 brackets: BracketPairConfig {
5727 pairs: vec![
5728 BracketPair {
5729 start: "{".to_string(),
5730 end: "}".to_string(),
5731 close: false,
5732 surround: false,
5733 newline: true,
5734 },
5735 BracketPair {
5736 start: "(".to_string(),
5737 end: ")".to_string(),
5738 close: false,
5739 surround: false,
5740 newline: true,
5741 },
5742 ],
5743 ..Default::default()
5744 },
5745 ..Default::default()
5746 },
5747 Some(tree_sitter_rust::LANGUAGE.into()),
5748 )
5749 .with_indents_query(
5750 r#"
5751 (_ "(" ")" @end) @indent
5752 (_ "{" "}" @end) @indent
5753 "#,
5754 )
5755 .unwrap(),
5756 );
5757
5758 let text = "fn a() {}";
5759
5760 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5761 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5762 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5763 editor
5764 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5765 .await;
5766
5767 editor.update_in(cx, |editor, window, cx| {
5768 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5769 editor.newline(&Newline, window, cx);
5770 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5771 assert_eq!(
5772 editor.selections.ranges(cx),
5773 &[
5774 Point::new(1, 4)..Point::new(1, 4),
5775 Point::new(3, 4)..Point::new(3, 4),
5776 Point::new(5, 0)..Point::new(5, 0)
5777 ]
5778 );
5779 });
5780}
5781
5782#[gpui::test]
5783async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5784 init_test(cx, |_| {});
5785
5786 {
5787 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5788 cx.set_state(indoc! {"
5789 impl A {
5790
5791 fn b() {}
5792
5793 «fn c() {
5794
5795 }ˇ»
5796 }
5797 "});
5798
5799 cx.update_editor(|editor, window, cx| {
5800 editor.autoindent(&Default::default(), window, cx);
5801 });
5802
5803 cx.assert_editor_state(indoc! {"
5804 impl A {
5805
5806 fn b() {}
5807
5808 «fn c() {
5809
5810 }ˇ»
5811 }
5812 "});
5813 }
5814
5815 {
5816 let mut cx = EditorTestContext::new_multibuffer(
5817 cx,
5818 [indoc! { "
5819 impl A {
5820 «
5821 // a
5822 fn b(){}
5823 »
5824 «
5825 }
5826 fn c(){}
5827 »
5828 "}],
5829 );
5830
5831 let buffer = cx.update_editor(|editor, _, cx| {
5832 let buffer = editor.buffer().update(cx, |buffer, _| {
5833 buffer.all_buffers().iter().next().unwrap().clone()
5834 });
5835 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5836 buffer
5837 });
5838
5839 cx.run_until_parked();
5840 cx.update_editor(|editor, window, cx| {
5841 editor.select_all(&Default::default(), window, cx);
5842 editor.autoindent(&Default::default(), window, cx)
5843 });
5844 cx.run_until_parked();
5845
5846 cx.update(|_, cx| {
5847 pretty_assertions::assert_eq!(
5848 buffer.read(cx).text(),
5849 indoc! { "
5850 impl A {
5851
5852 // a
5853 fn b(){}
5854
5855
5856 }
5857 fn c(){}
5858
5859 " }
5860 )
5861 });
5862 }
5863}
5864
5865#[gpui::test]
5866async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5867 init_test(cx, |_| {});
5868
5869 let mut cx = EditorTestContext::new(cx).await;
5870
5871 let language = Arc::new(Language::new(
5872 LanguageConfig {
5873 brackets: BracketPairConfig {
5874 pairs: vec![
5875 BracketPair {
5876 start: "{".to_string(),
5877 end: "}".to_string(),
5878 close: true,
5879 surround: true,
5880 newline: true,
5881 },
5882 BracketPair {
5883 start: "(".to_string(),
5884 end: ")".to_string(),
5885 close: true,
5886 surround: true,
5887 newline: true,
5888 },
5889 BracketPair {
5890 start: "/*".to_string(),
5891 end: " */".to_string(),
5892 close: true,
5893 surround: true,
5894 newline: true,
5895 },
5896 BracketPair {
5897 start: "[".to_string(),
5898 end: "]".to_string(),
5899 close: false,
5900 surround: false,
5901 newline: true,
5902 },
5903 BracketPair {
5904 start: "\"".to_string(),
5905 end: "\"".to_string(),
5906 close: true,
5907 surround: true,
5908 newline: false,
5909 },
5910 BracketPair {
5911 start: "<".to_string(),
5912 end: ">".to_string(),
5913 close: false,
5914 surround: true,
5915 newline: true,
5916 },
5917 ],
5918 ..Default::default()
5919 },
5920 autoclose_before: "})]".to_string(),
5921 ..Default::default()
5922 },
5923 Some(tree_sitter_rust::LANGUAGE.into()),
5924 ));
5925
5926 cx.language_registry().add(language.clone());
5927 cx.update_buffer(|buffer, cx| {
5928 buffer.set_language(Some(language), cx);
5929 });
5930
5931 cx.set_state(
5932 &r#"
5933 🏀ˇ
5934 εˇ
5935 ❤️ˇ
5936 "#
5937 .unindent(),
5938 );
5939
5940 // autoclose multiple nested brackets at multiple cursors
5941 cx.update_editor(|editor, window, cx| {
5942 editor.handle_input("{", window, cx);
5943 editor.handle_input("{", window, cx);
5944 editor.handle_input("{", window, cx);
5945 });
5946 cx.assert_editor_state(
5947 &"
5948 🏀{{{ˇ}}}
5949 ε{{{ˇ}}}
5950 ❤️{{{ˇ}}}
5951 "
5952 .unindent(),
5953 );
5954
5955 // insert a different closing bracket
5956 cx.update_editor(|editor, window, cx| {
5957 editor.handle_input(")", window, cx);
5958 });
5959 cx.assert_editor_state(
5960 &"
5961 🏀{{{)ˇ}}}
5962 ε{{{)ˇ}}}
5963 ❤️{{{)ˇ}}}
5964 "
5965 .unindent(),
5966 );
5967
5968 // skip over the auto-closed brackets when typing a closing bracket
5969 cx.update_editor(|editor, window, cx| {
5970 editor.move_right(&MoveRight, window, cx);
5971 editor.handle_input("}", window, cx);
5972 editor.handle_input("}", window, cx);
5973 editor.handle_input("}", window, cx);
5974 });
5975 cx.assert_editor_state(
5976 &"
5977 🏀{{{)}}}}ˇ
5978 ε{{{)}}}}ˇ
5979 ❤️{{{)}}}}ˇ
5980 "
5981 .unindent(),
5982 );
5983
5984 // autoclose multi-character pairs
5985 cx.set_state(
5986 &"
5987 ˇ
5988 ˇ
5989 "
5990 .unindent(),
5991 );
5992 cx.update_editor(|editor, window, cx| {
5993 editor.handle_input("/", window, cx);
5994 editor.handle_input("*", window, cx);
5995 });
5996 cx.assert_editor_state(
5997 &"
5998 /*ˇ */
5999 /*ˇ */
6000 "
6001 .unindent(),
6002 );
6003
6004 // one cursor autocloses a multi-character pair, one cursor
6005 // does not autoclose.
6006 cx.set_state(
6007 &"
6008 /ˇ
6009 ˇ
6010 "
6011 .unindent(),
6012 );
6013 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6014 cx.assert_editor_state(
6015 &"
6016 /*ˇ */
6017 *ˇ
6018 "
6019 .unindent(),
6020 );
6021
6022 // Don't autoclose if the next character isn't whitespace and isn't
6023 // listed in the language's "autoclose_before" section.
6024 cx.set_state("ˇa b");
6025 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6026 cx.assert_editor_state("{ˇa b");
6027
6028 // Don't autoclose if `close` is false for the bracket pair
6029 cx.set_state("ˇ");
6030 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6031 cx.assert_editor_state("[ˇ");
6032
6033 // Surround with brackets if text is selected
6034 cx.set_state("«aˇ» b");
6035 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6036 cx.assert_editor_state("{«aˇ»} b");
6037
6038 // Autclose pair where the start and end characters are the same
6039 cx.set_state("aˇ");
6040 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6041 cx.assert_editor_state("a\"ˇ\"");
6042 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6043 cx.assert_editor_state("a\"\"ˇ");
6044
6045 // Don't autoclose pair if autoclose is disabled
6046 cx.set_state("ˇ");
6047 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6048 cx.assert_editor_state("<ˇ");
6049
6050 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6051 cx.set_state("«aˇ» b");
6052 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6053 cx.assert_editor_state("<«aˇ»> b");
6054}
6055
6056#[gpui::test]
6057async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
6058 init_test(cx, |settings| {
6059 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6060 });
6061
6062 let mut cx = EditorTestContext::new(cx).await;
6063
6064 let language = Arc::new(Language::new(
6065 LanguageConfig {
6066 brackets: BracketPairConfig {
6067 pairs: vec![
6068 BracketPair {
6069 start: "{".to_string(),
6070 end: "}".to_string(),
6071 close: true,
6072 surround: true,
6073 newline: true,
6074 },
6075 BracketPair {
6076 start: "(".to_string(),
6077 end: ")".to_string(),
6078 close: true,
6079 surround: true,
6080 newline: true,
6081 },
6082 BracketPair {
6083 start: "[".to_string(),
6084 end: "]".to_string(),
6085 close: false,
6086 surround: false,
6087 newline: true,
6088 },
6089 ],
6090 ..Default::default()
6091 },
6092 autoclose_before: "})]".to_string(),
6093 ..Default::default()
6094 },
6095 Some(tree_sitter_rust::LANGUAGE.into()),
6096 ));
6097
6098 cx.language_registry().add(language.clone());
6099 cx.update_buffer(|buffer, cx| {
6100 buffer.set_language(Some(language), cx);
6101 });
6102
6103 cx.set_state(
6104 &"
6105 ˇ
6106 ˇ
6107 ˇ
6108 "
6109 .unindent(),
6110 );
6111
6112 // ensure only matching closing brackets are skipped over
6113 cx.update_editor(|editor, window, cx| {
6114 editor.handle_input("}", window, cx);
6115 editor.move_left(&MoveLeft, window, cx);
6116 editor.handle_input(")", window, cx);
6117 editor.move_left(&MoveLeft, window, cx);
6118 });
6119 cx.assert_editor_state(
6120 &"
6121 ˇ)}
6122 ˇ)}
6123 ˇ)}
6124 "
6125 .unindent(),
6126 );
6127
6128 // skip-over closing brackets at multiple cursors
6129 cx.update_editor(|editor, window, cx| {
6130 editor.handle_input(")", window, cx);
6131 editor.handle_input("}", window, cx);
6132 });
6133 cx.assert_editor_state(
6134 &"
6135 )}ˇ
6136 )}ˇ
6137 )}ˇ
6138 "
6139 .unindent(),
6140 );
6141
6142 // ignore non-close brackets
6143 cx.update_editor(|editor, window, cx| {
6144 editor.handle_input("]", window, cx);
6145 editor.move_left(&MoveLeft, window, cx);
6146 editor.handle_input("]", window, cx);
6147 });
6148 cx.assert_editor_state(
6149 &"
6150 )}]ˇ]
6151 )}]ˇ]
6152 )}]ˇ]
6153 "
6154 .unindent(),
6155 );
6156}
6157
6158#[gpui::test]
6159async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
6160 init_test(cx, |_| {});
6161
6162 let mut cx = EditorTestContext::new(cx).await;
6163
6164 let html_language = Arc::new(
6165 Language::new(
6166 LanguageConfig {
6167 name: "HTML".into(),
6168 brackets: BracketPairConfig {
6169 pairs: vec![
6170 BracketPair {
6171 start: "<".into(),
6172 end: ">".into(),
6173 close: true,
6174 ..Default::default()
6175 },
6176 BracketPair {
6177 start: "{".into(),
6178 end: "}".into(),
6179 close: true,
6180 ..Default::default()
6181 },
6182 BracketPair {
6183 start: "(".into(),
6184 end: ")".into(),
6185 close: true,
6186 ..Default::default()
6187 },
6188 ],
6189 ..Default::default()
6190 },
6191 autoclose_before: "})]>".into(),
6192 ..Default::default()
6193 },
6194 Some(tree_sitter_html::language()),
6195 )
6196 .with_injection_query(
6197 r#"
6198 (script_element
6199 (raw_text) @injection.content
6200 (#set! injection.language "javascript"))
6201 "#,
6202 )
6203 .unwrap(),
6204 );
6205
6206 let javascript_language = Arc::new(Language::new(
6207 LanguageConfig {
6208 name: "JavaScript".into(),
6209 brackets: BracketPairConfig {
6210 pairs: vec![
6211 BracketPair {
6212 start: "/*".into(),
6213 end: " */".into(),
6214 close: true,
6215 ..Default::default()
6216 },
6217 BracketPair {
6218 start: "{".into(),
6219 end: "}".into(),
6220 close: true,
6221 ..Default::default()
6222 },
6223 BracketPair {
6224 start: "(".into(),
6225 end: ")".into(),
6226 close: true,
6227 ..Default::default()
6228 },
6229 ],
6230 ..Default::default()
6231 },
6232 autoclose_before: "})]>".into(),
6233 ..Default::default()
6234 },
6235 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6236 ));
6237
6238 cx.language_registry().add(html_language.clone());
6239 cx.language_registry().add(javascript_language.clone());
6240
6241 cx.update_buffer(|buffer, cx| {
6242 buffer.set_language(Some(html_language), cx);
6243 });
6244
6245 cx.set_state(
6246 &r#"
6247 <body>ˇ
6248 <script>
6249 var x = 1;ˇ
6250 </script>
6251 </body>ˇ
6252 "#
6253 .unindent(),
6254 );
6255
6256 // Precondition: different languages are active at different locations.
6257 cx.update_editor(|editor, window, cx| {
6258 let snapshot = editor.snapshot(window, cx);
6259 let cursors = editor.selections.ranges::<usize>(cx);
6260 let languages = cursors
6261 .iter()
6262 .map(|c| snapshot.language_at(c.start).unwrap().name())
6263 .collect::<Vec<_>>();
6264 assert_eq!(
6265 languages,
6266 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6267 );
6268 });
6269
6270 // Angle brackets autoclose in HTML, but not JavaScript.
6271 cx.update_editor(|editor, window, cx| {
6272 editor.handle_input("<", window, cx);
6273 editor.handle_input("a", window, cx);
6274 });
6275 cx.assert_editor_state(
6276 &r#"
6277 <body><aˇ>
6278 <script>
6279 var x = 1;<aˇ
6280 </script>
6281 </body><aˇ>
6282 "#
6283 .unindent(),
6284 );
6285
6286 // Curly braces and parens autoclose in both HTML and JavaScript.
6287 cx.update_editor(|editor, window, cx| {
6288 editor.handle_input(" b=", window, cx);
6289 editor.handle_input("{", window, cx);
6290 editor.handle_input("c", window, cx);
6291 editor.handle_input("(", window, cx);
6292 });
6293 cx.assert_editor_state(
6294 &r#"
6295 <body><a b={c(ˇ)}>
6296 <script>
6297 var x = 1;<a b={c(ˇ)}
6298 </script>
6299 </body><a b={c(ˇ)}>
6300 "#
6301 .unindent(),
6302 );
6303
6304 // Brackets that were already autoclosed are skipped.
6305 cx.update_editor(|editor, window, cx| {
6306 editor.handle_input(")", window, cx);
6307 editor.handle_input("d", window, cx);
6308 editor.handle_input("}", window, cx);
6309 });
6310 cx.assert_editor_state(
6311 &r#"
6312 <body><a b={c()d}ˇ>
6313 <script>
6314 var x = 1;<a b={c()d}ˇ
6315 </script>
6316 </body><a b={c()d}ˇ>
6317 "#
6318 .unindent(),
6319 );
6320 cx.update_editor(|editor, window, cx| {
6321 editor.handle_input(">", window, cx);
6322 });
6323 cx.assert_editor_state(
6324 &r#"
6325 <body><a b={c()d}>ˇ
6326 <script>
6327 var x = 1;<a b={c()d}>ˇ
6328 </script>
6329 </body><a b={c()d}>ˇ
6330 "#
6331 .unindent(),
6332 );
6333
6334 // Reset
6335 cx.set_state(
6336 &r#"
6337 <body>ˇ
6338 <script>
6339 var x = 1;ˇ
6340 </script>
6341 </body>ˇ
6342 "#
6343 .unindent(),
6344 );
6345
6346 cx.update_editor(|editor, window, cx| {
6347 editor.handle_input("<", window, cx);
6348 });
6349 cx.assert_editor_state(
6350 &r#"
6351 <body><ˇ>
6352 <script>
6353 var x = 1;<ˇ
6354 </script>
6355 </body><ˇ>
6356 "#
6357 .unindent(),
6358 );
6359
6360 // When backspacing, the closing angle brackets are removed.
6361 cx.update_editor(|editor, window, cx| {
6362 editor.backspace(&Backspace, window, cx);
6363 });
6364 cx.assert_editor_state(
6365 &r#"
6366 <body>ˇ
6367 <script>
6368 var x = 1;ˇ
6369 </script>
6370 </body>ˇ
6371 "#
6372 .unindent(),
6373 );
6374
6375 // Block comments autoclose in JavaScript, but not HTML.
6376 cx.update_editor(|editor, window, cx| {
6377 editor.handle_input("/", window, cx);
6378 editor.handle_input("*", window, cx);
6379 });
6380 cx.assert_editor_state(
6381 &r#"
6382 <body>/*ˇ
6383 <script>
6384 var x = 1;/*ˇ */
6385 </script>
6386 </body>/*ˇ
6387 "#
6388 .unindent(),
6389 );
6390}
6391
6392#[gpui::test]
6393async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6394 init_test(cx, |_| {});
6395
6396 let mut cx = EditorTestContext::new(cx).await;
6397
6398 let rust_language = Arc::new(
6399 Language::new(
6400 LanguageConfig {
6401 name: "Rust".into(),
6402 brackets: serde_json::from_value(json!([
6403 { "start": "{", "end": "}", "close": true, "newline": true },
6404 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6405 ]))
6406 .unwrap(),
6407 autoclose_before: "})]>".into(),
6408 ..Default::default()
6409 },
6410 Some(tree_sitter_rust::LANGUAGE.into()),
6411 )
6412 .with_override_query("(string_literal) @string")
6413 .unwrap(),
6414 );
6415
6416 cx.language_registry().add(rust_language.clone());
6417 cx.update_buffer(|buffer, cx| {
6418 buffer.set_language(Some(rust_language), cx);
6419 });
6420
6421 cx.set_state(
6422 &r#"
6423 let x = ˇ
6424 "#
6425 .unindent(),
6426 );
6427
6428 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6429 cx.update_editor(|editor, window, cx| {
6430 editor.handle_input("\"", window, cx);
6431 });
6432 cx.assert_editor_state(
6433 &r#"
6434 let x = "ˇ"
6435 "#
6436 .unindent(),
6437 );
6438
6439 // Inserting another quotation mark. The cursor moves across the existing
6440 // automatically-inserted quotation mark.
6441 cx.update_editor(|editor, window, cx| {
6442 editor.handle_input("\"", window, cx);
6443 });
6444 cx.assert_editor_state(
6445 &r#"
6446 let x = ""ˇ
6447 "#
6448 .unindent(),
6449 );
6450
6451 // Reset
6452 cx.set_state(
6453 &r#"
6454 let x = ˇ
6455 "#
6456 .unindent(),
6457 );
6458
6459 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6460 cx.update_editor(|editor, window, cx| {
6461 editor.handle_input("\"", window, cx);
6462 editor.handle_input(" ", window, cx);
6463 editor.move_left(&Default::default(), window, cx);
6464 editor.handle_input("\\", window, cx);
6465 editor.handle_input("\"", window, cx);
6466 });
6467 cx.assert_editor_state(
6468 &r#"
6469 let x = "\"ˇ "
6470 "#
6471 .unindent(),
6472 );
6473
6474 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6475 // mark. Nothing is inserted.
6476 cx.update_editor(|editor, window, cx| {
6477 editor.move_right(&Default::default(), window, cx);
6478 editor.handle_input("\"", window, cx);
6479 });
6480 cx.assert_editor_state(
6481 &r#"
6482 let x = "\" "ˇ
6483 "#
6484 .unindent(),
6485 );
6486}
6487
6488#[gpui::test]
6489async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6490 init_test(cx, |_| {});
6491
6492 let language = Arc::new(Language::new(
6493 LanguageConfig {
6494 brackets: BracketPairConfig {
6495 pairs: vec![
6496 BracketPair {
6497 start: "{".to_string(),
6498 end: "}".to_string(),
6499 close: true,
6500 surround: true,
6501 newline: true,
6502 },
6503 BracketPair {
6504 start: "/* ".to_string(),
6505 end: "*/".to_string(),
6506 close: true,
6507 surround: true,
6508 ..Default::default()
6509 },
6510 ],
6511 ..Default::default()
6512 },
6513 ..Default::default()
6514 },
6515 Some(tree_sitter_rust::LANGUAGE.into()),
6516 ));
6517
6518 let text = r#"
6519 a
6520 b
6521 c
6522 "#
6523 .unindent();
6524
6525 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6526 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6527 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6528 editor
6529 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6530 .await;
6531
6532 editor.update_in(cx, |editor, window, cx| {
6533 editor.change_selections(None, window, cx, |s| {
6534 s.select_display_ranges([
6535 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6536 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6537 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6538 ])
6539 });
6540
6541 editor.handle_input("{", window, cx);
6542 editor.handle_input("{", window, cx);
6543 editor.handle_input("{", window, cx);
6544 assert_eq!(
6545 editor.text(cx),
6546 "
6547 {{{a}}}
6548 {{{b}}}
6549 {{{c}}}
6550 "
6551 .unindent()
6552 );
6553 assert_eq!(
6554 editor.selections.display_ranges(cx),
6555 [
6556 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6557 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6558 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6559 ]
6560 );
6561
6562 editor.undo(&Undo, window, cx);
6563 editor.undo(&Undo, window, cx);
6564 editor.undo(&Undo, window, cx);
6565 assert_eq!(
6566 editor.text(cx),
6567 "
6568 a
6569 b
6570 c
6571 "
6572 .unindent()
6573 );
6574 assert_eq!(
6575 editor.selections.display_ranges(cx),
6576 [
6577 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6578 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6579 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6580 ]
6581 );
6582
6583 // Ensure inserting the first character of a multi-byte bracket pair
6584 // doesn't surround the selections with the bracket.
6585 editor.handle_input("/", window, cx);
6586 assert_eq!(
6587 editor.text(cx),
6588 "
6589 /
6590 /
6591 /
6592 "
6593 .unindent()
6594 );
6595 assert_eq!(
6596 editor.selections.display_ranges(cx),
6597 [
6598 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6599 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6600 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6601 ]
6602 );
6603
6604 editor.undo(&Undo, window, cx);
6605 assert_eq!(
6606 editor.text(cx),
6607 "
6608 a
6609 b
6610 c
6611 "
6612 .unindent()
6613 );
6614 assert_eq!(
6615 editor.selections.display_ranges(cx),
6616 [
6617 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6618 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6619 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6620 ]
6621 );
6622
6623 // Ensure inserting the last character of a multi-byte bracket pair
6624 // doesn't surround the selections with the bracket.
6625 editor.handle_input("*", window, cx);
6626 assert_eq!(
6627 editor.text(cx),
6628 "
6629 *
6630 *
6631 *
6632 "
6633 .unindent()
6634 );
6635 assert_eq!(
6636 editor.selections.display_ranges(cx),
6637 [
6638 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6639 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6640 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6641 ]
6642 );
6643 });
6644}
6645
6646#[gpui::test]
6647async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6648 init_test(cx, |_| {});
6649
6650 let language = Arc::new(Language::new(
6651 LanguageConfig {
6652 brackets: BracketPairConfig {
6653 pairs: vec![BracketPair {
6654 start: "{".to_string(),
6655 end: "}".to_string(),
6656 close: true,
6657 surround: true,
6658 newline: true,
6659 }],
6660 ..Default::default()
6661 },
6662 autoclose_before: "}".to_string(),
6663 ..Default::default()
6664 },
6665 Some(tree_sitter_rust::LANGUAGE.into()),
6666 ));
6667
6668 let text = r#"
6669 a
6670 b
6671 c
6672 "#
6673 .unindent();
6674
6675 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6677 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6678 editor
6679 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6680 .await;
6681
6682 editor.update_in(cx, |editor, window, cx| {
6683 editor.change_selections(None, window, cx, |s| {
6684 s.select_ranges([
6685 Point::new(0, 1)..Point::new(0, 1),
6686 Point::new(1, 1)..Point::new(1, 1),
6687 Point::new(2, 1)..Point::new(2, 1),
6688 ])
6689 });
6690
6691 editor.handle_input("{", window, cx);
6692 editor.handle_input("{", window, cx);
6693 editor.handle_input("_", window, cx);
6694 assert_eq!(
6695 editor.text(cx),
6696 "
6697 a{{_}}
6698 b{{_}}
6699 c{{_}}
6700 "
6701 .unindent()
6702 );
6703 assert_eq!(
6704 editor.selections.ranges::<Point>(cx),
6705 [
6706 Point::new(0, 4)..Point::new(0, 4),
6707 Point::new(1, 4)..Point::new(1, 4),
6708 Point::new(2, 4)..Point::new(2, 4)
6709 ]
6710 );
6711
6712 editor.backspace(&Default::default(), window, cx);
6713 editor.backspace(&Default::default(), window, cx);
6714 assert_eq!(
6715 editor.text(cx),
6716 "
6717 a{}
6718 b{}
6719 c{}
6720 "
6721 .unindent()
6722 );
6723 assert_eq!(
6724 editor.selections.ranges::<Point>(cx),
6725 [
6726 Point::new(0, 2)..Point::new(0, 2),
6727 Point::new(1, 2)..Point::new(1, 2),
6728 Point::new(2, 2)..Point::new(2, 2)
6729 ]
6730 );
6731
6732 editor.delete_to_previous_word_start(&Default::default(), window, cx);
6733 assert_eq!(
6734 editor.text(cx),
6735 "
6736 a
6737 b
6738 c
6739 "
6740 .unindent()
6741 );
6742 assert_eq!(
6743 editor.selections.ranges::<Point>(cx),
6744 [
6745 Point::new(0, 1)..Point::new(0, 1),
6746 Point::new(1, 1)..Point::new(1, 1),
6747 Point::new(2, 1)..Point::new(2, 1)
6748 ]
6749 );
6750 });
6751}
6752
6753#[gpui::test]
6754async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6755 init_test(cx, |settings| {
6756 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6757 });
6758
6759 let mut cx = EditorTestContext::new(cx).await;
6760
6761 let language = Arc::new(Language::new(
6762 LanguageConfig {
6763 brackets: BracketPairConfig {
6764 pairs: vec![
6765 BracketPair {
6766 start: "{".to_string(),
6767 end: "}".to_string(),
6768 close: true,
6769 surround: true,
6770 newline: true,
6771 },
6772 BracketPair {
6773 start: "(".to_string(),
6774 end: ")".to_string(),
6775 close: true,
6776 surround: true,
6777 newline: true,
6778 },
6779 BracketPair {
6780 start: "[".to_string(),
6781 end: "]".to_string(),
6782 close: false,
6783 surround: true,
6784 newline: true,
6785 },
6786 ],
6787 ..Default::default()
6788 },
6789 autoclose_before: "})]".to_string(),
6790 ..Default::default()
6791 },
6792 Some(tree_sitter_rust::LANGUAGE.into()),
6793 ));
6794
6795 cx.language_registry().add(language.clone());
6796 cx.update_buffer(|buffer, cx| {
6797 buffer.set_language(Some(language), cx);
6798 });
6799
6800 cx.set_state(
6801 &"
6802 {(ˇ)}
6803 [[ˇ]]
6804 {(ˇ)}
6805 "
6806 .unindent(),
6807 );
6808
6809 cx.update_editor(|editor, window, cx| {
6810 editor.backspace(&Default::default(), window, cx);
6811 editor.backspace(&Default::default(), window, cx);
6812 });
6813
6814 cx.assert_editor_state(
6815 &"
6816 ˇ
6817 ˇ]]
6818 ˇ
6819 "
6820 .unindent(),
6821 );
6822
6823 cx.update_editor(|editor, window, cx| {
6824 editor.handle_input("{", window, cx);
6825 editor.handle_input("{", window, cx);
6826 editor.move_right(&MoveRight, window, cx);
6827 editor.move_right(&MoveRight, window, cx);
6828 editor.move_left(&MoveLeft, window, cx);
6829 editor.move_left(&MoveLeft, window, cx);
6830 editor.backspace(&Default::default(), window, cx);
6831 });
6832
6833 cx.assert_editor_state(
6834 &"
6835 {ˇ}
6836 {ˇ}]]
6837 {ˇ}
6838 "
6839 .unindent(),
6840 );
6841
6842 cx.update_editor(|editor, window, cx| {
6843 editor.backspace(&Default::default(), window, cx);
6844 });
6845
6846 cx.assert_editor_state(
6847 &"
6848 ˇ
6849 ˇ]]
6850 ˇ
6851 "
6852 .unindent(),
6853 );
6854}
6855
6856#[gpui::test]
6857async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6858 init_test(cx, |_| {});
6859
6860 let language = Arc::new(Language::new(
6861 LanguageConfig::default(),
6862 Some(tree_sitter_rust::LANGUAGE.into()),
6863 ));
6864
6865 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
6866 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6867 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6868 editor
6869 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6870 .await;
6871
6872 editor.update_in(cx, |editor, window, cx| {
6873 editor.set_auto_replace_emoji_shortcode(true);
6874
6875 editor.handle_input("Hello ", window, cx);
6876 editor.handle_input(":wave", window, cx);
6877 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6878
6879 editor.handle_input(":", window, cx);
6880 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6881
6882 editor.handle_input(" :smile", window, cx);
6883 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6884
6885 editor.handle_input(":", window, cx);
6886 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6887
6888 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6889 editor.handle_input(":wave", window, cx);
6890 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6891
6892 editor.handle_input(":", window, cx);
6893 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6894
6895 editor.handle_input(":1", window, cx);
6896 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6897
6898 editor.handle_input(":", window, cx);
6899 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6900
6901 // Ensure shortcode does not get replaced when it is part of a word
6902 editor.handle_input(" Test:wave", window, cx);
6903 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6904
6905 editor.handle_input(":", window, cx);
6906 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6907
6908 editor.set_auto_replace_emoji_shortcode(false);
6909
6910 // Ensure shortcode does not get replaced when auto replace is off
6911 editor.handle_input(" :wave", window, cx);
6912 assert_eq!(
6913 editor.text(cx),
6914 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6915 );
6916
6917 editor.handle_input(":", window, cx);
6918 assert_eq!(
6919 editor.text(cx),
6920 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6921 );
6922 });
6923}
6924
6925#[gpui::test]
6926async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6927 init_test(cx, |_| {});
6928
6929 let (text, insertion_ranges) = marked_text_ranges(
6930 indoc! {"
6931 ˇ
6932 "},
6933 false,
6934 );
6935
6936 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6937 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6938
6939 _ = editor.update_in(cx, |editor, window, cx| {
6940 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6941
6942 editor
6943 .insert_snippet(&insertion_ranges, snippet, window, cx)
6944 .unwrap();
6945
6946 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
6947 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6948 assert_eq!(editor.text(cx), expected_text);
6949 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6950 }
6951
6952 assert(
6953 editor,
6954 cx,
6955 indoc! {"
6956 type «» =•
6957 "},
6958 );
6959
6960 assert!(editor.context_menu_visible(), "There should be a matches");
6961 });
6962}
6963
6964#[gpui::test]
6965async fn test_snippets(cx: &mut gpui::TestAppContext) {
6966 init_test(cx, |_| {});
6967
6968 let (text, insertion_ranges) = marked_text_ranges(
6969 indoc! {"
6970 a.ˇ b
6971 a.ˇ b
6972 a.ˇ b
6973 "},
6974 false,
6975 );
6976
6977 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6978 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6979
6980 editor.update_in(cx, |editor, window, cx| {
6981 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6982
6983 editor
6984 .insert_snippet(&insertion_ranges, snippet, window, cx)
6985 .unwrap();
6986
6987 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
6988 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6989 assert_eq!(editor.text(cx), expected_text);
6990 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6991 }
6992
6993 assert(
6994 editor,
6995 cx,
6996 indoc! {"
6997 a.f(«one», two, «three») b
6998 a.f(«one», two, «three») b
6999 a.f(«one», two, «three») b
7000 "},
7001 );
7002
7003 // Can't move earlier than the first tab stop
7004 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7005 assert(
7006 editor,
7007 cx,
7008 indoc! {"
7009 a.f(«one», two, «three») b
7010 a.f(«one», two, «three») b
7011 a.f(«one», two, «three») b
7012 "},
7013 );
7014
7015 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7016 assert(
7017 editor,
7018 cx,
7019 indoc! {"
7020 a.f(one, «two», three) b
7021 a.f(one, «two», three) b
7022 a.f(one, «two», three) b
7023 "},
7024 );
7025
7026 editor.move_to_prev_snippet_tabstop(window, cx);
7027 assert(
7028 editor,
7029 cx,
7030 indoc! {"
7031 a.f(«one», two, «three») b
7032 a.f(«one», two, «three») b
7033 a.f(«one», two, «three») b
7034 "},
7035 );
7036
7037 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7038 assert(
7039 editor,
7040 cx,
7041 indoc! {"
7042 a.f(one, «two», three) b
7043 a.f(one, «two», three) b
7044 a.f(one, «two», three) b
7045 "},
7046 );
7047 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7048 assert(
7049 editor,
7050 cx,
7051 indoc! {"
7052 a.f(one, two, three)ˇ b
7053 a.f(one, two, three)ˇ b
7054 a.f(one, two, three)ˇ b
7055 "},
7056 );
7057
7058 // As soon as the last tab stop is reached, snippet state is gone
7059 editor.move_to_prev_snippet_tabstop(window, cx);
7060 assert(
7061 editor,
7062 cx,
7063 indoc! {"
7064 a.f(one, two, three)ˇ b
7065 a.f(one, two, three)ˇ b
7066 a.f(one, two, three)ˇ b
7067 "},
7068 );
7069 });
7070}
7071
7072#[gpui::test]
7073async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
7074 init_test(cx, |_| {});
7075
7076 let fs = FakeFs::new(cx.executor());
7077 fs.insert_file("/file.rs", Default::default()).await;
7078
7079 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7080
7081 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7082 language_registry.add(rust_lang());
7083 let mut fake_servers = language_registry.register_fake_lsp(
7084 "Rust",
7085 FakeLspAdapter {
7086 capabilities: lsp::ServerCapabilities {
7087 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7088 ..Default::default()
7089 },
7090 ..Default::default()
7091 },
7092 );
7093
7094 let buffer = project
7095 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7096 .await
7097 .unwrap();
7098
7099 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7100 let (editor, cx) = cx.add_window_view(|window, cx| {
7101 build_editor_with_project(project.clone(), buffer, window, cx)
7102 });
7103 editor.update_in(cx, |editor, window, cx| {
7104 editor.set_text("one\ntwo\nthree\n", window, cx)
7105 });
7106 assert!(cx.read(|cx| editor.is_dirty(cx)));
7107
7108 cx.executor().start_waiting();
7109 let fake_server = fake_servers.next().await.unwrap();
7110
7111 let save = editor
7112 .update_in(cx, |editor, window, cx| {
7113 editor.save(true, project.clone(), window, cx)
7114 })
7115 .unwrap();
7116 fake_server
7117 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7118 assert_eq!(
7119 params.text_document.uri,
7120 lsp::Url::from_file_path("/file.rs").unwrap()
7121 );
7122 assert_eq!(params.options.tab_size, 4);
7123 Ok(Some(vec![lsp::TextEdit::new(
7124 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7125 ", ".to_string(),
7126 )]))
7127 })
7128 .next()
7129 .await;
7130 cx.executor().start_waiting();
7131 save.await;
7132
7133 assert_eq!(
7134 editor.update(cx, |editor, cx| editor.text(cx)),
7135 "one, two\nthree\n"
7136 );
7137 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7138
7139 editor.update_in(cx, |editor, window, cx| {
7140 editor.set_text("one\ntwo\nthree\n", window, cx)
7141 });
7142 assert!(cx.read(|cx| editor.is_dirty(cx)));
7143
7144 // Ensure we can still save even if formatting hangs.
7145 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7146 assert_eq!(
7147 params.text_document.uri,
7148 lsp::Url::from_file_path("/file.rs").unwrap()
7149 );
7150 futures::future::pending::<()>().await;
7151 unreachable!()
7152 });
7153 let save = editor
7154 .update_in(cx, |editor, window, cx| {
7155 editor.save(true, project.clone(), window, cx)
7156 })
7157 .unwrap();
7158 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7159 cx.executor().start_waiting();
7160 save.await;
7161 assert_eq!(
7162 editor.update(cx, |editor, cx| editor.text(cx)),
7163 "one\ntwo\nthree\n"
7164 );
7165 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7166
7167 // For non-dirty buffer, no formatting request should be sent
7168 let save = editor
7169 .update_in(cx, |editor, window, cx| {
7170 editor.save(true, project.clone(), window, cx)
7171 })
7172 .unwrap();
7173 let _pending_format_request = fake_server
7174 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7175 panic!("Should not be invoked on non-dirty buffer");
7176 })
7177 .next();
7178 cx.executor().start_waiting();
7179 save.await;
7180
7181 // Set rust language override and assert overridden tabsize is sent to language server
7182 update_test_language_settings(cx, |settings| {
7183 settings.languages.insert(
7184 "Rust".into(),
7185 LanguageSettingsContent {
7186 tab_size: NonZeroU32::new(8),
7187 ..Default::default()
7188 },
7189 );
7190 });
7191
7192 editor.update_in(cx, |editor, window, cx| {
7193 editor.set_text("somehting_new\n", window, cx)
7194 });
7195 assert!(cx.read(|cx| editor.is_dirty(cx)));
7196 let save = editor
7197 .update_in(cx, |editor, window, cx| {
7198 editor.save(true, project.clone(), window, cx)
7199 })
7200 .unwrap();
7201 fake_server
7202 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7203 assert_eq!(
7204 params.text_document.uri,
7205 lsp::Url::from_file_path("/file.rs").unwrap()
7206 );
7207 assert_eq!(params.options.tab_size, 8);
7208 Ok(Some(vec![]))
7209 })
7210 .next()
7211 .await;
7212 cx.executor().start_waiting();
7213 save.await;
7214}
7215
7216#[gpui::test]
7217async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
7218 init_test(cx, |_| {});
7219
7220 let cols = 4;
7221 let rows = 10;
7222 let sample_text_1 = sample_text(rows, cols, 'a');
7223 assert_eq!(
7224 sample_text_1,
7225 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7226 );
7227 let sample_text_2 = sample_text(rows, cols, 'l');
7228 assert_eq!(
7229 sample_text_2,
7230 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7231 );
7232 let sample_text_3 = sample_text(rows, cols, 'v');
7233 assert_eq!(
7234 sample_text_3,
7235 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7236 );
7237
7238 let fs = FakeFs::new(cx.executor());
7239 fs.insert_tree(
7240 "/a",
7241 json!({
7242 "main.rs": sample_text_1,
7243 "other.rs": sample_text_2,
7244 "lib.rs": sample_text_3,
7245 }),
7246 )
7247 .await;
7248
7249 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7250 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7251 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7252
7253 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7254 language_registry.add(rust_lang());
7255 let mut fake_servers = language_registry.register_fake_lsp(
7256 "Rust",
7257 FakeLspAdapter {
7258 capabilities: lsp::ServerCapabilities {
7259 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7260 ..Default::default()
7261 },
7262 ..Default::default()
7263 },
7264 );
7265
7266 let worktree = project.update(cx, |project, cx| {
7267 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7268 assert_eq!(worktrees.len(), 1);
7269 worktrees.pop().unwrap()
7270 });
7271 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7272
7273 let buffer_1 = project
7274 .update(cx, |project, cx| {
7275 project.open_buffer((worktree_id, "main.rs"), cx)
7276 })
7277 .await
7278 .unwrap();
7279 let buffer_2 = project
7280 .update(cx, |project, cx| {
7281 project.open_buffer((worktree_id, "other.rs"), cx)
7282 })
7283 .await
7284 .unwrap();
7285 let buffer_3 = project
7286 .update(cx, |project, cx| {
7287 project.open_buffer((worktree_id, "lib.rs"), cx)
7288 })
7289 .await
7290 .unwrap();
7291
7292 let multi_buffer = cx.new(|cx| {
7293 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7294 multi_buffer.push_excerpts(
7295 buffer_1.clone(),
7296 [
7297 ExcerptRange {
7298 context: Point::new(0, 0)..Point::new(3, 0),
7299 primary: None,
7300 },
7301 ExcerptRange {
7302 context: Point::new(5, 0)..Point::new(7, 0),
7303 primary: None,
7304 },
7305 ExcerptRange {
7306 context: Point::new(9, 0)..Point::new(10, 4),
7307 primary: None,
7308 },
7309 ],
7310 cx,
7311 );
7312 multi_buffer.push_excerpts(
7313 buffer_2.clone(),
7314 [
7315 ExcerptRange {
7316 context: Point::new(0, 0)..Point::new(3, 0),
7317 primary: None,
7318 },
7319 ExcerptRange {
7320 context: Point::new(5, 0)..Point::new(7, 0),
7321 primary: None,
7322 },
7323 ExcerptRange {
7324 context: Point::new(9, 0)..Point::new(10, 4),
7325 primary: None,
7326 },
7327 ],
7328 cx,
7329 );
7330 multi_buffer.push_excerpts(
7331 buffer_3.clone(),
7332 [
7333 ExcerptRange {
7334 context: Point::new(0, 0)..Point::new(3, 0),
7335 primary: None,
7336 },
7337 ExcerptRange {
7338 context: Point::new(5, 0)..Point::new(7, 0),
7339 primary: None,
7340 },
7341 ExcerptRange {
7342 context: Point::new(9, 0)..Point::new(10, 4),
7343 primary: None,
7344 },
7345 ],
7346 cx,
7347 );
7348 multi_buffer
7349 });
7350 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7351 Editor::new(
7352 EditorMode::Full,
7353 multi_buffer,
7354 Some(project.clone()),
7355 true,
7356 window,
7357 cx,
7358 )
7359 });
7360
7361 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7362 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7363 s.select_ranges(Some(1..2))
7364 });
7365 editor.insert("|one|two|three|", window, cx);
7366 });
7367 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7368 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7369 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7370 s.select_ranges(Some(60..70))
7371 });
7372 editor.insert("|four|five|six|", window, cx);
7373 });
7374 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7375
7376 // First two buffers should be edited, but not the third one.
7377 assert_eq!(
7378 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7379 "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}",
7380 );
7381 buffer_1.update(cx, |buffer, _| {
7382 assert!(buffer.is_dirty());
7383 assert_eq!(
7384 buffer.text(),
7385 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7386 )
7387 });
7388 buffer_2.update(cx, |buffer, _| {
7389 assert!(buffer.is_dirty());
7390 assert_eq!(
7391 buffer.text(),
7392 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7393 )
7394 });
7395 buffer_3.update(cx, |buffer, _| {
7396 assert!(!buffer.is_dirty());
7397 assert_eq!(buffer.text(), sample_text_3,)
7398 });
7399 cx.executor().run_until_parked();
7400
7401 cx.executor().start_waiting();
7402 let save = multi_buffer_editor
7403 .update_in(cx, |editor, window, cx| {
7404 editor.save(true, project.clone(), window, cx)
7405 })
7406 .unwrap();
7407
7408 let fake_server = fake_servers.next().await.unwrap();
7409 fake_server
7410 .server
7411 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7412 Ok(Some(vec![lsp::TextEdit::new(
7413 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7414 format!("[{} formatted]", params.text_document.uri),
7415 )]))
7416 })
7417 .detach();
7418 save.await;
7419
7420 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7421 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7422 assert_eq!(
7423 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7424 "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}",
7425 );
7426 buffer_1.update(cx, |buffer, _| {
7427 assert!(!buffer.is_dirty());
7428 assert_eq!(
7429 buffer.text(),
7430 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7431 )
7432 });
7433 buffer_2.update(cx, |buffer, _| {
7434 assert!(!buffer.is_dirty());
7435 assert_eq!(
7436 buffer.text(),
7437 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7438 )
7439 });
7440 buffer_3.update(cx, |buffer, _| {
7441 assert!(!buffer.is_dirty());
7442 assert_eq!(buffer.text(), sample_text_3,)
7443 });
7444}
7445
7446#[gpui::test]
7447async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7448 init_test(cx, |_| {});
7449
7450 let fs = FakeFs::new(cx.executor());
7451 fs.insert_file("/file.rs", Default::default()).await;
7452
7453 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7454
7455 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7456 language_registry.add(rust_lang());
7457 let mut fake_servers = language_registry.register_fake_lsp(
7458 "Rust",
7459 FakeLspAdapter {
7460 capabilities: lsp::ServerCapabilities {
7461 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7462 ..Default::default()
7463 },
7464 ..Default::default()
7465 },
7466 );
7467
7468 let buffer = project
7469 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7470 .await
7471 .unwrap();
7472
7473 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7474 let (editor, cx) = cx.add_window_view(|window, cx| {
7475 build_editor_with_project(project.clone(), buffer, window, cx)
7476 });
7477 editor.update_in(cx, |editor, window, cx| {
7478 editor.set_text("one\ntwo\nthree\n", window, cx)
7479 });
7480 assert!(cx.read(|cx| editor.is_dirty(cx)));
7481
7482 cx.executor().start_waiting();
7483 let fake_server = fake_servers.next().await.unwrap();
7484
7485 let save = editor
7486 .update_in(cx, |editor, window, cx| {
7487 editor.save(true, project.clone(), window, cx)
7488 })
7489 .unwrap();
7490 fake_server
7491 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7492 assert_eq!(
7493 params.text_document.uri,
7494 lsp::Url::from_file_path("/file.rs").unwrap()
7495 );
7496 assert_eq!(params.options.tab_size, 4);
7497 Ok(Some(vec![lsp::TextEdit::new(
7498 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7499 ", ".to_string(),
7500 )]))
7501 })
7502 .next()
7503 .await;
7504 cx.executor().start_waiting();
7505 save.await;
7506 assert_eq!(
7507 editor.update(cx, |editor, cx| editor.text(cx)),
7508 "one, two\nthree\n"
7509 );
7510 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7511
7512 editor.update_in(cx, |editor, window, cx| {
7513 editor.set_text("one\ntwo\nthree\n", window, cx)
7514 });
7515 assert!(cx.read(|cx| editor.is_dirty(cx)));
7516
7517 // Ensure we can still save even if formatting hangs.
7518 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7519 move |params, _| async move {
7520 assert_eq!(
7521 params.text_document.uri,
7522 lsp::Url::from_file_path("/file.rs").unwrap()
7523 );
7524 futures::future::pending::<()>().await;
7525 unreachable!()
7526 },
7527 );
7528 let save = editor
7529 .update_in(cx, |editor, window, cx| {
7530 editor.save(true, project.clone(), window, cx)
7531 })
7532 .unwrap();
7533 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7534 cx.executor().start_waiting();
7535 save.await;
7536 assert_eq!(
7537 editor.update(cx, |editor, cx| editor.text(cx)),
7538 "one\ntwo\nthree\n"
7539 );
7540 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7541
7542 // For non-dirty buffer, no formatting request should be sent
7543 let save = editor
7544 .update_in(cx, |editor, window, cx| {
7545 editor.save(true, project.clone(), window, cx)
7546 })
7547 .unwrap();
7548 let _pending_format_request = fake_server
7549 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7550 panic!("Should not be invoked on non-dirty buffer");
7551 })
7552 .next();
7553 cx.executor().start_waiting();
7554 save.await;
7555
7556 // Set Rust language override and assert overridden tabsize is sent to language server
7557 update_test_language_settings(cx, |settings| {
7558 settings.languages.insert(
7559 "Rust".into(),
7560 LanguageSettingsContent {
7561 tab_size: NonZeroU32::new(8),
7562 ..Default::default()
7563 },
7564 );
7565 });
7566
7567 editor.update_in(cx, |editor, window, cx| {
7568 editor.set_text("somehting_new\n", window, cx)
7569 });
7570 assert!(cx.read(|cx| editor.is_dirty(cx)));
7571 let save = editor
7572 .update_in(cx, |editor, window, cx| {
7573 editor.save(true, project.clone(), window, cx)
7574 })
7575 .unwrap();
7576 fake_server
7577 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7578 assert_eq!(
7579 params.text_document.uri,
7580 lsp::Url::from_file_path("/file.rs").unwrap()
7581 );
7582 assert_eq!(params.options.tab_size, 8);
7583 Ok(Some(vec![]))
7584 })
7585 .next()
7586 .await;
7587 cx.executor().start_waiting();
7588 save.await;
7589}
7590
7591#[gpui::test]
7592async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7593 init_test(cx, |settings| {
7594 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7595 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7596 ))
7597 });
7598
7599 let fs = FakeFs::new(cx.executor());
7600 fs.insert_file("/file.rs", Default::default()).await;
7601
7602 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7603
7604 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7605 language_registry.add(Arc::new(Language::new(
7606 LanguageConfig {
7607 name: "Rust".into(),
7608 matcher: LanguageMatcher {
7609 path_suffixes: vec!["rs".to_string()],
7610 ..Default::default()
7611 },
7612 ..LanguageConfig::default()
7613 },
7614 Some(tree_sitter_rust::LANGUAGE.into()),
7615 )));
7616 update_test_language_settings(cx, |settings| {
7617 // Enable Prettier formatting for the same buffer, and ensure
7618 // LSP is called instead of Prettier.
7619 settings.defaults.prettier = Some(PrettierSettings {
7620 allowed: true,
7621 ..PrettierSettings::default()
7622 });
7623 });
7624 let mut fake_servers = language_registry.register_fake_lsp(
7625 "Rust",
7626 FakeLspAdapter {
7627 capabilities: lsp::ServerCapabilities {
7628 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7629 ..Default::default()
7630 },
7631 ..Default::default()
7632 },
7633 );
7634
7635 let buffer = project
7636 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7637 .await
7638 .unwrap();
7639
7640 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7641 let (editor, cx) = cx.add_window_view(|window, cx| {
7642 build_editor_with_project(project.clone(), buffer, window, cx)
7643 });
7644 editor.update_in(cx, |editor, window, cx| {
7645 editor.set_text("one\ntwo\nthree\n", window, cx)
7646 });
7647
7648 cx.executor().start_waiting();
7649 let fake_server = fake_servers.next().await.unwrap();
7650
7651 let format = editor
7652 .update_in(cx, |editor, window, cx| {
7653 editor.perform_format(
7654 project.clone(),
7655 FormatTrigger::Manual,
7656 FormatTarget::Buffers,
7657 window,
7658 cx,
7659 )
7660 })
7661 .unwrap();
7662 fake_server
7663 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7664 assert_eq!(
7665 params.text_document.uri,
7666 lsp::Url::from_file_path("/file.rs").unwrap()
7667 );
7668 assert_eq!(params.options.tab_size, 4);
7669 Ok(Some(vec![lsp::TextEdit::new(
7670 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7671 ", ".to_string(),
7672 )]))
7673 })
7674 .next()
7675 .await;
7676 cx.executor().start_waiting();
7677 format.await;
7678 assert_eq!(
7679 editor.update(cx, |editor, cx| editor.text(cx)),
7680 "one, two\nthree\n"
7681 );
7682
7683 editor.update_in(cx, |editor, window, cx| {
7684 editor.set_text("one\ntwo\nthree\n", window, cx)
7685 });
7686 // Ensure we don't lock if formatting hangs.
7687 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7688 assert_eq!(
7689 params.text_document.uri,
7690 lsp::Url::from_file_path("/file.rs").unwrap()
7691 );
7692 futures::future::pending::<()>().await;
7693 unreachable!()
7694 });
7695 let format = editor
7696 .update_in(cx, |editor, window, cx| {
7697 editor.perform_format(
7698 project,
7699 FormatTrigger::Manual,
7700 FormatTarget::Buffers,
7701 window,
7702 cx,
7703 )
7704 })
7705 .unwrap();
7706 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7707 cx.executor().start_waiting();
7708 format.await;
7709 assert_eq!(
7710 editor.update(cx, |editor, cx| editor.text(cx)),
7711 "one\ntwo\nthree\n"
7712 );
7713}
7714
7715#[gpui::test]
7716async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7717 init_test(cx, |_| {});
7718
7719 let mut cx = EditorLspTestContext::new_rust(
7720 lsp::ServerCapabilities {
7721 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7722 ..Default::default()
7723 },
7724 cx,
7725 )
7726 .await;
7727
7728 cx.set_state(indoc! {"
7729 one.twoˇ
7730 "});
7731
7732 // The format request takes a long time. When it completes, it inserts
7733 // a newline and an indent before the `.`
7734 cx.lsp
7735 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7736 let executor = cx.background_executor().clone();
7737 async move {
7738 executor.timer(Duration::from_millis(100)).await;
7739 Ok(Some(vec![lsp::TextEdit {
7740 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7741 new_text: "\n ".into(),
7742 }]))
7743 }
7744 });
7745
7746 // Submit a format request.
7747 let format_1 = cx
7748 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7749 .unwrap();
7750 cx.executor().run_until_parked();
7751
7752 // Submit a second format request.
7753 let format_2 = cx
7754 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7755 .unwrap();
7756 cx.executor().run_until_parked();
7757
7758 // Wait for both format requests to complete
7759 cx.executor().advance_clock(Duration::from_millis(200));
7760 cx.executor().start_waiting();
7761 format_1.await.unwrap();
7762 cx.executor().start_waiting();
7763 format_2.await.unwrap();
7764
7765 // The formatting edits only happens once.
7766 cx.assert_editor_state(indoc! {"
7767 one
7768 .twoˇ
7769 "});
7770}
7771
7772#[gpui::test]
7773async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7774 init_test(cx, |settings| {
7775 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7776 });
7777
7778 let mut cx = EditorLspTestContext::new_rust(
7779 lsp::ServerCapabilities {
7780 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7781 ..Default::default()
7782 },
7783 cx,
7784 )
7785 .await;
7786
7787 // Set up a buffer white some trailing whitespace and no trailing newline.
7788 cx.set_state(
7789 &[
7790 "one ", //
7791 "twoˇ", //
7792 "three ", //
7793 "four", //
7794 ]
7795 .join("\n"),
7796 );
7797
7798 // Submit a format request.
7799 let format = cx
7800 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
7801 .unwrap();
7802
7803 // Record which buffer changes have been sent to the language server
7804 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7805 cx.lsp
7806 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7807 let buffer_changes = buffer_changes.clone();
7808 move |params, _| {
7809 buffer_changes.lock().extend(
7810 params
7811 .content_changes
7812 .into_iter()
7813 .map(|e| (e.range.unwrap(), e.text)),
7814 );
7815 }
7816 });
7817
7818 // Handle formatting requests to the language server.
7819 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7820 let buffer_changes = buffer_changes.clone();
7821 move |_, _| {
7822 // When formatting is requested, trailing whitespace has already been stripped,
7823 // and the trailing newline has already been added.
7824 assert_eq!(
7825 &buffer_changes.lock()[1..],
7826 &[
7827 (
7828 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7829 "".into()
7830 ),
7831 (
7832 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7833 "".into()
7834 ),
7835 (
7836 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7837 "\n".into()
7838 ),
7839 ]
7840 );
7841
7842 // Insert blank lines between each line of the buffer.
7843 async move {
7844 Ok(Some(vec![
7845 lsp::TextEdit {
7846 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7847 new_text: "\n".into(),
7848 },
7849 lsp::TextEdit {
7850 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7851 new_text: "\n".into(),
7852 },
7853 ]))
7854 }
7855 }
7856 });
7857
7858 // After formatting the buffer, the trailing whitespace is stripped,
7859 // a newline is appended, and the edits provided by the language server
7860 // have been applied.
7861 format.await.unwrap();
7862 cx.assert_editor_state(
7863 &[
7864 "one", //
7865 "", //
7866 "twoˇ", //
7867 "", //
7868 "three", //
7869 "four", //
7870 "", //
7871 ]
7872 .join("\n"),
7873 );
7874
7875 // Undoing the formatting undoes the trailing whitespace removal, the
7876 // trailing newline, and the LSP edits.
7877 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7878 cx.assert_editor_state(
7879 &[
7880 "one ", //
7881 "twoˇ", //
7882 "three ", //
7883 "four", //
7884 ]
7885 .join("\n"),
7886 );
7887}
7888
7889#[gpui::test]
7890async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7891 cx: &mut gpui::TestAppContext,
7892) {
7893 init_test(cx, |_| {});
7894
7895 cx.update(|cx| {
7896 cx.update_global::<SettingsStore, _>(|settings, cx| {
7897 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7898 settings.auto_signature_help = Some(true);
7899 });
7900 });
7901 });
7902
7903 let mut cx = EditorLspTestContext::new_rust(
7904 lsp::ServerCapabilities {
7905 signature_help_provider: Some(lsp::SignatureHelpOptions {
7906 ..Default::default()
7907 }),
7908 ..Default::default()
7909 },
7910 cx,
7911 )
7912 .await;
7913
7914 let language = Language::new(
7915 LanguageConfig {
7916 name: "Rust".into(),
7917 brackets: BracketPairConfig {
7918 pairs: vec![
7919 BracketPair {
7920 start: "{".to_string(),
7921 end: "}".to_string(),
7922 close: true,
7923 surround: true,
7924 newline: true,
7925 },
7926 BracketPair {
7927 start: "(".to_string(),
7928 end: ")".to_string(),
7929 close: true,
7930 surround: true,
7931 newline: true,
7932 },
7933 BracketPair {
7934 start: "/*".to_string(),
7935 end: " */".to_string(),
7936 close: true,
7937 surround: true,
7938 newline: true,
7939 },
7940 BracketPair {
7941 start: "[".to_string(),
7942 end: "]".to_string(),
7943 close: false,
7944 surround: false,
7945 newline: true,
7946 },
7947 BracketPair {
7948 start: "\"".to_string(),
7949 end: "\"".to_string(),
7950 close: true,
7951 surround: true,
7952 newline: false,
7953 },
7954 BracketPair {
7955 start: "<".to_string(),
7956 end: ">".to_string(),
7957 close: false,
7958 surround: true,
7959 newline: true,
7960 },
7961 ],
7962 ..Default::default()
7963 },
7964 autoclose_before: "})]".to_string(),
7965 ..Default::default()
7966 },
7967 Some(tree_sitter_rust::LANGUAGE.into()),
7968 );
7969 let language = Arc::new(language);
7970
7971 cx.language_registry().add(language.clone());
7972 cx.update_buffer(|buffer, cx| {
7973 buffer.set_language(Some(language), cx);
7974 });
7975
7976 cx.set_state(
7977 &r#"
7978 fn main() {
7979 sampleˇ
7980 }
7981 "#
7982 .unindent(),
7983 );
7984
7985 cx.update_editor(|editor, window, cx| {
7986 editor.handle_input("(", window, cx);
7987 });
7988 cx.assert_editor_state(
7989 &"
7990 fn main() {
7991 sample(ˇ)
7992 }
7993 "
7994 .unindent(),
7995 );
7996
7997 let mocked_response = lsp::SignatureHelp {
7998 signatures: vec![lsp::SignatureInformation {
7999 label: "fn sample(param1: u8, param2: u8)".to_string(),
8000 documentation: None,
8001 parameters: Some(vec![
8002 lsp::ParameterInformation {
8003 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8004 documentation: None,
8005 },
8006 lsp::ParameterInformation {
8007 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8008 documentation: None,
8009 },
8010 ]),
8011 active_parameter: None,
8012 }],
8013 active_signature: Some(0),
8014 active_parameter: Some(0),
8015 };
8016 handle_signature_help_request(&mut cx, mocked_response).await;
8017
8018 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8019 .await;
8020
8021 cx.editor(|editor, _, _| {
8022 let signature_help_state = editor.signature_help_state.popover().cloned();
8023 assert!(signature_help_state.is_some());
8024 let ParsedMarkdown {
8025 text, highlights, ..
8026 } = signature_help_state.unwrap().parsed_content;
8027 assert_eq!(text, "param1: u8, param2: u8");
8028 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8029 });
8030}
8031
8032#[gpui::test]
8033async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
8034 init_test(cx, |_| {});
8035
8036 cx.update(|cx| {
8037 cx.update_global::<SettingsStore, _>(|settings, cx| {
8038 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8039 settings.auto_signature_help = Some(false);
8040 settings.show_signature_help_after_edits = Some(false);
8041 });
8042 });
8043 });
8044
8045 let mut cx = EditorLspTestContext::new_rust(
8046 lsp::ServerCapabilities {
8047 signature_help_provider: Some(lsp::SignatureHelpOptions {
8048 ..Default::default()
8049 }),
8050 ..Default::default()
8051 },
8052 cx,
8053 )
8054 .await;
8055
8056 let language = Language::new(
8057 LanguageConfig {
8058 name: "Rust".into(),
8059 brackets: BracketPairConfig {
8060 pairs: vec![
8061 BracketPair {
8062 start: "{".to_string(),
8063 end: "}".to_string(),
8064 close: true,
8065 surround: true,
8066 newline: true,
8067 },
8068 BracketPair {
8069 start: "(".to_string(),
8070 end: ")".to_string(),
8071 close: true,
8072 surround: true,
8073 newline: true,
8074 },
8075 BracketPair {
8076 start: "/*".to_string(),
8077 end: " */".to_string(),
8078 close: true,
8079 surround: true,
8080 newline: true,
8081 },
8082 BracketPair {
8083 start: "[".to_string(),
8084 end: "]".to_string(),
8085 close: false,
8086 surround: false,
8087 newline: true,
8088 },
8089 BracketPair {
8090 start: "\"".to_string(),
8091 end: "\"".to_string(),
8092 close: true,
8093 surround: true,
8094 newline: false,
8095 },
8096 BracketPair {
8097 start: "<".to_string(),
8098 end: ">".to_string(),
8099 close: false,
8100 surround: true,
8101 newline: true,
8102 },
8103 ],
8104 ..Default::default()
8105 },
8106 autoclose_before: "})]".to_string(),
8107 ..Default::default()
8108 },
8109 Some(tree_sitter_rust::LANGUAGE.into()),
8110 );
8111 let language = Arc::new(language);
8112
8113 cx.language_registry().add(language.clone());
8114 cx.update_buffer(|buffer, cx| {
8115 buffer.set_language(Some(language), cx);
8116 });
8117
8118 // Ensure that signature_help is not called when no signature help is enabled.
8119 cx.set_state(
8120 &r#"
8121 fn main() {
8122 sampleˇ
8123 }
8124 "#
8125 .unindent(),
8126 );
8127 cx.update_editor(|editor, window, cx| {
8128 editor.handle_input("(", window, cx);
8129 });
8130 cx.assert_editor_state(
8131 &"
8132 fn main() {
8133 sample(ˇ)
8134 }
8135 "
8136 .unindent(),
8137 );
8138 cx.editor(|editor, _, _| {
8139 assert!(editor.signature_help_state.task().is_none());
8140 });
8141
8142 let mocked_response = lsp::SignatureHelp {
8143 signatures: vec![lsp::SignatureInformation {
8144 label: "fn sample(param1: u8, param2: u8)".to_string(),
8145 documentation: None,
8146 parameters: Some(vec![
8147 lsp::ParameterInformation {
8148 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8149 documentation: None,
8150 },
8151 lsp::ParameterInformation {
8152 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8153 documentation: None,
8154 },
8155 ]),
8156 active_parameter: None,
8157 }],
8158 active_signature: Some(0),
8159 active_parameter: Some(0),
8160 };
8161
8162 // Ensure that signature_help is called when enabled afte edits
8163 cx.update(|_, cx| {
8164 cx.update_global::<SettingsStore, _>(|settings, cx| {
8165 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8166 settings.auto_signature_help = Some(false);
8167 settings.show_signature_help_after_edits = Some(true);
8168 });
8169 });
8170 });
8171 cx.set_state(
8172 &r#"
8173 fn main() {
8174 sampleˇ
8175 }
8176 "#
8177 .unindent(),
8178 );
8179 cx.update_editor(|editor, window, cx| {
8180 editor.handle_input("(", window, cx);
8181 });
8182 cx.assert_editor_state(
8183 &"
8184 fn main() {
8185 sample(ˇ)
8186 }
8187 "
8188 .unindent(),
8189 );
8190 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8191 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8192 .await;
8193 cx.update_editor(|editor, _, _| {
8194 let signature_help_state = editor.signature_help_state.popover().cloned();
8195 assert!(signature_help_state.is_some());
8196 let ParsedMarkdown {
8197 text, highlights, ..
8198 } = signature_help_state.unwrap().parsed_content;
8199 assert_eq!(text, "param1: u8, param2: u8");
8200 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8201 editor.signature_help_state = SignatureHelpState::default();
8202 });
8203
8204 // Ensure that signature_help is called when auto signature help override is enabled
8205 cx.update(|_, cx| {
8206 cx.update_global::<SettingsStore, _>(|settings, cx| {
8207 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8208 settings.auto_signature_help = Some(true);
8209 settings.show_signature_help_after_edits = Some(false);
8210 });
8211 });
8212 });
8213 cx.set_state(
8214 &r#"
8215 fn main() {
8216 sampleˇ
8217 }
8218 "#
8219 .unindent(),
8220 );
8221 cx.update_editor(|editor, window, cx| {
8222 editor.handle_input("(", window, cx);
8223 });
8224 cx.assert_editor_state(
8225 &"
8226 fn main() {
8227 sample(ˇ)
8228 }
8229 "
8230 .unindent(),
8231 );
8232 handle_signature_help_request(&mut cx, mocked_response).await;
8233 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8234 .await;
8235 cx.editor(|editor, _, _| {
8236 let signature_help_state = editor.signature_help_state.popover().cloned();
8237 assert!(signature_help_state.is_some());
8238 let ParsedMarkdown {
8239 text, highlights, ..
8240 } = signature_help_state.unwrap().parsed_content;
8241 assert_eq!(text, "param1: u8, param2: u8");
8242 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8243 });
8244}
8245
8246#[gpui::test]
8247async fn test_signature_help(cx: &mut gpui::TestAppContext) {
8248 init_test(cx, |_| {});
8249 cx.update(|cx| {
8250 cx.update_global::<SettingsStore, _>(|settings, cx| {
8251 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8252 settings.auto_signature_help = Some(true);
8253 });
8254 });
8255 });
8256
8257 let mut cx = EditorLspTestContext::new_rust(
8258 lsp::ServerCapabilities {
8259 signature_help_provider: Some(lsp::SignatureHelpOptions {
8260 ..Default::default()
8261 }),
8262 ..Default::default()
8263 },
8264 cx,
8265 )
8266 .await;
8267
8268 // A test that directly calls `show_signature_help`
8269 cx.update_editor(|editor, window, cx| {
8270 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8271 });
8272
8273 let mocked_response = lsp::SignatureHelp {
8274 signatures: vec![lsp::SignatureInformation {
8275 label: "fn sample(param1: u8, param2: u8)".to_string(),
8276 documentation: None,
8277 parameters: Some(vec![
8278 lsp::ParameterInformation {
8279 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8280 documentation: None,
8281 },
8282 lsp::ParameterInformation {
8283 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8284 documentation: None,
8285 },
8286 ]),
8287 active_parameter: None,
8288 }],
8289 active_signature: Some(0),
8290 active_parameter: Some(0),
8291 };
8292 handle_signature_help_request(&mut cx, mocked_response).await;
8293
8294 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8295 .await;
8296
8297 cx.editor(|editor, _, _| {
8298 let signature_help_state = editor.signature_help_state.popover().cloned();
8299 assert!(signature_help_state.is_some());
8300 let ParsedMarkdown {
8301 text, highlights, ..
8302 } = signature_help_state.unwrap().parsed_content;
8303 assert_eq!(text, "param1: u8, param2: u8");
8304 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8305 });
8306
8307 // When exiting outside from inside the brackets, `signature_help` is closed.
8308 cx.set_state(indoc! {"
8309 fn main() {
8310 sample(ˇ);
8311 }
8312
8313 fn sample(param1: u8, param2: u8) {}
8314 "});
8315
8316 cx.update_editor(|editor, window, cx| {
8317 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8318 });
8319
8320 let mocked_response = lsp::SignatureHelp {
8321 signatures: Vec::new(),
8322 active_signature: None,
8323 active_parameter: None,
8324 };
8325 handle_signature_help_request(&mut cx, mocked_response).await;
8326
8327 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8328 .await;
8329
8330 cx.editor(|editor, _, _| {
8331 assert!(!editor.signature_help_state.is_shown());
8332 });
8333
8334 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8335 cx.set_state(indoc! {"
8336 fn main() {
8337 sample(ˇ);
8338 }
8339
8340 fn sample(param1: u8, param2: u8) {}
8341 "});
8342
8343 let mocked_response = lsp::SignatureHelp {
8344 signatures: vec![lsp::SignatureInformation {
8345 label: "fn sample(param1: u8, param2: u8)".to_string(),
8346 documentation: None,
8347 parameters: Some(vec![
8348 lsp::ParameterInformation {
8349 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8350 documentation: None,
8351 },
8352 lsp::ParameterInformation {
8353 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8354 documentation: None,
8355 },
8356 ]),
8357 active_parameter: None,
8358 }],
8359 active_signature: Some(0),
8360 active_parameter: Some(0),
8361 };
8362 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8363 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8364 .await;
8365 cx.editor(|editor, _, _| {
8366 assert!(editor.signature_help_state.is_shown());
8367 });
8368
8369 // Restore the popover with more parameter input
8370 cx.set_state(indoc! {"
8371 fn main() {
8372 sample(param1, param2ˇ);
8373 }
8374
8375 fn sample(param1: u8, param2: u8) {}
8376 "});
8377
8378 let mocked_response = lsp::SignatureHelp {
8379 signatures: vec![lsp::SignatureInformation {
8380 label: "fn sample(param1: u8, param2: u8)".to_string(),
8381 documentation: None,
8382 parameters: Some(vec![
8383 lsp::ParameterInformation {
8384 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8385 documentation: None,
8386 },
8387 lsp::ParameterInformation {
8388 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8389 documentation: None,
8390 },
8391 ]),
8392 active_parameter: None,
8393 }],
8394 active_signature: Some(0),
8395 active_parameter: Some(1),
8396 };
8397 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8398 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8399 .await;
8400
8401 // When selecting a range, the popover is gone.
8402 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8403 cx.update_editor(|editor, window, cx| {
8404 editor.change_selections(None, window, cx, |s| {
8405 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8406 })
8407 });
8408 cx.assert_editor_state(indoc! {"
8409 fn main() {
8410 sample(param1, «ˇparam2»);
8411 }
8412
8413 fn sample(param1: u8, param2: u8) {}
8414 "});
8415 cx.editor(|editor, _, _| {
8416 assert!(!editor.signature_help_state.is_shown());
8417 });
8418
8419 // When unselecting again, the popover is back if within the brackets.
8420 cx.update_editor(|editor, window, cx| {
8421 editor.change_selections(None, window, cx, |s| {
8422 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8423 })
8424 });
8425 cx.assert_editor_state(indoc! {"
8426 fn main() {
8427 sample(param1, ˇparam2);
8428 }
8429
8430 fn sample(param1: u8, param2: u8) {}
8431 "});
8432 handle_signature_help_request(&mut cx, mocked_response).await;
8433 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8434 .await;
8435 cx.editor(|editor, _, _| {
8436 assert!(editor.signature_help_state.is_shown());
8437 });
8438
8439 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8440 cx.update_editor(|editor, window, cx| {
8441 editor.change_selections(None, window, cx, |s| {
8442 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8443 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8444 })
8445 });
8446 cx.assert_editor_state(indoc! {"
8447 fn main() {
8448 sample(param1, ˇparam2);
8449 }
8450
8451 fn sample(param1: u8, param2: u8) {}
8452 "});
8453
8454 let mocked_response = lsp::SignatureHelp {
8455 signatures: vec![lsp::SignatureInformation {
8456 label: "fn sample(param1: u8, param2: u8)".to_string(),
8457 documentation: None,
8458 parameters: Some(vec![
8459 lsp::ParameterInformation {
8460 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8461 documentation: None,
8462 },
8463 lsp::ParameterInformation {
8464 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8465 documentation: None,
8466 },
8467 ]),
8468 active_parameter: None,
8469 }],
8470 active_signature: Some(0),
8471 active_parameter: Some(1),
8472 };
8473 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8474 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8475 .await;
8476 cx.update_editor(|editor, _, cx| {
8477 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8478 });
8479 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8480 .await;
8481 cx.update_editor(|editor, window, cx| {
8482 editor.change_selections(None, window, cx, |s| {
8483 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8484 })
8485 });
8486 cx.assert_editor_state(indoc! {"
8487 fn main() {
8488 sample(param1, «ˇparam2»);
8489 }
8490
8491 fn sample(param1: u8, param2: u8) {}
8492 "});
8493 cx.update_editor(|editor, window, cx| {
8494 editor.change_selections(None, window, cx, |s| {
8495 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8496 })
8497 });
8498 cx.assert_editor_state(indoc! {"
8499 fn main() {
8500 sample(param1, ˇparam2);
8501 }
8502
8503 fn sample(param1: u8, param2: u8) {}
8504 "});
8505 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8506 .await;
8507}
8508
8509#[gpui::test]
8510async fn test_completion(cx: &mut gpui::TestAppContext) {
8511 init_test(cx, |_| {});
8512
8513 let mut cx = EditorLspTestContext::new_rust(
8514 lsp::ServerCapabilities {
8515 completion_provider: Some(lsp::CompletionOptions {
8516 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8517 resolve_provider: Some(true),
8518 ..Default::default()
8519 }),
8520 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8521 ..Default::default()
8522 },
8523 cx,
8524 )
8525 .await;
8526 let counter = Arc::new(AtomicUsize::new(0));
8527
8528 cx.set_state(indoc! {"
8529 oneˇ
8530 two
8531 three
8532 "});
8533 cx.simulate_keystroke(".");
8534 handle_completion_request(
8535 &mut cx,
8536 indoc! {"
8537 one.|<>
8538 two
8539 three
8540 "},
8541 vec!["first_completion", "second_completion"],
8542 counter.clone(),
8543 )
8544 .await;
8545 cx.condition(|editor, _| editor.context_menu_visible())
8546 .await;
8547 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8548
8549 let _handler = handle_signature_help_request(
8550 &mut cx,
8551 lsp::SignatureHelp {
8552 signatures: vec![lsp::SignatureInformation {
8553 label: "test signature".to_string(),
8554 documentation: None,
8555 parameters: Some(vec![lsp::ParameterInformation {
8556 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8557 documentation: None,
8558 }]),
8559 active_parameter: None,
8560 }],
8561 active_signature: None,
8562 active_parameter: None,
8563 },
8564 );
8565 cx.update_editor(|editor, window, cx| {
8566 assert!(
8567 !editor.signature_help_state.is_shown(),
8568 "No signature help was called for"
8569 );
8570 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8571 });
8572 cx.run_until_parked();
8573 cx.update_editor(|editor, _, _| {
8574 assert!(
8575 !editor.signature_help_state.is_shown(),
8576 "No signature help should be shown when completions menu is open"
8577 );
8578 });
8579
8580 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8581 editor.context_menu_next(&Default::default(), window, cx);
8582 editor
8583 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8584 .unwrap()
8585 });
8586 cx.assert_editor_state(indoc! {"
8587 one.second_completionˇ
8588 two
8589 three
8590 "});
8591
8592 handle_resolve_completion_request(
8593 &mut cx,
8594 Some(vec![
8595 (
8596 //This overlaps with the primary completion edit which is
8597 //misbehavior from the LSP spec, test that we filter it out
8598 indoc! {"
8599 one.second_ˇcompletion
8600 two
8601 threeˇ
8602 "},
8603 "overlapping additional edit",
8604 ),
8605 (
8606 indoc! {"
8607 one.second_completion
8608 two
8609 threeˇ
8610 "},
8611 "\nadditional edit",
8612 ),
8613 ]),
8614 )
8615 .await;
8616 apply_additional_edits.await.unwrap();
8617 cx.assert_editor_state(indoc! {"
8618 one.second_completionˇ
8619 two
8620 three
8621 additional edit
8622 "});
8623
8624 cx.set_state(indoc! {"
8625 one.second_completion
8626 twoˇ
8627 threeˇ
8628 additional edit
8629 "});
8630 cx.simulate_keystroke(" ");
8631 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8632 cx.simulate_keystroke("s");
8633 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8634
8635 cx.assert_editor_state(indoc! {"
8636 one.second_completion
8637 two sˇ
8638 three sˇ
8639 additional edit
8640 "});
8641 handle_completion_request(
8642 &mut cx,
8643 indoc! {"
8644 one.second_completion
8645 two s
8646 three <s|>
8647 additional edit
8648 "},
8649 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8650 counter.clone(),
8651 )
8652 .await;
8653 cx.condition(|editor, _| editor.context_menu_visible())
8654 .await;
8655 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8656
8657 cx.simulate_keystroke("i");
8658
8659 handle_completion_request(
8660 &mut cx,
8661 indoc! {"
8662 one.second_completion
8663 two si
8664 three <si|>
8665 additional edit
8666 "},
8667 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8668 counter.clone(),
8669 )
8670 .await;
8671 cx.condition(|editor, _| editor.context_menu_visible())
8672 .await;
8673 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8674
8675 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8676 editor
8677 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8678 .unwrap()
8679 });
8680 cx.assert_editor_state(indoc! {"
8681 one.second_completion
8682 two sixth_completionˇ
8683 three sixth_completionˇ
8684 additional edit
8685 "});
8686
8687 apply_additional_edits.await.unwrap();
8688
8689 update_test_language_settings(&mut cx, |settings| {
8690 settings.defaults.show_completions_on_input = Some(false);
8691 });
8692 cx.set_state("editorˇ");
8693 cx.simulate_keystroke(".");
8694 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8695 cx.simulate_keystroke("c");
8696 cx.simulate_keystroke("l");
8697 cx.simulate_keystroke("o");
8698 cx.assert_editor_state("editor.cloˇ");
8699 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
8700 cx.update_editor(|editor, window, cx| {
8701 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
8702 });
8703 handle_completion_request(
8704 &mut cx,
8705 "editor.<clo|>",
8706 vec!["close", "clobber"],
8707 counter.clone(),
8708 )
8709 .await;
8710 cx.condition(|editor, _| editor.context_menu_visible())
8711 .await;
8712 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8713
8714 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
8715 editor
8716 .confirm_completion(&ConfirmCompletion::default(), window, cx)
8717 .unwrap()
8718 });
8719 cx.assert_editor_state("editor.closeˇ");
8720 handle_resolve_completion_request(&mut cx, None).await;
8721 apply_additional_edits.await.unwrap();
8722}
8723
8724#[gpui::test]
8725async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
8726 init_test(cx, |_| {});
8727
8728 let fs = FakeFs::new(cx.executor());
8729 fs.insert_tree(
8730 "/a",
8731 json!({
8732 "main.ts": "a",
8733 }),
8734 )
8735 .await;
8736
8737 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8738 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8739 let typescript_language = Arc::new(Language::new(
8740 LanguageConfig {
8741 name: "TypeScript".into(),
8742 matcher: LanguageMatcher {
8743 path_suffixes: vec!["ts".to_string()],
8744 ..LanguageMatcher::default()
8745 },
8746 line_comments: vec!["// ".into()],
8747 ..LanguageConfig::default()
8748 },
8749 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8750 ));
8751 language_registry.add(typescript_language.clone());
8752 let mut fake_servers = language_registry.register_fake_lsp(
8753 "TypeScript",
8754 FakeLspAdapter {
8755 capabilities: lsp::ServerCapabilities {
8756 completion_provider: Some(lsp::CompletionOptions {
8757 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8758 ..lsp::CompletionOptions::default()
8759 }),
8760 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8761 ..lsp::ServerCapabilities::default()
8762 },
8763 // Emulate vtsls label generation
8764 label_for_completion: Some(Box::new(|item, _| {
8765 let text = if let Some(description) = item
8766 .label_details
8767 .as_ref()
8768 .and_then(|label_details| label_details.description.as_ref())
8769 {
8770 format!("{} {}", item.label, description)
8771 } else if let Some(detail) = &item.detail {
8772 format!("{} {}", item.label, detail)
8773 } else {
8774 item.label.clone()
8775 };
8776 let len = text.len();
8777 Some(language::CodeLabel {
8778 text,
8779 runs: Vec::new(),
8780 filter_range: 0..len,
8781 })
8782 })),
8783 ..FakeLspAdapter::default()
8784 },
8785 );
8786 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
8787 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8788 let worktree_id = workspace
8789 .update(cx, |workspace, _window, cx| {
8790 workspace.project().update(cx, |project, cx| {
8791 project.worktrees(cx).next().unwrap().read(cx).id()
8792 })
8793 })
8794 .unwrap();
8795 let _buffer = project
8796 .update(cx, |project, cx| {
8797 project.open_local_buffer_with_lsp("/a/main.ts", cx)
8798 })
8799 .await
8800 .unwrap();
8801 let editor = workspace
8802 .update(cx, |workspace, window, cx| {
8803 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
8804 })
8805 .unwrap()
8806 .await
8807 .unwrap()
8808 .downcast::<Editor>()
8809 .unwrap();
8810 let fake_server = fake_servers.next().await.unwrap();
8811
8812 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8813 let multiline_label_2 = "a\nb\nc\n";
8814 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8815 let multiline_description = "d\ne\nf\n";
8816 let multiline_detail_2 = "g\nh\ni\n";
8817
8818 let mut completion_handle =
8819 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8820 Ok(Some(lsp::CompletionResponse::Array(vec![
8821 lsp::CompletionItem {
8822 label: multiline_label.to_string(),
8823 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8824 range: lsp::Range {
8825 start: lsp::Position {
8826 line: params.text_document_position.position.line,
8827 character: params.text_document_position.position.character,
8828 },
8829 end: lsp::Position {
8830 line: params.text_document_position.position.line,
8831 character: params.text_document_position.position.character,
8832 },
8833 },
8834 new_text: "new_text_1".to_string(),
8835 })),
8836 ..lsp::CompletionItem::default()
8837 },
8838 lsp::CompletionItem {
8839 label: "single line label 1".to_string(),
8840 detail: Some(multiline_detail.to_string()),
8841 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8842 range: lsp::Range {
8843 start: lsp::Position {
8844 line: params.text_document_position.position.line,
8845 character: params.text_document_position.position.character,
8846 },
8847 end: lsp::Position {
8848 line: params.text_document_position.position.line,
8849 character: params.text_document_position.position.character,
8850 },
8851 },
8852 new_text: "new_text_2".to_string(),
8853 })),
8854 ..lsp::CompletionItem::default()
8855 },
8856 lsp::CompletionItem {
8857 label: "single line label 2".to_string(),
8858 label_details: Some(lsp::CompletionItemLabelDetails {
8859 description: Some(multiline_description.to_string()),
8860 detail: None,
8861 }),
8862 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8863 range: lsp::Range {
8864 start: lsp::Position {
8865 line: params.text_document_position.position.line,
8866 character: params.text_document_position.position.character,
8867 },
8868 end: lsp::Position {
8869 line: params.text_document_position.position.line,
8870 character: params.text_document_position.position.character,
8871 },
8872 },
8873 new_text: "new_text_2".to_string(),
8874 })),
8875 ..lsp::CompletionItem::default()
8876 },
8877 lsp::CompletionItem {
8878 label: multiline_label_2.to_string(),
8879 detail: Some(multiline_detail_2.to_string()),
8880 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8881 range: lsp::Range {
8882 start: lsp::Position {
8883 line: params.text_document_position.position.line,
8884 character: params.text_document_position.position.character,
8885 },
8886 end: lsp::Position {
8887 line: params.text_document_position.position.line,
8888 character: params.text_document_position.position.character,
8889 },
8890 },
8891 new_text: "new_text_3".to_string(),
8892 })),
8893 ..lsp::CompletionItem::default()
8894 },
8895 lsp::CompletionItem {
8896 label: "Label with many spaces and \t but without newlines".to_string(),
8897 detail: Some(
8898 "Details with many spaces and \t but without newlines".to_string(),
8899 ),
8900 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8901 range: lsp::Range {
8902 start: lsp::Position {
8903 line: params.text_document_position.position.line,
8904 character: params.text_document_position.position.character,
8905 },
8906 end: lsp::Position {
8907 line: params.text_document_position.position.line,
8908 character: params.text_document_position.position.character,
8909 },
8910 },
8911 new_text: "new_text_4".to_string(),
8912 })),
8913 ..lsp::CompletionItem::default()
8914 },
8915 ])))
8916 });
8917
8918 editor.update_in(cx, |editor, window, cx| {
8919 cx.focus_self(window);
8920 editor.move_to_end(&MoveToEnd, window, cx);
8921 editor.handle_input(".", window, cx);
8922 });
8923 cx.run_until_parked();
8924 completion_handle.next().await.unwrap();
8925
8926 editor.update(cx, |editor, _| {
8927 assert!(editor.context_menu_visible());
8928 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8929 {
8930 let completion_labels = menu
8931 .completions
8932 .borrow()
8933 .iter()
8934 .map(|c| c.label.text.clone())
8935 .collect::<Vec<_>>();
8936 assert_eq!(
8937 completion_labels,
8938 &[
8939 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
8940 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
8941 "single line label 2 d e f ",
8942 "a b c g h i ",
8943 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
8944 ],
8945 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
8946 );
8947
8948 for completion in menu
8949 .completions
8950 .borrow()
8951 .iter() {
8952 assert_eq!(
8953 completion.label.filter_range,
8954 0..completion.label.text.len(),
8955 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
8956 );
8957 }
8958
8959 } else {
8960 panic!("expected completion menu to be open");
8961 }
8962 });
8963}
8964
8965#[gpui::test]
8966async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8967 init_test(cx, |_| {});
8968 let mut cx = EditorLspTestContext::new_rust(
8969 lsp::ServerCapabilities {
8970 completion_provider: Some(lsp::CompletionOptions {
8971 trigger_characters: Some(vec![".".to_string()]),
8972 ..Default::default()
8973 }),
8974 ..Default::default()
8975 },
8976 cx,
8977 )
8978 .await;
8979 cx.lsp
8980 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8981 Ok(Some(lsp::CompletionResponse::Array(vec![
8982 lsp::CompletionItem {
8983 label: "first".into(),
8984 ..Default::default()
8985 },
8986 lsp::CompletionItem {
8987 label: "last".into(),
8988 ..Default::default()
8989 },
8990 ])))
8991 });
8992 cx.set_state("variableˇ");
8993 cx.simulate_keystroke(".");
8994 cx.executor().run_until_parked();
8995
8996 cx.update_editor(|editor, _, _| {
8997 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8998 {
8999 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9000 } else {
9001 panic!("expected completion menu to be open");
9002 }
9003 });
9004
9005 cx.update_editor(|editor, window, cx| {
9006 editor.move_page_down(&MovePageDown::default(), window, cx);
9007 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9008 {
9009 assert!(
9010 menu.selected_item == 1,
9011 "expected PageDown to select the last item from the context menu"
9012 );
9013 } else {
9014 panic!("expected completion menu to stay open after PageDown");
9015 }
9016 });
9017
9018 cx.update_editor(|editor, window, cx| {
9019 editor.move_page_up(&MovePageUp::default(), window, cx);
9020 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9021 {
9022 assert!(
9023 menu.selected_item == 0,
9024 "expected PageUp to select the first item from the context menu"
9025 );
9026 } else {
9027 panic!("expected completion menu to stay open after PageUp");
9028 }
9029 });
9030}
9031
9032#[gpui::test]
9033async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
9034 init_test(cx, |_| {});
9035 let mut cx = EditorLspTestContext::new_rust(
9036 lsp::ServerCapabilities {
9037 completion_provider: Some(lsp::CompletionOptions {
9038 trigger_characters: Some(vec![".".to_string()]),
9039 ..Default::default()
9040 }),
9041 ..Default::default()
9042 },
9043 cx,
9044 )
9045 .await;
9046 cx.lsp
9047 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9048 Ok(Some(lsp::CompletionResponse::Array(vec![
9049 lsp::CompletionItem {
9050 label: "Range".into(),
9051 sort_text: Some("a".into()),
9052 ..Default::default()
9053 },
9054 lsp::CompletionItem {
9055 label: "r".into(),
9056 sort_text: Some("b".into()),
9057 ..Default::default()
9058 },
9059 lsp::CompletionItem {
9060 label: "ret".into(),
9061 sort_text: Some("c".into()),
9062 ..Default::default()
9063 },
9064 lsp::CompletionItem {
9065 label: "return".into(),
9066 sort_text: Some("d".into()),
9067 ..Default::default()
9068 },
9069 lsp::CompletionItem {
9070 label: "slice".into(),
9071 sort_text: Some("d".into()),
9072 ..Default::default()
9073 },
9074 ])))
9075 });
9076 cx.set_state("rˇ");
9077 cx.executor().run_until_parked();
9078 cx.update_editor(|editor, window, cx| {
9079 editor.show_completions(
9080 &ShowCompletions {
9081 trigger: Some("r".into()),
9082 },
9083 window,
9084 cx,
9085 );
9086 });
9087 cx.executor().run_until_parked();
9088
9089 cx.update_editor(|editor, _, _| {
9090 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9091 {
9092 assert_eq!(
9093 completion_menu_entries(&menu),
9094 &["r", "ret", "Range", "return"]
9095 );
9096 } else {
9097 panic!("expected completion menu to be open");
9098 }
9099 });
9100}
9101
9102#[gpui::test]
9103async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
9104 init_test(cx, |_| {});
9105
9106 let mut cx = EditorLspTestContext::new_rust(
9107 lsp::ServerCapabilities {
9108 completion_provider: Some(lsp::CompletionOptions {
9109 trigger_characters: Some(vec![".".to_string()]),
9110 resolve_provider: Some(true),
9111 ..Default::default()
9112 }),
9113 ..Default::default()
9114 },
9115 cx,
9116 )
9117 .await;
9118
9119 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9120 cx.simulate_keystroke(".");
9121 let completion_item = lsp::CompletionItem {
9122 label: "Some".into(),
9123 kind: Some(lsp::CompletionItemKind::SNIPPET),
9124 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9125 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9126 kind: lsp::MarkupKind::Markdown,
9127 value: "```rust\nSome(2)\n```".to_string(),
9128 })),
9129 deprecated: Some(false),
9130 sort_text: Some("Some".to_string()),
9131 filter_text: Some("Some".to_string()),
9132 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9133 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9134 range: lsp::Range {
9135 start: lsp::Position {
9136 line: 0,
9137 character: 22,
9138 },
9139 end: lsp::Position {
9140 line: 0,
9141 character: 22,
9142 },
9143 },
9144 new_text: "Some(2)".to_string(),
9145 })),
9146 additional_text_edits: Some(vec![lsp::TextEdit {
9147 range: lsp::Range {
9148 start: lsp::Position {
9149 line: 0,
9150 character: 20,
9151 },
9152 end: lsp::Position {
9153 line: 0,
9154 character: 22,
9155 },
9156 },
9157 new_text: "".to_string(),
9158 }]),
9159 ..Default::default()
9160 };
9161
9162 let closure_completion_item = completion_item.clone();
9163 let counter = Arc::new(AtomicUsize::new(0));
9164 let counter_clone = counter.clone();
9165 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9166 let task_completion_item = closure_completion_item.clone();
9167 counter_clone.fetch_add(1, atomic::Ordering::Release);
9168 async move {
9169 Ok(Some(lsp::CompletionResponse::Array(vec![
9170 task_completion_item,
9171 ])))
9172 }
9173 });
9174
9175 cx.condition(|editor, _| editor.context_menu_visible())
9176 .await;
9177 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9178 assert!(request.next().await.is_some());
9179 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9180
9181 cx.simulate_keystroke("S");
9182 cx.simulate_keystroke("o");
9183 cx.simulate_keystroke("m");
9184 cx.condition(|editor, _| editor.context_menu_visible())
9185 .await;
9186 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9187 assert!(request.next().await.is_some());
9188 assert!(request.next().await.is_some());
9189 assert!(request.next().await.is_some());
9190 request.close();
9191 assert!(request.next().await.is_none());
9192 assert_eq!(
9193 counter.load(atomic::Ordering::Acquire),
9194 4,
9195 "With the completions menu open, only one LSP request should happen per input"
9196 );
9197}
9198
9199#[gpui::test]
9200async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
9201 init_test(cx, |_| {});
9202 let mut cx = EditorTestContext::new(cx).await;
9203 let language = Arc::new(Language::new(
9204 LanguageConfig {
9205 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9206 ..Default::default()
9207 },
9208 Some(tree_sitter_rust::LANGUAGE.into()),
9209 ));
9210 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9211
9212 // If multiple selections intersect a line, the line is only toggled once.
9213 cx.set_state(indoc! {"
9214 fn a() {
9215 «//b();
9216 ˇ»// «c();
9217 //ˇ» d();
9218 }
9219 "});
9220
9221 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9222
9223 cx.assert_editor_state(indoc! {"
9224 fn a() {
9225 «b();
9226 c();
9227 ˇ» d();
9228 }
9229 "});
9230
9231 // The comment prefix is inserted at the same column for every line in a
9232 // selection.
9233 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9234
9235 cx.assert_editor_state(indoc! {"
9236 fn a() {
9237 // «b();
9238 // c();
9239 ˇ»// d();
9240 }
9241 "});
9242
9243 // If a selection ends at the beginning of a line, that line is not toggled.
9244 cx.set_selections_state(indoc! {"
9245 fn a() {
9246 // b();
9247 «// c();
9248 ˇ» // d();
9249 }
9250 "});
9251
9252 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9253
9254 cx.assert_editor_state(indoc! {"
9255 fn a() {
9256 // b();
9257 «c();
9258 ˇ» // d();
9259 }
9260 "});
9261
9262 // If a selection span a single line and is empty, the line is toggled.
9263 cx.set_state(indoc! {"
9264 fn a() {
9265 a();
9266 b();
9267 ˇ
9268 }
9269 "});
9270
9271 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9272
9273 cx.assert_editor_state(indoc! {"
9274 fn a() {
9275 a();
9276 b();
9277 //•ˇ
9278 }
9279 "});
9280
9281 // If a selection span multiple lines, empty lines are not toggled.
9282 cx.set_state(indoc! {"
9283 fn a() {
9284 «a();
9285
9286 c();ˇ»
9287 }
9288 "});
9289
9290 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9291
9292 cx.assert_editor_state(indoc! {"
9293 fn a() {
9294 // «a();
9295
9296 // c();ˇ»
9297 }
9298 "});
9299
9300 // If a selection includes multiple comment prefixes, all lines are uncommented.
9301 cx.set_state(indoc! {"
9302 fn a() {
9303 «// a();
9304 /// b();
9305 //! c();ˇ»
9306 }
9307 "});
9308
9309 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9310
9311 cx.assert_editor_state(indoc! {"
9312 fn a() {
9313 «a();
9314 b();
9315 c();ˇ»
9316 }
9317 "});
9318}
9319
9320#[gpui::test]
9321async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
9322 init_test(cx, |_| {});
9323 let mut cx = EditorTestContext::new(cx).await;
9324 let language = Arc::new(Language::new(
9325 LanguageConfig {
9326 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9327 ..Default::default()
9328 },
9329 Some(tree_sitter_rust::LANGUAGE.into()),
9330 ));
9331 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9332
9333 let toggle_comments = &ToggleComments {
9334 advance_downwards: false,
9335 ignore_indent: true,
9336 };
9337
9338 // If multiple selections intersect a line, the line is only toggled once.
9339 cx.set_state(indoc! {"
9340 fn a() {
9341 // «b();
9342 // c();
9343 // ˇ» d();
9344 }
9345 "});
9346
9347 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9348
9349 cx.assert_editor_state(indoc! {"
9350 fn a() {
9351 «b();
9352 c();
9353 ˇ» d();
9354 }
9355 "});
9356
9357 // The comment prefix is inserted at the beginning of each line
9358 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9359
9360 cx.assert_editor_state(indoc! {"
9361 fn a() {
9362 // «b();
9363 // c();
9364 // ˇ» d();
9365 }
9366 "});
9367
9368 // If a selection ends at the beginning of a line, that line is not toggled.
9369 cx.set_selections_state(indoc! {"
9370 fn a() {
9371 // b();
9372 // «c();
9373 ˇ»// d();
9374 }
9375 "});
9376
9377 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9378
9379 cx.assert_editor_state(indoc! {"
9380 fn a() {
9381 // b();
9382 «c();
9383 ˇ»// d();
9384 }
9385 "});
9386
9387 // If a selection span a single line and is empty, the line is toggled.
9388 cx.set_state(indoc! {"
9389 fn a() {
9390 a();
9391 b();
9392 ˇ
9393 }
9394 "});
9395
9396 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9397
9398 cx.assert_editor_state(indoc! {"
9399 fn a() {
9400 a();
9401 b();
9402 //ˇ
9403 }
9404 "});
9405
9406 // If a selection span multiple lines, empty lines are not toggled.
9407 cx.set_state(indoc! {"
9408 fn a() {
9409 «a();
9410
9411 c();ˇ»
9412 }
9413 "});
9414
9415 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9416
9417 cx.assert_editor_state(indoc! {"
9418 fn a() {
9419 // «a();
9420
9421 // c();ˇ»
9422 }
9423 "});
9424
9425 // If a selection includes multiple comment prefixes, all lines are uncommented.
9426 cx.set_state(indoc! {"
9427 fn a() {
9428 // «a();
9429 /// b();
9430 //! c();ˇ»
9431 }
9432 "});
9433
9434 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9435
9436 cx.assert_editor_state(indoc! {"
9437 fn a() {
9438 «a();
9439 b();
9440 c();ˇ»
9441 }
9442 "});
9443}
9444
9445#[gpui::test]
9446async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
9447 init_test(cx, |_| {});
9448
9449 let language = Arc::new(Language::new(
9450 LanguageConfig {
9451 line_comments: vec!["// ".into()],
9452 ..Default::default()
9453 },
9454 Some(tree_sitter_rust::LANGUAGE.into()),
9455 ));
9456
9457 let mut cx = EditorTestContext::new(cx).await;
9458
9459 cx.language_registry().add(language.clone());
9460 cx.update_buffer(|buffer, cx| {
9461 buffer.set_language(Some(language), cx);
9462 });
9463
9464 let toggle_comments = &ToggleComments {
9465 advance_downwards: true,
9466 ignore_indent: false,
9467 };
9468
9469 // Single cursor on one line -> advance
9470 // Cursor moves horizontally 3 characters as well on non-blank line
9471 cx.set_state(indoc!(
9472 "fn a() {
9473 ˇdog();
9474 cat();
9475 }"
9476 ));
9477 cx.update_editor(|editor, window, cx| {
9478 editor.toggle_comments(toggle_comments, window, cx);
9479 });
9480 cx.assert_editor_state(indoc!(
9481 "fn a() {
9482 // dog();
9483 catˇ();
9484 }"
9485 ));
9486
9487 // Single selection on one line -> don't advance
9488 cx.set_state(indoc!(
9489 "fn a() {
9490 «dog()ˇ»;
9491 cat();
9492 }"
9493 ));
9494 cx.update_editor(|editor, window, cx| {
9495 editor.toggle_comments(toggle_comments, window, cx);
9496 });
9497 cx.assert_editor_state(indoc!(
9498 "fn a() {
9499 // «dog()ˇ»;
9500 cat();
9501 }"
9502 ));
9503
9504 // Multiple cursors on one line -> advance
9505 cx.set_state(indoc!(
9506 "fn a() {
9507 ˇdˇog();
9508 cat();
9509 }"
9510 ));
9511 cx.update_editor(|editor, window, cx| {
9512 editor.toggle_comments(toggle_comments, window, cx);
9513 });
9514 cx.assert_editor_state(indoc!(
9515 "fn a() {
9516 // dog();
9517 catˇ(ˇ);
9518 }"
9519 ));
9520
9521 // Multiple cursors on one line, with selection -> don't advance
9522 cx.set_state(indoc!(
9523 "fn a() {
9524 ˇdˇog«()ˇ»;
9525 cat();
9526 }"
9527 ));
9528 cx.update_editor(|editor, window, cx| {
9529 editor.toggle_comments(toggle_comments, window, cx);
9530 });
9531 cx.assert_editor_state(indoc!(
9532 "fn a() {
9533 // ˇdˇog«()ˇ»;
9534 cat();
9535 }"
9536 ));
9537
9538 // Single cursor on one line -> advance
9539 // Cursor moves to column 0 on blank line
9540 cx.set_state(indoc!(
9541 "fn a() {
9542 ˇdog();
9543
9544 cat();
9545 }"
9546 ));
9547 cx.update_editor(|editor, window, cx| {
9548 editor.toggle_comments(toggle_comments, window, cx);
9549 });
9550 cx.assert_editor_state(indoc!(
9551 "fn a() {
9552 // dog();
9553 ˇ
9554 cat();
9555 }"
9556 ));
9557
9558 // Single cursor on one line -> advance
9559 // Cursor starts and ends at column 0
9560 cx.set_state(indoc!(
9561 "fn a() {
9562 ˇ dog();
9563 cat();
9564 }"
9565 ));
9566 cx.update_editor(|editor, window, cx| {
9567 editor.toggle_comments(toggle_comments, window, cx);
9568 });
9569 cx.assert_editor_state(indoc!(
9570 "fn a() {
9571 // dog();
9572 ˇ cat();
9573 }"
9574 ));
9575}
9576
9577#[gpui::test]
9578async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9579 init_test(cx, |_| {});
9580
9581 let mut cx = EditorTestContext::new(cx).await;
9582
9583 let html_language = Arc::new(
9584 Language::new(
9585 LanguageConfig {
9586 name: "HTML".into(),
9587 block_comment: Some(("<!-- ".into(), " -->".into())),
9588 ..Default::default()
9589 },
9590 Some(tree_sitter_html::language()),
9591 )
9592 .with_injection_query(
9593 r#"
9594 (script_element
9595 (raw_text) @injection.content
9596 (#set! injection.language "javascript"))
9597 "#,
9598 )
9599 .unwrap(),
9600 );
9601
9602 let javascript_language = Arc::new(Language::new(
9603 LanguageConfig {
9604 name: "JavaScript".into(),
9605 line_comments: vec!["// ".into()],
9606 ..Default::default()
9607 },
9608 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9609 ));
9610
9611 cx.language_registry().add(html_language.clone());
9612 cx.language_registry().add(javascript_language.clone());
9613 cx.update_buffer(|buffer, cx| {
9614 buffer.set_language(Some(html_language), cx);
9615 });
9616
9617 // Toggle comments for empty selections
9618 cx.set_state(
9619 &r#"
9620 <p>A</p>ˇ
9621 <p>B</p>ˇ
9622 <p>C</p>ˇ
9623 "#
9624 .unindent(),
9625 );
9626 cx.update_editor(|editor, window, cx| {
9627 editor.toggle_comments(&ToggleComments::default(), window, cx)
9628 });
9629 cx.assert_editor_state(
9630 &r#"
9631 <!-- <p>A</p>ˇ -->
9632 <!-- <p>B</p>ˇ -->
9633 <!-- <p>C</p>ˇ -->
9634 "#
9635 .unindent(),
9636 );
9637 cx.update_editor(|editor, window, cx| {
9638 editor.toggle_comments(&ToggleComments::default(), window, cx)
9639 });
9640 cx.assert_editor_state(
9641 &r#"
9642 <p>A</p>ˇ
9643 <p>B</p>ˇ
9644 <p>C</p>ˇ
9645 "#
9646 .unindent(),
9647 );
9648
9649 // Toggle comments for mixture of empty and non-empty selections, where
9650 // multiple selections occupy a given line.
9651 cx.set_state(
9652 &r#"
9653 <p>A«</p>
9654 <p>ˇ»B</p>ˇ
9655 <p>C«</p>
9656 <p>ˇ»D</p>ˇ
9657 "#
9658 .unindent(),
9659 );
9660
9661 cx.update_editor(|editor, window, cx| {
9662 editor.toggle_comments(&ToggleComments::default(), window, cx)
9663 });
9664 cx.assert_editor_state(
9665 &r#"
9666 <!-- <p>A«</p>
9667 <p>ˇ»B</p>ˇ -->
9668 <!-- <p>C«</p>
9669 <p>ˇ»D</p>ˇ -->
9670 "#
9671 .unindent(),
9672 );
9673 cx.update_editor(|editor, window, cx| {
9674 editor.toggle_comments(&ToggleComments::default(), window, cx)
9675 });
9676 cx.assert_editor_state(
9677 &r#"
9678 <p>A«</p>
9679 <p>ˇ»B</p>ˇ
9680 <p>C«</p>
9681 <p>ˇ»D</p>ˇ
9682 "#
9683 .unindent(),
9684 );
9685
9686 // Toggle comments when different languages are active for different
9687 // selections.
9688 cx.set_state(
9689 &r#"
9690 ˇ<script>
9691 ˇvar x = new Y();
9692 ˇ</script>
9693 "#
9694 .unindent(),
9695 );
9696 cx.executor().run_until_parked();
9697 cx.update_editor(|editor, window, cx| {
9698 editor.toggle_comments(&ToggleComments::default(), window, cx)
9699 });
9700 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9701 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9702 cx.assert_editor_state(
9703 &r#"
9704 <!-- ˇ<script> -->
9705 // ˇvar x = new Y();
9706 // ˇ</script>
9707 "#
9708 .unindent(),
9709 );
9710}
9711
9712#[gpui::test]
9713fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9714 init_test(cx, |_| {});
9715
9716 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9717 let multibuffer = cx.new(|cx| {
9718 let mut multibuffer = MultiBuffer::new(ReadWrite);
9719 multibuffer.push_excerpts(
9720 buffer.clone(),
9721 [
9722 ExcerptRange {
9723 context: Point::new(0, 0)..Point::new(0, 4),
9724 primary: None,
9725 },
9726 ExcerptRange {
9727 context: Point::new(1, 0)..Point::new(1, 4),
9728 primary: None,
9729 },
9730 ],
9731 cx,
9732 );
9733 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9734 multibuffer
9735 });
9736
9737 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9738 editor.update_in(cx, |editor, window, cx| {
9739 assert_eq!(editor.text(cx), "aaaa\nbbbb");
9740 editor.change_selections(None, window, cx, |s| {
9741 s.select_ranges([
9742 Point::new(0, 0)..Point::new(0, 0),
9743 Point::new(1, 0)..Point::new(1, 0),
9744 ])
9745 });
9746
9747 editor.handle_input("X", window, cx);
9748 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
9749 assert_eq!(
9750 editor.selections.ranges(cx),
9751 [
9752 Point::new(0, 1)..Point::new(0, 1),
9753 Point::new(1, 1)..Point::new(1, 1),
9754 ]
9755 );
9756
9757 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9758 editor.change_selections(None, window, cx, |s| {
9759 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9760 });
9761 editor.backspace(&Default::default(), window, cx);
9762 assert_eq!(editor.text(cx), "Xa\nbbb");
9763 assert_eq!(
9764 editor.selections.ranges(cx),
9765 [Point::new(1, 0)..Point::new(1, 0)]
9766 );
9767
9768 editor.change_selections(None, window, cx, |s| {
9769 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9770 });
9771 editor.backspace(&Default::default(), window, cx);
9772 assert_eq!(editor.text(cx), "X\nbb");
9773 assert_eq!(
9774 editor.selections.ranges(cx),
9775 [Point::new(0, 1)..Point::new(0, 1)]
9776 );
9777 });
9778}
9779
9780#[gpui::test]
9781fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9782 init_test(cx, |_| {});
9783
9784 let markers = vec![('[', ']').into(), ('(', ')').into()];
9785 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9786 indoc! {"
9787 [aaaa
9788 (bbbb]
9789 cccc)",
9790 },
9791 markers.clone(),
9792 );
9793 let excerpt_ranges = markers.into_iter().map(|marker| {
9794 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9795 ExcerptRange {
9796 context,
9797 primary: None,
9798 }
9799 });
9800 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
9801 let multibuffer = cx.new(|cx| {
9802 let mut multibuffer = MultiBuffer::new(ReadWrite);
9803 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9804 multibuffer
9805 });
9806
9807 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
9808 editor.update_in(cx, |editor, window, cx| {
9809 let (expected_text, selection_ranges) = marked_text_ranges(
9810 indoc! {"
9811 aaaa
9812 bˇbbb
9813 bˇbbˇb
9814 cccc"
9815 },
9816 true,
9817 );
9818 assert_eq!(editor.text(cx), expected_text);
9819 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
9820
9821 editor.handle_input("X", window, cx);
9822
9823 let (expected_text, expected_selections) = marked_text_ranges(
9824 indoc! {"
9825 aaaa
9826 bXˇbbXb
9827 bXˇbbXˇb
9828 cccc"
9829 },
9830 false,
9831 );
9832 assert_eq!(editor.text(cx), expected_text);
9833 assert_eq!(editor.selections.ranges(cx), expected_selections);
9834
9835 editor.newline(&Newline, window, cx);
9836 let (expected_text, expected_selections) = marked_text_ranges(
9837 indoc! {"
9838 aaaa
9839 bX
9840 ˇbbX
9841 b
9842 bX
9843 ˇbbX
9844 ˇb
9845 cccc"
9846 },
9847 false,
9848 );
9849 assert_eq!(editor.text(cx), expected_text);
9850 assert_eq!(editor.selections.ranges(cx), expected_selections);
9851 });
9852}
9853
9854#[gpui::test]
9855fn test_refresh_selections(cx: &mut TestAppContext) {
9856 init_test(cx, |_| {});
9857
9858 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9859 let mut excerpt1_id = None;
9860 let multibuffer = cx.new(|cx| {
9861 let mut multibuffer = MultiBuffer::new(ReadWrite);
9862 excerpt1_id = multibuffer
9863 .push_excerpts(
9864 buffer.clone(),
9865 [
9866 ExcerptRange {
9867 context: Point::new(0, 0)..Point::new(1, 4),
9868 primary: None,
9869 },
9870 ExcerptRange {
9871 context: Point::new(1, 0)..Point::new(2, 4),
9872 primary: None,
9873 },
9874 ],
9875 cx,
9876 )
9877 .into_iter()
9878 .next();
9879 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9880 multibuffer
9881 });
9882
9883 let editor = cx.add_window(|window, cx| {
9884 let mut editor = build_editor(multibuffer.clone(), window, cx);
9885 let snapshot = editor.snapshot(window, cx);
9886 editor.change_selections(None, window, cx, |s| {
9887 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9888 });
9889 editor.begin_selection(
9890 Point::new(2, 1).to_display_point(&snapshot),
9891 true,
9892 1,
9893 window,
9894 cx,
9895 );
9896 assert_eq!(
9897 editor.selections.ranges(cx),
9898 [
9899 Point::new(1, 3)..Point::new(1, 3),
9900 Point::new(2, 1)..Point::new(2, 1),
9901 ]
9902 );
9903 editor
9904 });
9905
9906 // Refreshing selections is a no-op when excerpts haven't changed.
9907 _ = editor.update(cx, |editor, window, cx| {
9908 editor.change_selections(None, window, cx, |s| s.refresh());
9909 assert_eq!(
9910 editor.selections.ranges(cx),
9911 [
9912 Point::new(1, 3)..Point::new(1, 3),
9913 Point::new(2, 1)..Point::new(2, 1),
9914 ]
9915 );
9916 });
9917
9918 multibuffer.update(cx, |multibuffer, cx| {
9919 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9920 });
9921 _ = editor.update(cx, |editor, window, cx| {
9922 // Removing an excerpt causes the first selection to become degenerate.
9923 assert_eq!(
9924 editor.selections.ranges(cx),
9925 [
9926 Point::new(0, 0)..Point::new(0, 0),
9927 Point::new(0, 1)..Point::new(0, 1)
9928 ]
9929 );
9930
9931 // Refreshing selections will relocate the first selection to the original buffer
9932 // location.
9933 editor.change_selections(None, window, cx, |s| s.refresh());
9934 assert_eq!(
9935 editor.selections.ranges(cx),
9936 [
9937 Point::new(0, 1)..Point::new(0, 1),
9938 Point::new(0, 3)..Point::new(0, 3)
9939 ]
9940 );
9941 assert!(editor.selections.pending_anchor().is_some());
9942 });
9943}
9944
9945#[gpui::test]
9946fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9947 init_test(cx, |_| {});
9948
9949 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9950 let mut excerpt1_id = None;
9951 let multibuffer = cx.new(|cx| {
9952 let mut multibuffer = MultiBuffer::new(ReadWrite);
9953 excerpt1_id = multibuffer
9954 .push_excerpts(
9955 buffer.clone(),
9956 [
9957 ExcerptRange {
9958 context: Point::new(0, 0)..Point::new(1, 4),
9959 primary: None,
9960 },
9961 ExcerptRange {
9962 context: Point::new(1, 0)..Point::new(2, 4),
9963 primary: None,
9964 },
9965 ],
9966 cx,
9967 )
9968 .into_iter()
9969 .next();
9970 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9971 multibuffer
9972 });
9973
9974 let editor = cx.add_window(|window, cx| {
9975 let mut editor = build_editor(multibuffer.clone(), window, cx);
9976 let snapshot = editor.snapshot(window, cx);
9977 editor.begin_selection(
9978 Point::new(1, 3).to_display_point(&snapshot),
9979 false,
9980 1,
9981 window,
9982 cx,
9983 );
9984 assert_eq!(
9985 editor.selections.ranges(cx),
9986 [Point::new(1, 3)..Point::new(1, 3)]
9987 );
9988 editor
9989 });
9990
9991 multibuffer.update(cx, |multibuffer, cx| {
9992 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9993 });
9994 _ = editor.update(cx, |editor, window, cx| {
9995 assert_eq!(
9996 editor.selections.ranges(cx),
9997 [Point::new(0, 0)..Point::new(0, 0)]
9998 );
9999
10000 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10001 editor.change_selections(None, window, cx, |s| s.refresh());
10002 assert_eq!(
10003 editor.selections.ranges(cx),
10004 [Point::new(0, 3)..Point::new(0, 3)]
10005 );
10006 assert!(editor.selections.pending_anchor().is_some());
10007 });
10008}
10009
10010#[gpui::test]
10011async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
10012 init_test(cx, |_| {});
10013
10014 let language = Arc::new(
10015 Language::new(
10016 LanguageConfig {
10017 brackets: BracketPairConfig {
10018 pairs: vec![
10019 BracketPair {
10020 start: "{".to_string(),
10021 end: "}".to_string(),
10022 close: true,
10023 surround: true,
10024 newline: true,
10025 },
10026 BracketPair {
10027 start: "/* ".to_string(),
10028 end: " */".to_string(),
10029 close: true,
10030 surround: true,
10031 newline: true,
10032 },
10033 ],
10034 ..Default::default()
10035 },
10036 ..Default::default()
10037 },
10038 Some(tree_sitter_rust::LANGUAGE.into()),
10039 )
10040 .with_indents_query("")
10041 .unwrap(),
10042 );
10043
10044 let text = concat!(
10045 "{ }\n", //
10046 " x\n", //
10047 " /* */\n", //
10048 "x\n", //
10049 "{{} }\n", //
10050 );
10051
10052 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10053 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10054 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10055 editor
10056 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10057 .await;
10058
10059 editor.update_in(cx, |editor, window, cx| {
10060 editor.change_selections(None, window, cx, |s| {
10061 s.select_display_ranges([
10062 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10063 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10064 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10065 ])
10066 });
10067 editor.newline(&Newline, window, cx);
10068
10069 assert_eq!(
10070 editor.buffer().read(cx).read(cx).text(),
10071 concat!(
10072 "{ \n", // Suppress rustfmt
10073 "\n", //
10074 "}\n", //
10075 " x\n", //
10076 " /* \n", //
10077 " \n", //
10078 " */\n", //
10079 "x\n", //
10080 "{{} \n", //
10081 "}\n", //
10082 )
10083 );
10084 });
10085}
10086
10087#[gpui::test]
10088fn test_highlighted_ranges(cx: &mut TestAppContext) {
10089 init_test(cx, |_| {});
10090
10091 let editor = cx.add_window(|window, cx| {
10092 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10093 build_editor(buffer.clone(), window, cx)
10094 });
10095
10096 _ = editor.update(cx, |editor, window, cx| {
10097 struct Type1;
10098 struct Type2;
10099
10100 let buffer = editor.buffer.read(cx).snapshot(cx);
10101
10102 let anchor_range =
10103 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10104
10105 editor.highlight_background::<Type1>(
10106 &[
10107 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10108 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10109 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10110 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10111 ],
10112 |_| Hsla::red(),
10113 cx,
10114 );
10115 editor.highlight_background::<Type2>(
10116 &[
10117 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10118 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10119 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10120 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10121 ],
10122 |_| Hsla::green(),
10123 cx,
10124 );
10125
10126 let snapshot = editor.snapshot(window, cx);
10127 let mut highlighted_ranges = editor.background_highlights_in_range(
10128 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10129 &snapshot,
10130 cx.theme().colors(),
10131 );
10132 // Enforce a consistent ordering based on color without relying on the ordering of the
10133 // highlight's `TypeId` which is non-executor.
10134 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10135 assert_eq!(
10136 highlighted_ranges,
10137 &[
10138 (
10139 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10140 Hsla::red(),
10141 ),
10142 (
10143 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10144 Hsla::red(),
10145 ),
10146 (
10147 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10148 Hsla::green(),
10149 ),
10150 (
10151 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10152 Hsla::green(),
10153 ),
10154 ]
10155 );
10156 assert_eq!(
10157 editor.background_highlights_in_range(
10158 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10159 &snapshot,
10160 cx.theme().colors(),
10161 ),
10162 &[(
10163 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10164 Hsla::red(),
10165 )]
10166 );
10167 });
10168}
10169
10170#[gpui::test]
10171async fn test_following(cx: &mut gpui::TestAppContext) {
10172 init_test(cx, |_| {});
10173
10174 let fs = FakeFs::new(cx.executor());
10175 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10176
10177 let buffer = project.update(cx, |project, cx| {
10178 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10179 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10180 });
10181 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10182 let follower = cx.update(|cx| {
10183 cx.open_window(
10184 WindowOptions {
10185 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10186 gpui::Point::new(px(0.), px(0.)),
10187 gpui::Point::new(px(10.), px(80.)),
10188 ))),
10189 ..Default::default()
10190 },
10191 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10192 )
10193 .unwrap()
10194 });
10195
10196 let is_still_following = Rc::new(RefCell::new(true));
10197 let follower_edit_event_count = Rc::new(RefCell::new(0));
10198 let pending_update = Rc::new(RefCell::new(None));
10199 let leader_model = leader.root(cx).unwrap();
10200 let follower_model = follower.root(cx).unwrap();
10201 _ = follower.update(cx, {
10202 let update = pending_update.clone();
10203 let is_still_following = is_still_following.clone();
10204 let follower_edit_event_count = follower_edit_event_count.clone();
10205 |_, window, cx| {
10206 cx.subscribe_in(
10207 &leader_model,
10208 window,
10209 move |_, leader, event, window, cx| {
10210 leader.read(cx).add_event_to_update_proto(
10211 event,
10212 &mut update.borrow_mut(),
10213 window,
10214 cx,
10215 );
10216 },
10217 )
10218 .detach();
10219
10220 cx.subscribe_in(
10221 &follower_model,
10222 window,
10223 move |_, _, event: &EditorEvent, _window, _cx| {
10224 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10225 *is_still_following.borrow_mut() = false;
10226 }
10227
10228 if let EditorEvent::BufferEdited = event {
10229 *follower_edit_event_count.borrow_mut() += 1;
10230 }
10231 },
10232 )
10233 .detach();
10234 }
10235 });
10236
10237 // Update the selections only
10238 _ = leader.update(cx, |leader, window, cx| {
10239 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10240 });
10241 follower
10242 .update(cx, |follower, window, cx| {
10243 follower.apply_update_proto(
10244 &project,
10245 pending_update.borrow_mut().take().unwrap(),
10246 window,
10247 cx,
10248 )
10249 })
10250 .unwrap()
10251 .await
10252 .unwrap();
10253 _ = follower.update(cx, |follower, _, cx| {
10254 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10255 });
10256 assert!(*is_still_following.borrow());
10257 assert_eq!(*follower_edit_event_count.borrow(), 0);
10258
10259 // Update the scroll position only
10260 _ = leader.update(cx, |leader, window, cx| {
10261 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10262 });
10263 follower
10264 .update(cx, |follower, window, cx| {
10265 follower.apply_update_proto(
10266 &project,
10267 pending_update.borrow_mut().take().unwrap(),
10268 window,
10269 cx,
10270 )
10271 })
10272 .unwrap()
10273 .await
10274 .unwrap();
10275 assert_eq!(
10276 follower
10277 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10278 .unwrap(),
10279 gpui::Point::new(1.5, 3.5)
10280 );
10281 assert!(*is_still_following.borrow());
10282 assert_eq!(*follower_edit_event_count.borrow(), 0);
10283
10284 // Update the selections and scroll position. The follower's scroll position is updated
10285 // via autoscroll, not via the leader's exact scroll position.
10286 _ = leader.update(cx, |leader, window, cx| {
10287 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10288 leader.request_autoscroll(Autoscroll::newest(), cx);
10289 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10290 });
10291 follower
10292 .update(cx, |follower, window, cx| {
10293 follower.apply_update_proto(
10294 &project,
10295 pending_update.borrow_mut().take().unwrap(),
10296 window,
10297 cx,
10298 )
10299 })
10300 .unwrap()
10301 .await
10302 .unwrap();
10303 _ = follower.update(cx, |follower, _, cx| {
10304 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10305 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10306 });
10307 assert!(*is_still_following.borrow());
10308
10309 // Creating a pending selection that precedes another selection
10310 _ = leader.update(cx, |leader, window, cx| {
10311 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10312 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10313 });
10314 follower
10315 .update(cx, |follower, window, cx| {
10316 follower.apply_update_proto(
10317 &project,
10318 pending_update.borrow_mut().take().unwrap(),
10319 window,
10320 cx,
10321 )
10322 })
10323 .unwrap()
10324 .await
10325 .unwrap();
10326 _ = follower.update(cx, |follower, _, cx| {
10327 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10328 });
10329 assert!(*is_still_following.borrow());
10330
10331 // Extend the pending selection so that it surrounds another selection
10332 _ = leader.update(cx, |leader, window, cx| {
10333 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10334 });
10335 follower
10336 .update(cx, |follower, window, cx| {
10337 follower.apply_update_proto(
10338 &project,
10339 pending_update.borrow_mut().take().unwrap(),
10340 window,
10341 cx,
10342 )
10343 })
10344 .unwrap()
10345 .await
10346 .unwrap();
10347 _ = follower.update(cx, |follower, _, cx| {
10348 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10349 });
10350
10351 // Scrolling locally breaks the follow
10352 _ = follower.update(cx, |follower, window, cx| {
10353 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10354 follower.set_scroll_anchor(
10355 ScrollAnchor {
10356 anchor: top_anchor,
10357 offset: gpui::Point::new(0.0, 0.5),
10358 },
10359 window,
10360 cx,
10361 );
10362 });
10363 assert!(!(*is_still_following.borrow()));
10364}
10365
10366#[gpui::test]
10367async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
10368 init_test(cx, |_| {});
10369
10370 let fs = FakeFs::new(cx.executor());
10371 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10372 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10373 let pane = workspace
10374 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10375 .unwrap();
10376
10377 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10378
10379 let leader = pane.update_in(cx, |_, window, cx| {
10380 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10381 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10382 });
10383
10384 // Start following the editor when it has no excerpts.
10385 let mut state_message =
10386 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10387 let workspace_model = workspace.root(cx).unwrap();
10388 let follower_1 = cx
10389 .update_window(*workspace.deref(), |_, window, cx| {
10390 Editor::from_state_proto(
10391 workspace_model,
10392 ViewId {
10393 creator: Default::default(),
10394 id: 0,
10395 },
10396 &mut state_message,
10397 window,
10398 cx,
10399 )
10400 })
10401 .unwrap()
10402 .unwrap()
10403 .await
10404 .unwrap();
10405
10406 let update_message = Rc::new(RefCell::new(None));
10407 follower_1.update_in(cx, {
10408 let update = update_message.clone();
10409 |_, window, cx| {
10410 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10411 leader.read(cx).add_event_to_update_proto(
10412 event,
10413 &mut update.borrow_mut(),
10414 window,
10415 cx,
10416 );
10417 })
10418 .detach();
10419 }
10420 });
10421
10422 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10423 (
10424 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10425 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10426 )
10427 });
10428
10429 // Insert some excerpts.
10430 leader.update(cx, |leader, cx| {
10431 leader.buffer.update(cx, |multibuffer, cx| {
10432 let excerpt_ids = multibuffer.push_excerpts(
10433 buffer_1.clone(),
10434 [
10435 ExcerptRange {
10436 context: 1..6,
10437 primary: None,
10438 },
10439 ExcerptRange {
10440 context: 12..15,
10441 primary: None,
10442 },
10443 ExcerptRange {
10444 context: 0..3,
10445 primary: None,
10446 },
10447 ],
10448 cx,
10449 );
10450 multibuffer.insert_excerpts_after(
10451 excerpt_ids[0],
10452 buffer_2.clone(),
10453 [
10454 ExcerptRange {
10455 context: 8..12,
10456 primary: None,
10457 },
10458 ExcerptRange {
10459 context: 0..6,
10460 primary: None,
10461 },
10462 ],
10463 cx,
10464 );
10465 });
10466 });
10467
10468 // Apply the update of adding the excerpts.
10469 follower_1
10470 .update_in(cx, |follower, window, cx| {
10471 follower.apply_update_proto(
10472 &project,
10473 update_message.borrow().clone().unwrap(),
10474 window,
10475 cx,
10476 )
10477 })
10478 .await
10479 .unwrap();
10480 assert_eq!(
10481 follower_1.update(cx, |editor, cx| editor.text(cx)),
10482 leader.update(cx, |editor, cx| editor.text(cx))
10483 );
10484 update_message.borrow_mut().take();
10485
10486 // Start following separately after it already has excerpts.
10487 let mut state_message =
10488 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10489 let workspace_model = workspace.root(cx).unwrap();
10490 let follower_2 = cx
10491 .update_window(*workspace.deref(), |_, window, cx| {
10492 Editor::from_state_proto(
10493 workspace_model,
10494 ViewId {
10495 creator: Default::default(),
10496 id: 0,
10497 },
10498 &mut state_message,
10499 window,
10500 cx,
10501 )
10502 })
10503 .unwrap()
10504 .unwrap()
10505 .await
10506 .unwrap();
10507 assert_eq!(
10508 follower_2.update(cx, |editor, cx| editor.text(cx)),
10509 leader.update(cx, |editor, cx| editor.text(cx))
10510 );
10511
10512 // Remove some excerpts.
10513 leader.update(cx, |leader, cx| {
10514 leader.buffer.update(cx, |multibuffer, cx| {
10515 let excerpt_ids = multibuffer.excerpt_ids();
10516 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10517 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10518 });
10519 });
10520
10521 // Apply the update of removing the excerpts.
10522 follower_1
10523 .update_in(cx, |follower, window, cx| {
10524 follower.apply_update_proto(
10525 &project,
10526 update_message.borrow().clone().unwrap(),
10527 window,
10528 cx,
10529 )
10530 })
10531 .await
10532 .unwrap();
10533 follower_2
10534 .update_in(cx, |follower, window, cx| {
10535 follower.apply_update_proto(
10536 &project,
10537 update_message.borrow().clone().unwrap(),
10538 window,
10539 cx,
10540 )
10541 })
10542 .await
10543 .unwrap();
10544 update_message.borrow_mut().take();
10545 assert_eq!(
10546 follower_1.update(cx, |editor, cx| editor.text(cx)),
10547 leader.update(cx, |editor, cx| editor.text(cx))
10548 );
10549}
10550
10551#[gpui::test]
10552async fn go_to_prev_overlapping_diagnostic(
10553 executor: BackgroundExecutor,
10554 cx: &mut gpui::TestAppContext,
10555) {
10556 init_test(cx, |_| {});
10557
10558 let mut cx = EditorTestContext::new(cx).await;
10559 let lsp_store =
10560 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10561
10562 cx.set_state(indoc! {"
10563 ˇfn func(abc def: i32) -> u32 {
10564 }
10565 "});
10566
10567 cx.update(|_, cx| {
10568 lsp_store.update(cx, |lsp_store, cx| {
10569 lsp_store
10570 .update_diagnostics(
10571 LanguageServerId(0),
10572 lsp::PublishDiagnosticsParams {
10573 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10574 version: None,
10575 diagnostics: vec![
10576 lsp::Diagnostic {
10577 range: lsp::Range::new(
10578 lsp::Position::new(0, 11),
10579 lsp::Position::new(0, 12),
10580 ),
10581 severity: Some(lsp::DiagnosticSeverity::ERROR),
10582 ..Default::default()
10583 },
10584 lsp::Diagnostic {
10585 range: lsp::Range::new(
10586 lsp::Position::new(0, 12),
10587 lsp::Position::new(0, 15),
10588 ),
10589 severity: Some(lsp::DiagnosticSeverity::ERROR),
10590 ..Default::default()
10591 },
10592 lsp::Diagnostic {
10593 range: lsp::Range::new(
10594 lsp::Position::new(0, 25),
10595 lsp::Position::new(0, 28),
10596 ),
10597 severity: Some(lsp::DiagnosticSeverity::ERROR),
10598 ..Default::default()
10599 },
10600 ],
10601 },
10602 &[],
10603 cx,
10604 )
10605 .unwrap()
10606 });
10607 });
10608
10609 executor.run_until_parked();
10610
10611 cx.update_editor(|editor, window, cx| {
10612 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10613 });
10614
10615 cx.assert_editor_state(indoc! {"
10616 fn func(abc def: i32) -> ˇu32 {
10617 }
10618 "});
10619
10620 cx.update_editor(|editor, window, cx| {
10621 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10622 });
10623
10624 cx.assert_editor_state(indoc! {"
10625 fn func(abc ˇdef: i32) -> u32 {
10626 }
10627 "});
10628
10629 cx.update_editor(|editor, window, cx| {
10630 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10631 });
10632
10633 cx.assert_editor_state(indoc! {"
10634 fn func(abcˇ def: i32) -> u32 {
10635 }
10636 "});
10637
10638 cx.update_editor(|editor, window, cx| {
10639 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
10640 });
10641
10642 cx.assert_editor_state(indoc! {"
10643 fn func(abc def: i32) -> ˇu32 {
10644 }
10645 "});
10646}
10647
10648#[gpui::test]
10649async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10650 init_test(cx, |_| {});
10651
10652 let mut cx = EditorTestContext::new(cx).await;
10653
10654 cx.set_state(indoc! {"
10655 fn func(abˇc def: i32) -> u32 {
10656 }
10657 "});
10658 let lsp_store =
10659 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10660
10661 cx.update(|_, cx| {
10662 lsp_store.update(cx, |lsp_store, cx| {
10663 lsp_store.update_diagnostics(
10664 LanguageServerId(0),
10665 lsp::PublishDiagnosticsParams {
10666 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10667 version: None,
10668 diagnostics: vec![lsp::Diagnostic {
10669 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10670 severity: Some(lsp::DiagnosticSeverity::ERROR),
10671 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10672 ..Default::default()
10673 }],
10674 },
10675 &[],
10676 cx,
10677 )
10678 })
10679 }).unwrap();
10680 cx.run_until_parked();
10681 cx.update_editor(|editor, window, cx| {
10682 hover_popover::hover(editor, &Default::default(), window, cx)
10683 });
10684 cx.run_until_parked();
10685 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10686}
10687
10688#[gpui::test]
10689async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10690 init_test(cx, |_| {});
10691
10692 let mut cx = EditorTestContext::new(cx).await;
10693
10694 let diff_base = r#"
10695 use some::mod;
10696
10697 const A: u32 = 42;
10698
10699 fn main() {
10700 println!("hello");
10701
10702 println!("world");
10703 }
10704 "#
10705 .unindent();
10706
10707 // Edits are modified, removed, modified, added
10708 cx.set_state(
10709 &r#"
10710 use some::modified;
10711
10712 ˇ
10713 fn main() {
10714 println!("hello there");
10715
10716 println!("around the");
10717 println!("world");
10718 }
10719 "#
10720 .unindent(),
10721 );
10722
10723 cx.set_diff_base(&diff_base);
10724 executor.run_until_parked();
10725
10726 cx.update_editor(|editor, window, cx| {
10727 //Wrap around the bottom of the buffer
10728 for _ in 0..3 {
10729 editor.go_to_next_hunk(&GoToHunk, window, cx);
10730 }
10731 });
10732
10733 cx.assert_editor_state(
10734 &r#"
10735 ˇuse some::modified;
10736
10737
10738 fn main() {
10739 println!("hello there");
10740
10741 println!("around the");
10742 println!("world");
10743 }
10744 "#
10745 .unindent(),
10746 );
10747
10748 cx.update_editor(|editor, window, cx| {
10749 //Wrap around the top of the buffer
10750 for _ in 0..2 {
10751 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10752 }
10753 });
10754
10755 cx.assert_editor_state(
10756 &r#"
10757 use some::modified;
10758
10759
10760 fn main() {
10761 ˇ println!("hello there");
10762
10763 println!("around the");
10764 println!("world");
10765 }
10766 "#
10767 .unindent(),
10768 );
10769
10770 cx.update_editor(|editor, window, cx| {
10771 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10772 });
10773
10774 cx.assert_editor_state(
10775 &r#"
10776 use some::modified;
10777
10778 ˇ
10779 fn main() {
10780 println!("hello there");
10781
10782 println!("around the");
10783 println!("world");
10784 }
10785 "#
10786 .unindent(),
10787 );
10788
10789 cx.update_editor(|editor, window, cx| {
10790 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10791 });
10792
10793 cx.assert_editor_state(
10794 &r#"
10795 ˇuse some::modified;
10796
10797
10798 fn main() {
10799 println!("hello there");
10800
10801 println!("around the");
10802 println!("world");
10803 }
10804 "#
10805 .unindent(),
10806 );
10807
10808 cx.update_editor(|editor, window, cx| {
10809 for _ in 0..2 {
10810 editor.go_to_prev_hunk(&GoToPrevHunk, window, cx);
10811 }
10812 });
10813
10814 cx.assert_editor_state(
10815 &r#"
10816 use some::modified;
10817
10818
10819 fn main() {
10820 ˇ println!("hello there");
10821
10822 println!("around the");
10823 println!("world");
10824 }
10825 "#
10826 .unindent(),
10827 );
10828
10829 cx.update_editor(|editor, window, cx| {
10830 editor.fold(&Fold, window, cx);
10831 });
10832
10833 cx.update_editor(|editor, window, cx| {
10834 editor.go_to_next_hunk(&GoToHunk, window, cx);
10835 });
10836
10837 cx.assert_editor_state(
10838 &r#"
10839 ˇuse some::modified;
10840
10841
10842 fn main() {
10843 println!("hello there");
10844
10845 println!("around the");
10846 println!("world");
10847 }
10848 "#
10849 .unindent(),
10850 );
10851}
10852
10853#[test]
10854fn test_split_words() {
10855 fn split(text: &str) -> Vec<&str> {
10856 split_words(text).collect()
10857 }
10858
10859 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10860 assert_eq!(split("hello_world"), &["hello_", "world"]);
10861 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10862 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10863 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10864 assert_eq!(split("helloworld"), &["helloworld"]);
10865
10866 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10867}
10868
10869#[gpui::test]
10870async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10871 init_test(cx, |_| {});
10872
10873 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10874 let mut assert = |before, after| {
10875 let _state_context = cx.set_state(before);
10876 cx.run_until_parked();
10877 cx.update_editor(|editor, window, cx| {
10878 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
10879 });
10880 cx.assert_editor_state(after);
10881 };
10882
10883 // Outside bracket jumps to outside of matching bracket
10884 assert("console.logˇ(var);", "console.log(var)ˇ;");
10885 assert("console.log(var)ˇ;", "console.logˇ(var);");
10886
10887 // Inside bracket jumps to inside of matching bracket
10888 assert("console.log(ˇvar);", "console.log(varˇ);");
10889 assert("console.log(varˇ);", "console.log(ˇvar);");
10890
10891 // When outside a bracket and inside, favor jumping to the inside bracket
10892 assert(
10893 "console.log('foo', [1, 2, 3]ˇ);",
10894 "console.log(ˇ'foo', [1, 2, 3]);",
10895 );
10896 assert(
10897 "console.log(ˇ'foo', [1, 2, 3]);",
10898 "console.log('foo', [1, 2, 3]ˇ);",
10899 );
10900
10901 // Bias forward if two options are equally likely
10902 assert(
10903 "let result = curried_fun()ˇ();",
10904 "let result = curried_fun()()ˇ;",
10905 );
10906
10907 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10908 assert(
10909 indoc! {"
10910 function test() {
10911 console.log('test')ˇ
10912 }"},
10913 indoc! {"
10914 function test() {
10915 console.logˇ('test')
10916 }"},
10917 );
10918}
10919
10920#[gpui::test]
10921async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10922 init_test(cx, |_| {});
10923
10924 let fs = FakeFs::new(cx.executor());
10925 fs.insert_tree(
10926 "/a",
10927 json!({
10928 "main.rs": "fn main() { let a = 5; }",
10929 "other.rs": "// Test file",
10930 }),
10931 )
10932 .await;
10933 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10934
10935 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10936 language_registry.add(Arc::new(Language::new(
10937 LanguageConfig {
10938 name: "Rust".into(),
10939 matcher: LanguageMatcher {
10940 path_suffixes: vec!["rs".to_string()],
10941 ..Default::default()
10942 },
10943 brackets: BracketPairConfig {
10944 pairs: vec![BracketPair {
10945 start: "{".to_string(),
10946 end: "}".to_string(),
10947 close: true,
10948 surround: true,
10949 newline: true,
10950 }],
10951 disabled_scopes_by_bracket_ix: Vec::new(),
10952 },
10953 ..Default::default()
10954 },
10955 Some(tree_sitter_rust::LANGUAGE.into()),
10956 )));
10957 let mut fake_servers = language_registry.register_fake_lsp(
10958 "Rust",
10959 FakeLspAdapter {
10960 capabilities: lsp::ServerCapabilities {
10961 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10962 first_trigger_character: "{".to_string(),
10963 more_trigger_character: None,
10964 }),
10965 ..Default::default()
10966 },
10967 ..Default::default()
10968 },
10969 );
10970
10971 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10972
10973 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10974
10975 let worktree_id = workspace
10976 .update(cx, |workspace, _, cx| {
10977 workspace.project().update(cx, |project, cx| {
10978 project.worktrees(cx).next().unwrap().read(cx).id()
10979 })
10980 })
10981 .unwrap();
10982
10983 let buffer = project
10984 .update(cx, |project, cx| {
10985 project.open_local_buffer("/a/main.rs", cx)
10986 })
10987 .await
10988 .unwrap();
10989 let editor_handle = workspace
10990 .update(cx, |workspace, window, cx| {
10991 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
10992 })
10993 .unwrap()
10994 .await
10995 .unwrap()
10996 .downcast::<Editor>()
10997 .unwrap();
10998
10999 cx.executor().start_waiting();
11000 let fake_server = fake_servers.next().await.unwrap();
11001
11002 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11003 assert_eq!(
11004 params.text_document_position.text_document.uri,
11005 lsp::Url::from_file_path("/a/main.rs").unwrap(),
11006 );
11007 assert_eq!(
11008 params.text_document_position.position,
11009 lsp::Position::new(0, 21),
11010 );
11011
11012 Ok(Some(vec![lsp::TextEdit {
11013 new_text: "]".to_string(),
11014 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11015 }]))
11016 });
11017
11018 editor_handle.update_in(cx, |editor, window, cx| {
11019 window.focus(&editor.focus_handle(cx));
11020 editor.change_selections(None, window, cx, |s| {
11021 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11022 });
11023 editor.handle_input("{", window, cx);
11024 });
11025
11026 cx.executor().run_until_parked();
11027
11028 buffer.update(cx, |buffer, _| {
11029 assert_eq!(
11030 buffer.text(),
11031 "fn main() { let a = {5}; }",
11032 "No extra braces from on type formatting should appear in the buffer"
11033 )
11034 });
11035}
11036
11037#[gpui::test]
11038async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
11039 init_test(cx, |_| {});
11040
11041 let fs = FakeFs::new(cx.executor());
11042 fs.insert_tree(
11043 "/a",
11044 json!({
11045 "main.rs": "fn main() { let a = 5; }",
11046 "other.rs": "// Test file",
11047 }),
11048 )
11049 .await;
11050
11051 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11052
11053 let server_restarts = Arc::new(AtomicUsize::new(0));
11054 let closure_restarts = Arc::clone(&server_restarts);
11055 let language_server_name = "test language server";
11056 let language_name: LanguageName = "Rust".into();
11057
11058 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11059 language_registry.add(Arc::new(Language::new(
11060 LanguageConfig {
11061 name: language_name.clone(),
11062 matcher: LanguageMatcher {
11063 path_suffixes: vec!["rs".to_string()],
11064 ..Default::default()
11065 },
11066 ..Default::default()
11067 },
11068 Some(tree_sitter_rust::LANGUAGE.into()),
11069 )));
11070 let mut fake_servers = language_registry.register_fake_lsp(
11071 "Rust",
11072 FakeLspAdapter {
11073 name: language_server_name,
11074 initialization_options: Some(json!({
11075 "testOptionValue": true
11076 })),
11077 initializer: Some(Box::new(move |fake_server| {
11078 let task_restarts = Arc::clone(&closure_restarts);
11079 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11080 task_restarts.fetch_add(1, atomic::Ordering::Release);
11081 futures::future::ready(Ok(()))
11082 });
11083 })),
11084 ..Default::default()
11085 },
11086 );
11087
11088 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11089 let _buffer = project
11090 .update(cx, |project, cx| {
11091 project.open_local_buffer_with_lsp("/a/main.rs", cx)
11092 })
11093 .await
11094 .unwrap();
11095 let _fake_server = fake_servers.next().await.unwrap();
11096 update_test_language_settings(cx, |language_settings| {
11097 language_settings.languages.insert(
11098 language_name.clone(),
11099 LanguageSettingsContent {
11100 tab_size: NonZeroU32::new(8),
11101 ..Default::default()
11102 },
11103 );
11104 });
11105 cx.executor().run_until_parked();
11106 assert_eq!(
11107 server_restarts.load(atomic::Ordering::Acquire),
11108 0,
11109 "Should not restart LSP server on an unrelated change"
11110 );
11111
11112 update_test_project_settings(cx, |project_settings| {
11113 project_settings.lsp.insert(
11114 "Some other server name".into(),
11115 LspSettings {
11116 binary: None,
11117 settings: None,
11118 initialization_options: Some(json!({
11119 "some other init value": false
11120 })),
11121 },
11122 );
11123 });
11124 cx.executor().run_until_parked();
11125 assert_eq!(
11126 server_restarts.load(atomic::Ordering::Acquire),
11127 0,
11128 "Should not restart LSP server on an unrelated LSP settings change"
11129 );
11130
11131 update_test_project_settings(cx, |project_settings| {
11132 project_settings.lsp.insert(
11133 language_server_name.into(),
11134 LspSettings {
11135 binary: None,
11136 settings: None,
11137 initialization_options: Some(json!({
11138 "anotherInitValue": false
11139 })),
11140 },
11141 );
11142 });
11143 cx.executor().run_until_parked();
11144 assert_eq!(
11145 server_restarts.load(atomic::Ordering::Acquire),
11146 1,
11147 "Should restart LSP server on a related LSP settings change"
11148 );
11149
11150 update_test_project_settings(cx, |project_settings| {
11151 project_settings.lsp.insert(
11152 language_server_name.into(),
11153 LspSettings {
11154 binary: None,
11155 settings: None,
11156 initialization_options: Some(json!({
11157 "anotherInitValue": false
11158 })),
11159 },
11160 );
11161 });
11162 cx.executor().run_until_parked();
11163 assert_eq!(
11164 server_restarts.load(atomic::Ordering::Acquire),
11165 1,
11166 "Should not restart LSP server on a related LSP settings change that is the same"
11167 );
11168
11169 update_test_project_settings(cx, |project_settings| {
11170 project_settings.lsp.insert(
11171 language_server_name.into(),
11172 LspSettings {
11173 binary: None,
11174 settings: None,
11175 initialization_options: None,
11176 },
11177 );
11178 });
11179 cx.executor().run_until_parked();
11180 assert_eq!(
11181 server_restarts.load(atomic::Ordering::Acquire),
11182 2,
11183 "Should restart LSP server on another related LSP settings change"
11184 );
11185}
11186
11187#[gpui::test]
11188async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
11189 init_test(cx, |_| {});
11190
11191 let mut cx = EditorLspTestContext::new_rust(
11192 lsp::ServerCapabilities {
11193 completion_provider: Some(lsp::CompletionOptions {
11194 trigger_characters: Some(vec![".".to_string()]),
11195 resolve_provider: Some(true),
11196 ..Default::default()
11197 }),
11198 ..Default::default()
11199 },
11200 cx,
11201 )
11202 .await;
11203
11204 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11205 cx.simulate_keystroke(".");
11206 let completion_item = lsp::CompletionItem {
11207 label: "some".into(),
11208 kind: Some(lsp::CompletionItemKind::SNIPPET),
11209 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11210 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11211 kind: lsp::MarkupKind::Markdown,
11212 value: "```rust\nSome(2)\n```".to_string(),
11213 })),
11214 deprecated: Some(false),
11215 sort_text: Some("fffffff2".to_string()),
11216 filter_text: Some("some".to_string()),
11217 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11218 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11219 range: lsp::Range {
11220 start: lsp::Position {
11221 line: 0,
11222 character: 22,
11223 },
11224 end: lsp::Position {
11225 line: 0,
11226 character: 22,
11227 },
11228 },
11229 new_text: "Some(2)".to_string(),
11230 })),
11231 additional_text_edits: Some(vec![lsp::TextEdit {
11232 range: lsp::Range {
11233 start: lsp::Position {
11234 line: 0,
11235 character: 20,
11236 },
11237 end: lsp::Position {
11238 line: 0,
11239 character: 22,
11240 },
11241 },
11242 new_text: "".to_string(),
11243 }]),
11244 ..Default::default()
11245 };
11246
11247 let closure_completion_item = completion_item.clone();
11248 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11249 let task_completion_item = closure_completion_item.clone();
11250 async move {
11251 Ok(Some(lsp::CompletionResponse::Array(vec![
11252 task_completion_item,
11253 ])))
11254 }
11255 });
11256
11257 request.next().await;
11258
11259 cx.condition(|editor, _| editor.context_menu_visible())
11260 .await;
11261 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11262 editor
11263 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11264 .unwrap()
11265 });
11266 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11267
11268 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11269 let task_completion_item = completion_item.clone();
11270 async move { Ok(task_completion_item) }
11271 })
11272 .next()
11273 .await
11274 .unwrap();
11275 apply_additional_edits.await.unwrap();
11276 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11277}
11278
11279#[gpui::test]
11280async fn test_completions_resolve_updates_labels_if_filter_text_matches(
11281 cx: &mut gpui::TestAppContext,
11282) {
11283 init_test(cx, |_| {});
11284
11285 let mut cx = EditorLspTestContext::new_rust(
11286 lsp::ServerCapabilities {
11287 completion_provider: Some(lsp::CompletionOptions {
11288 trigger_characters: Some(vec![".".to_string()]),
11289 resolve_provider: Some(true),
11290 ..Default::default()
11291 }),
11292 ..Default::default()
11293 },
11294 cx,
11295 )
11296 .await;
11297
11298 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11299 cx.simulate_keystroke(".");
11300
11301 let item1 = lsp::CompletionItem {
11302 label: "method id()".to_string(),
11303 filter_text: Some("id".to_string()),
11304 detail: None,
11305 documentation: None,
11306 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11307 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11308 new_text: ".id".to_string(),
11309 })),
11310 ..lsp::CompletionItem::default()
11311 };
11312
11313 let item2 = lsp::CompletionItem {
11314 label: "other".to_string(),
11315 filter_text: Some("other".to_string()),
11316 detail: None,
11317 documentation: None,
11318 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11319 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11320 new_text: ".other".to_string(),
11321 })),
11322 ..lsp::CompletionItem::default()
11323 };
11324
11325 let item1 = item1.clone();
11326 cx.handle_request::<lsp::request::Completion, _, _>({
11327 let item1 = item1.clone();
11328 move |_, _, _| {
11329 let item1 = item1.clone();
11330 let item2 = item2.clone();
11331 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11332 }
11333 })
11334 .next()
11335 .await;
11336
11337 cx.condition(|editor, _| editor.context_menu_visible())
11338 .await;
11339 cx.update_editor(|editor, _, _| {
11340 let context_menu = editor.context_menu.borrow_mut();
11341 let context_menu = context_menu
11342 .as_ref()
11343 .expect("Should have the context menu deployed");
11344 match context_menu {
11345 CodeContextMenu::Completions(completions_menu) => {
11346 let completions = completions_menu.completions.borrow_mut();
11347 assert_eq!(
11348 completions
11349 .iter()
11350 .map(|completion| &completion.label.text)
11351 .collect::<Vec<_>>(),
11352 vec!["method id()", "other"]
11353 )
11354 }
11355 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11356 }
11357 });
11358
11359 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11360 let item1 = item1.clone();
11361 move |_, item_to_resolve, _| {
11362 let item1 = item1.clone();
11363 async move {
11364 if item1 == item_to_resolve {
11365 Ok(lsp::CompletionItem {
11366 label: "method id()".to_string(),
11367 filter_text: Some("id".to_string()),
11368 detail: Some("Now resolved!".to_string()),
11369 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11370 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11371 range: lsp::Range::new(
11372 lsp::Position::new(0, 22),
11373 lsp::Position::new(0, 22),
11374 ),
11375 new_text: ".id".to_string(),
11376 })),
11377 ..lsp::CompletionItem::default()
11378 })
11379 } else {
11380 Ok(item_to_resolve)
11381 }
11382 }
11383 }
11384 })
11385 .next()
11386 .await
11387 .unwrap();
11388 cx.run_until_parked();
11389
11390 cx.update_editor(|editor, window, cx| {
11391 editor.context_menu_next(&Default::default(), window, cx);
11392 });
11393
11394 cx.update_editor(|editor, _, _| {
11395 let context_menu = editor.context_menu.borrow_mut();
11396 let context_menu = context_menu
11397 .as_ref()
11398 .expect("Should have the context menu deployed");
11399 match context_menu {
11400 CodeContextMenu::Completions(completions_menu) => {
11401 let completions = completions_menu.completions.borrow_mut();
11402 assert_eq!(
11403 completions
11404 .iter()
11405 .map(|completion| &completion.label.text)
11406 .collect::<Vec<_>>(),
11407 vec!["method id() Now resolved!", "other"],
11408 "Should update first completion label, but not second as the filter text did not match."
11409 );
11410 }
11411 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11412 }
11413 });
11414}
11415
11416#[gpui::test]
11417async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
11418 init_test(cx, |_| {});
11419
11420 let mut cx = EditorLspTestContext::new_rust(
11421 lsp::ServerCapabilities {
11422 completion_provider: Some(lsp::CompletionOptions {
11423 trigger_characters: Some(vec![".".to_string()]),
11424 resolve_provider: Some(true),
11425 ..Default::default()
11426 }),
11427 ..Default::default()
11428 },
11429 cx,
11430 )
11431 .await;
11432
11433 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11434 cx.simulate_keystroke(".");
11435
11436 let unresolved_item_1 = lsp::CompletionItem {
11437 label: "id".to_string(),
11438 filter_text: Some("id".to_string()),
11439 detail: None,
11440 documentation: None,
11441 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11442 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11443 new_text: ".id".to_string(),
11444 })),
11445 ..lsp::CompletionItem::default()
11446 };
11447 let resolved_item_1 = lsp::CompletionItem {
11448 additional_text_edits: Some(vec![lsp::TextEdit {
11449 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11450 new_text: "!!".to_string(),
11451 }]),
11452 ..unresolved_item_1.clone()
11453 };
11454 let unresolved_item_2 = lsp::CompletionItem {
11455 label: "other".to_string(),
11456 filter_text: Some("other".to_string()),
11457 detail: None,
11458 documentation: None,
11459 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11460 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11461 new_text: ".other".to_string(),
11462 })),
11463 ..lsp::CompletionItem::default()
11464 };
11465 let resolved_item_2 = lsp::CompletionItem {
11466 additional_text_edits: Some(vec![lsp::TextEdit {
11467 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11468 new_text: "??".to_string(),
11469 }]),
11470 ..unresolved_item_2.clone()
11471 };
11472
11473 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11474 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11475 cx.lsp
11476 .server
11477 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11478 let unresolved_item_1 = unresolved_item_1.clone();
11479 let resolved_item_1 = resolved_item_1.clone();
11480 let unresolved_item_2 = unresolved_item_2.clone();
11481 let resolved_item_2 = resolved_item_2.clone();
11482 let resolve_requests_1 = resolve_requests_1.clone();
11483 let resolve_requests_2 = resolve_requests_2.clone();
11484 move |unresolved_request, _| {
11485 let unresolved_item_1 = unresolved_item_1.clone();
11486 let resolved_item_1 = resolved_item_1.clone();
11487 let unresolved_item_2 = unresolved_item_2.clone();
11488 let resolved_item_2 = resolved_item_2.clone();
11489 let resolve_requests_1 = resolve_requests_1.clone();
11490 let resolve_requests_2 = resolve_requests_2.clone();
11491 async move {
11492 if unresolved_request == unresolved_item_1 {
11493 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11494 Ok(resolved_item_1.clone())
11495 } else if unresolved_request == unresolved_item_2 {
11496 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11497 Ok(resolved_item_2.clone())
11498 } else {
11499 panic!("Unexpected completion item {unresolved_request:?}")
11500 }
11501 }
11502 }
11503 })
11504 .detach();
11505
11506 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11507 let unresolved_item_1 = unresolved_item_1.clone();
11508 let unresolved_item_2 = unresolved_item_2.clone();
11509 async move {
11510 Ok(Some(lsp::CompletionResponse::Array(vec![
11511 unresolved_item_1,
11512 unresolved_item_2,
11513 ])))
11514 }
11515 })
11516 .next()
11517 .await;
11518
11519 cx.condition(|editor, _| editor.context_menu_visible())
11520 .await;
11521 cx.update_editor(|editor, _, _| {
11522 let context_menu = editor.context_menu.borrow_mut();
11523 let context_menu = context_menu
11524 .as_ref()
11525 .expect("Should have the context menu deployed");
11526 match context_menu {
11527 CodeContextMenu::Completions(completions_menu) => {
11528 let completions = completions_menu.completions.borrow_mut();
11529 assert_eq!(
11530 completions
11531 .iter()
11532 .map(|completion| &completion.label.text)
11533 .collect::<Vec<_>>(),
11534 vec!["id", "other"]
11535 )
11536 }
11537 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11538 }
11539 });
11540 cx.run_until_parked();
11541
11542 cx.update_editor(|editor, window, cx| {
11543 editor.context_menu_next(&ContextMenuNext, window, cx);
11544 });
11545 cx.run_until_parked();
11546 cx.update_editor(|editor, window, cx| {
11547 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11548 });
11549 cx.run_until_parked();
11550 cx.update_editor(|editor, window, cx| {
11551 editor.context_menu_next(&ContextMenuNext, window, cx);
11552 });
11553 cx.run_until_parked();
11554 cx.update_editor(|editor, window, cx| {
11555 editor
11556 .compose_completion(&ComposeCompletion::default(), window, cx)
11557 .expect("No task returned")
11558 })
11559 .await
11560 .expect("Completion failed");
11561 cx.run_until_parked();
11562
11563 cx.update_editor(|editor, _, cx| {
11564 assert_eq!(
11565 resolve_requests_1.load(atomic::Ordering::Acquire),
11566 1,
11567 "Should always resolve once despite multiple selections"
11568 );
11569 assert_eq!(
11570 resolve_requests_2.load(atomic::Ordering::Acquire),
11571 1,
11572 "Should always resolve once after multiple selections and applying the completion"
11573 );
11574 assert_eq!(
11575 editor.text(cx),
11576 "fn main() { let a = ??.other; }",
11577 "Should use resolved data when applying the completion"
11578 );
11579 });
11580}
11581
11582#[gpui::test]
11583async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
11584 init_test(cx, |_| {});
11585
11586 let item_0 = lsp::CompletionItem {
11587 label: "abs".into(),
11588 insert_text: Some("abs".into()),
11589 data: Some(json!({ "very": "special"})),
11590 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11591 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11592 lsp::InsertReplaceEdit {
11593 new_text: "abs".to_string(),
11594 insert: lsp::Range::default(),
11595 replace: lsp::Range::default(),
11596 },
11597 )),
11598 ..lsp::CompletionItem::default()
11599 };
11600 let items = iter::once(item_0.clone())
11601 .chain((11..51).map(|i| lsp::CompletionItem {
11602 label: format!("item_{}", i),
11603 insert_text: Some(format!("item_{}", i)),
11604 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11605 ..lsp::CompletionItem::default()
11606 }))
11607 .collect::<Vec<_>>();
11608
11609 let default_commit_characters = vec!["?".to_string()];
11610 let default_data = json!({ "default": "data"});
11611 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11612 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11613 let default_edit_range = lsp::Range {
11614 start: lsp::Position {
11615 line: 0,
11616 character: 5,
11617 },
11618 end: lsp::Position {
11619 line: 0,
11620 character: 5,
11621 },
11622 };
11623
11624 let item_0_out = lsp::CompletionItem {
11625 commit_characters: Some(default_commit_characters.clone()),
11626 insert_text_format: Some(default_insert_text_format),
11627 ..item_0
11628 };
11629 let items_out = iter::once(item_0_out)
11630 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11631 commit_characters: Some(default_commit_characters.clone()),
11632 data: Some(default_data.clone()),
11633 insert_text_mode: Some(default_insert_text_mode),
11634 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11635 range: default_edit_range,
11636 new_text: item.label.clone(),
11637 })),
11638 ..item.clone()
11639 }))
11640 .collect::<Vec<lsp::CompletionItem>>();
11641
11642 let mut cx = EditorLspTestContext::new_rust(
11643 lsp::ServerCapabilities {
11644 completion_provider: Some(lsp::CompletionOptions {
11645 trigger_characters: Some(vec![".".to_string()]),
11646 resolve_provider: Some(true),
11647 ..Default::default()
11648 }),
11649 ..Default::default()
11650 },
11651 cx,
11652 )
11653 .await;
11654
11655 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11656 cx.simulate_keystroke(".");
11657
11658 let completion_data = default_data.clone();
11659 let completion_characters = default_commit_characters.clone();
11660 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11661 let default_data = completion_data.clone();
11662 let default_commit_characters = completion_characters.clone();
11663 let items = items.clone();
11664 async move {
11665 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11666 items,
11667 item_defaults: Some(lsp::CompletionListItemDefaults {
11668 data: Some(default_data.clone()),
11669 commit_characters: Some(default_commit_characters.clone()),
11670 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11671 default_edit_range,
11672 )),
11673 insert_text_format: Some(default_insert_text_format),
11674 insert_text_mode: Some(default_insert_text_mode),
11675 }),
11676 ..lsp::CompletionList::default()
11677 })))
11678 }
11679 })
11680 .next()
11681 .await;
11682
11683 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11684 cx.lsp
11685 .server
11686 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11687 let closure_resolved_items = resolved_items.clone();
11688 move |item_to_resolve, _| {
11689 let closure_resolved_items = closure_resolved_items.clone();
11690 async move {
11691 closure_resolved_items.lock().push(item_to_resolve.clone());
11692 Ok(item_to_resolve)
11693 }
11694 }
11695 })
11696 .detach();
11697
11698 cx.condition(|editor, _| editor.context_menu_visible())
11699 .await;
11700 cx.run_until_parked();
11701 cx.update_editor(|editor, _, _| {
11702 let menu = editor.context_menu.borrow_mut();
11703 match menu.as_ref().expect("should have the completions menu") {
11704 CodeContextMenu::Completions(completions_menu) => {
11705 assert_eq!(
11706 completions_menu
11707 .entries
11708 .borrow()
11709 .iter()
11710 .flat_map(|c| match c {
11711 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11712 _ => None,
11713 })
11714 .collect::<Vec<String>>(),
11715 items_out
11716 .iter()
11717 .map(|completion| completion.label.clone())
11718 .collect::<Vec<String>>()
11719 );
11720 }
11721 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11722 }
11723 });
11724 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11725 // with 4 from the end.
11726 assert_eq!(
11727 *resolved_items.lock(),
11728 [
11729 &items_out[0..16],
11730 &items_out[items_out.len() - 4..items_out.len()]
11731 ]
11732 .concat()
11733 .iter()
11734 .cloned()
11735 .collect::<Vec<lsp::CompletionItem>>()
11736 );
11737 resolved_items.lock().clear();
11738
11739 cx.update_editor(|editor, window, cx| {
11740 editor.context_menu_prev(&ContextMenuPrev, window, cx);
11741 });
11742 cx.run_until_parked();
11743 // Completions that have already been resolved are skipped.
11744 assert_eq!(
11745 *resolved_items.lock(),
11746 items_out[items_out.len() - 16..items_out.len() - 4]
11747 .iter()
11748 .cloned()
11749 .collect::<Vec<lsp::CompletionItem>>()
11750 );
11751 resolved_items.lock().clear();
11752}
11753
11754#[gpui::test]
11755async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11756 init_test(cx, |_| {});
11757
11758 let mut cx = EditorLspTestContext::new(
11759 Language::new(
11760 LanguageConfig {
11761 matcher: LanguageMatcher {
11762 path_suffixes: vec!["jsx".into()],
11763 ..Default::default()
11764 },
11765 overrides: [(
11766 "element".into(),
11767 LanguageConfigOverride {
11768 word_characters: Override::Set(['-'].into_iter().collect()),
11769 ..Default::default()
11770 },
11771 )]
11772 .into_iter()
11773 .collect(),
11774 ..Default::default()
11775 },
11776 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11777 )
11778 .with_override_query("(jsx_self_closing_element) @element")
11779 .unwrap(),
11780 lsp::ServerCapabilities {
11781 completion_provider: Some(lsp::CompletionOptions {
11782 trigger_characters: Some(vec![":".to_string()]),
11783 ..Default::default()
11784 }),
11785 ..Default::default()
11786 },
11787 cx,
11788 )
11789 .await;
11790
11791 cx.lsp
11792 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11793 Ok(Some(lsp::CompletionResponse::Array(vec![
11794 lsp::CompletionItem {
11795 label: "bg-blue".into(),
11796 ..Default::default()
11797 },
11798 lsp::CompletionItem {
11799 label: "bg-red".into(),
11800 ..Default::default()
11801 },
11802 lsp::CompletionItem {
11803 label: "bg-yellow".into(),
11804 ..Default::default()
11805 },
11806 ])))
11807 });
11808
11809 cx.set_state(r#"<p class="bgˇ" />"#);
11810
11811 // Trigger completion when typing a dash, because the dash is an extra
11812 // word character in the 'element' scope, which contains the cursor.
11813 cx.simulate_keystroke("-");
11814 cx.executor().run_until_parked();
11815 cx.update_editor(|editor, _, _| {
11816 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11817 {
11818 assert_eq!(
11819 completion_menu_entries(&menu),
11820 &["bg-red", "bg-blue", "bg-yellow"]
11821 );
11822 } else {
11823 panic!("expected completion menu to be open");
11824 }
11825 });
11826
11827 cx.simulate_keystroke("l");
11828 cx.executor().run_until_parked();
11829 cx.update_editor(|editor, _, _| {
11830 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11831 {
11832 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
11833 } else {
11834 panic!("expected completion menu to be open");
11835 }
11836 });
11837
11838 // When filtering completions, consider the character after the '-' to
11839 // be the start of a subword.
11840 cx.set_state(r#"<p class="yelˇ" />"#);
11841 cx.simulate_keystroke("l");
11842 cx.executor().run_until_parked();
11843 cx.update_editor(|editor, _, _| {
11844 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11845 {
11846 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
11847 } else {
11848 panic!("expected completion menu to be open");
11849 }
11850 });
11851}
11852
11853fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
11854 let entries = menu.entries.borrow();
11855 entries
11856 .iter()
11857 .flat_map(|e| match e {
11858 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11859 _ => None,
11860 })
11861 .collect()
11862}
11863
11864#[gpui::test]
11865async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11866 init_test(cx, |settings| {
11867 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11868 FormatterList(vec![Formatter::Prettier].into()),
11869 ))
11870 });
11871
11872 let fs = FakeFs::new(cx.executor());
11873 fs.insert_file("/file.ts", Default::default()).await;
11874
11875 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11876 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11877
11878 language_registry.add(Arc::new(Language::new(
11879 LanguageConfig {
11880 name: "TypeScript".into(),
11881 matcher: LanguageMatcher {
11882 path_suffixes: vec!["ts".to_string()],
11883 ..Default::default()
11884 },
11885 ..Default::default()
11886 },
11887 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11888 )));
11889 update_test_language_settings(cx, |settings| {
11890 settings.defaults.prettier = Some(PrettierSettings {
11891 allowed: true,
11892 ..PrettierSettings::default()
11893 });
11894 });
11895
11896 let test_plugin = "test_plugin";
11897 let _ = language_registry.register_fake_lsp(
11898 "TypeScript",
11899 FakeLspAdapter {
11900 prettier_plugins: vec![test_plugin],
11901 ..Default::default()
11902 },
11903 );
11904
11905 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11906 let buffer = project
11907 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11908 .await
11909 .unwrap();
11910
11911 let buffer_text = "one\ntwo\nthree\n";
11912 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11913 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11914 editor.update_in(cx, |editor, window, cx| {
11915 editor.set_text(buffer_text, window, cx)
11916 });
11917
11918 editor
11919 .update_in(cx, |editor, window, cx| {
11920 editor.perform_format(
11921 project.clone(),
11922 FormatTrigger::Manual,
11923 FormatTarget::Buffers,
11924 window,
11925 cx,
11926 )
11927 })
11928 .unwrap()
11929 .await;
11930 assert_eq!(
11931 editor.update(cx, |editor, cx| editor.text(cx)),
11932 buffer_text.to_string() + prettier_format_suffix,
11933 "Test prettier formatting was not applied to the original buffer text",
11934 );
11935
11936 update_test_language_settings(cx, |settings| {
11937 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11938 });
11939 let format = editor.update_in(cx, |editor, window, cx| {
11940 editor.perform_format(
11941 project.clone(),
11942 FormatTrigger::Manual,
11943 FormatTarget::Buffers,
11944 window,
11945 cx,
11946 )
11947 });
11948 format.await.unwrap();
11949 assert_eq!(
11950 editor.update(cx, |editor, cx| editor.text(cx)),
11951 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11952 "Autoformatting (via test prettier) was not applied to the original buffer text",
11953 );
11954}
11955
11956#[gpui::test]
11957async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11958 init_test(cx, |_| {});
11959 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11960 let base_text = indoc! {r#"
11961 struct Row;
11962 struct Row1;
11963 struct Row2;
11964
11965 struct Row4;
11966 struct Row5;
11967 struct Row6;
11968
11969 struct Row8;
11970 struct Row9;
11971 struct Row10;"#};
11972
11973 // When addition hunks are not adjacent to carets, no hunk revert is performed
11974 assert_hunk_revert(
11975 indoc! {r#"struct Row;
11976 struct Row1;
11977 struct Row1.1;
11978 struct Row1.2;
11979 struct Row2;ˇ
11980
11981 struct Row4;
11982 struct Row5;
11983 struct Row6;
11984
11985 struct Row8;
11986 ˇstruct Row9;
11987 struct Row9.1;
11988 struct Row9.2;
11989 struct Row9.3;
11990 struct Row10;"#},
11991 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11992 indoc! {r#"struct Row;
11993 struct Row1;
11994 struct Row1.1;
11995 struct Row1.2;
11996 struct Row2;ˇ
11997
11998 struct Row4;
11999 struct Row5;
12000 struct Row6;
12001
12002 struct Row8;
12003 ˇstruct Row9;
12004 struct Row9.1;
12005 struct Row9.2;
12006 struct Row9.3;
12007 struct Row10;"#},
12008 base_text,
12009 &mut cx,
12010 );
12011 // Same for selections
12012 assert_hunk_revert(
12013 indoc! {r#"struct Row;
12014 struct Row1;
12015 struct Row2;
12016 struct Row2.1;
12017 struct Row2.2;
12018 «ˇ
12019 struct Row4;
12020 struct» Row5;
12021 «struct Row6;
12022 ˇ»
12023 struct Row9.1;
12024 struct Row9.2;
12025 struct Row9.3;
12026 struct Row8;
12027 struct Row9;
12028 struct Row10;"#},
12029 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
12030 indoc! {r#"struct Row;
12031 struct Row1;
12032 struct Row2;
12033 struct Row2.1;
12034 struct Row2.2;
12035 «ˇ
12036 struct Row4;
12037 struct» Row5;
12038 «struct Row6;
12039 ˇ»
12040 struct Row9.1;
12041 struct Row9.2;
12042 struct Row9.3;
12043 struct Row8;
12044 struct Row9;
12045 struct Row10;"#},
12046 base_text,
12047 &mut cx,
12048 );
12049
12050 // When carets and selections intersect the addition hunks, those are reverted.
12051 // Adjacent carets got merged.
12052 assert_hunk_revert(
12053 indoc! {r#"struct Row;
12054 ˇ// something on the top
12055 struct Row1;
12056 struct Row2;
12057 struct Roˇw3.1;
12058 struct Row2.2;
12059 struct Row2.3;ˇ
12060
12061 struct Row4;
12062 struct ˇRow5.1;
12063 struct Row5.2;
12064 struct «Rowˇ»5.3;
12065 struct Row5;
12066 struct Row6;
12067 ˇ
12068 struct Row9.1;
12069 struct «Rowˇ»9.2;
12070 struct «ˇRow»9.3;
12071 struct Row8;
12072 struct Row9;
12073 «ˇ// something on bottom»
12074 struct Row10;"#},
12075 vec![
12076 DiffHunkStatus::Added,
12077 DiffHunkStatus::Added,
12078 DiffHunkStatus::Added,
12079 DiffHunkStatus::Added,
12080 DiffHunkStatus::Added,
12081 ],
12082 indoc! {r#"struct Row;
12083 ˇstruct Row1;
12084 struct Row2;
12085 ˇ
12086 struct Row4;
12087 ˇstruct Row5;
12088 struct Row6;
12089 ˇ
12090 ˇstruct Row8;
12091 struct Row9;
12092 ˇstruct Row10;"#},
12093 base_text,
12094 &mut cx,
12095 );
12096}
12097
12098#[gpui::test]
12099async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
12100 init_test(cx, |_| {});
12101 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12102 let base_text = indoc! {r#"
12103 struct Row;
12104 struct Row1;
12105 struct Row2;
12106
12107 struct Row4;
12108 struct Row5;
12109 struct Row6;
12110
12111 struct Row8;
12112 struct Row9;
12113 struct Row10;"#};
12114
12115 // Modification hunks behave the same as the addition ones.
12116 assert_hunk_revert(
12117 indoc! {r#"struct Row;
12118 struct Row1;
12119 struct Row33;
12120 ˇ
12121 struct Row4;
12122 struct Row5;
12123 struct Row6;
12124 ˇ
12125 struct Row99;
12126 struct Row9;
12127 struct Row10;"#},
12128 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
12129 indoc! {r#"struct Row;
12130 struct Row1;
12131 struct Row33;
12132 ˇ
12133 struct Row4;
12134 struct Row5;
12135 struct Row6;
12136 ˇ
12137 struct Row99;
12138 struct Row9;
12139 struct Row10;"#},
12140 base_text,
12141 &mut cx,
12142 );
12143 assert_hunk_revert(
12144 indoc! {r#"struct Row;
12145 struct Row1;
12146 struct Row33;
12147 «ˇ
12148 struct Row4;
12149 struct» Row5;
12150 «struct Row6;
12151 ˇ»
12152 struct Row99;
12153 struct Row9;
12154 struct Row10;"#},
12155 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
12156 indoc! {r#"struct Row;
12157 struct Row1;
12158 struct Row33;
12159 «ˇ
12160 struct Row4;
12161 struct» Row5;
12162 «struct Row6;
12163 ˇ»
12164 struct Row99;
12165 struct Row9;
12166 struct Row10;"#},
12167 base_text,
12168 &mut cx,
12169 );
12170
12171 assert_hunk_revert(
12172 indoc! {r#"ˇstruct Row1.1;
12173 struct Row1;
12174 «ˇstr»uct Row22;
12175
12176 struct ˇRow44;
12177 struct Row5;
12178 struct «Rˇ»ow66;ˇ
12179
12180 «struˇ»ct Row88;
12181 struct Row9;
12182 struct Row1011;ˇ"#},
12183 vec![
12184 DiffHunkStatus::Modified,
12185 DiffHunkStatus::Modified,
12186 DiffHunkStatus::Modified,
12187 DiffHunkStatus::Modified,
12188 DiffHunkStatus::Modified,
12189 DiffHunkStatus::Modified,
12190 ],
12191 indoc! {r#"struct Row;
12192 ˇstruct Row1;
12193 struct Row2;
12194 ˇ
12195 struct Row4;
12196 ˇstruct Row5;
12197 struct Row6;
12198 ˇ
12199 struct Row8;
12200 ˇstruct Row9;
12201 struct Row10;ˇ"#},
12202 base_text,
12203 &mut cx,
12204 );
12205}
12206
12207#[gpui::test]
12208async fn test_deleting_over_diff_hunk(cx: &mut gpui::TestAppContext) {
12209 init_test(cx, |_| {});
12210 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12211 let base_text = indoc! {r#"
12212 one
12213
12214 two
12215 three
12216 "#};
12217
12218 cx.set_diff_base(base_text);
12219 cx.set_state("\nˇ\n");
12220 cx.executor().run_until_parked();
12221 cx.update_editor(|editor, _window, cx| {
12222 editor.expand_selected_diff_hunks(cx);
12223 });
12224 cx.executor().run_until_parked();
12225 cx.update_editor(|editor, window, cx| {
12226 editor.backspace(&Default::default(), window, cx);
12227 });
12228 cx.run_until_parked();
12229 cx.assert_state_with_diff(
12230 indoc! {r#"
12231
12232 - two
12233 - threeˇ
12234 +
12235 "#}
12236 .to_string(),
12237 );
12238}
12239
12240#[gpui::test]
12241async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
12242 init_test(cx, |_| {});
12243 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12244 let base_text = indoc! {r#"struct Row;
12245struct Row1;
12246struct Row2;
12247
12248struct Row4;
12249struct Row5;
12250struct Row6;
12251
12252struct Row8;
12253struct Row9;
12254struct Row10;"#};
12255
12256 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12257 assert_hunk_revert(
12258 indoc! {r#"struct Row;
12259 struct Row2;
12260
12261 ˇstruct Row4;
12262 struct Row5;
12263 struct Row6;
12264 ˇ
12265 struct Row8;
12266 struct Row10;"#},
12267 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12268 indoc! {r#"struct Row;
12269 struct Row2;
12270
12271 ˇstruct Row4;
12272 struct Row5;
12273 struct Row6;
12274 ˇ
12275 struct Row8;
12276 struct Row10;"#},
12277 base_text,
12278 &mut cx,
12279 );
12280 assert_hunk_revert(
12281 indoc! {r#"struct Row;
12282 struct Row2;
12283
12284 «ˇstruct Row4;
12285 struct» Row5;
12286 «struct Row6;
12287 ˇ»
12288 struct Row8;
12289 struct Row10;"#},
12290 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12291 indoc! {r#"struct Row;
12292 struct Row2;
12293
12294 «ˇstruct Row4;
12295 struct» Row5;
12296 «struct Row6;
12297 ˇ»
12298 struct Row8;
12299 struct Row10;"#},
12300 base_text,
12301 &mut cx,
12302 );
12303
12304 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12305 assert_hunk_revert(
12306 indoc! {r#"struct Row;
12307 ˇstruct Row2;
12308
12309 struct Row4;
12310 struct Row5;
12311 struct Row6;
12312
12313 struct Row8;ˇ
12314 struct Row10;"#},
12315 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12316 indoc! {r#"struct Row;
12317 struct Row1;
12318 ˇstruct Row2;
12319
12320 struct Row4;
12321 struct Row5;
12322 struct Row6;
12323
12324 struct Row8;ˇ
12325 struct Row9;
12326 struct Row10;"#},
12327 base_text,
12328 &mut cx,
12329 );
12330 assert_hunk_revert(
12331 indoc! {r#"struct Row;
12332 struct Row2«ˇ;
12333 struct Row4;
12334 struct» Row5;
12335 «struct Row6;
12336
12337 struct Row8;ˇ»
12338 struct Row10;"#},
12339 vec![
12340 DiffHunkStatus::Removed,
12341 DiffHunkStatus::Removed,
12342 DiffHunkStatus::Removed,
12343 ],
12344 indoc! {r#"struct Row;
12345 struct Row1;
12346 struct Row2«ˇ;
12347
12348 struct Row4;
12349 struct» Row5;
12350 «struct Row6;
12351
12352 struct Row8;ˇ»
12353 struct Row9;
12354 struct Row10;"#},
12355 base_text,
12356 &mut cx,
12357 );
12358}
12359
12360#[gpui::test]
12361async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
12362 init_test(cx, |_| {});
12363
12364 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12365 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12366 let base_text_3 =
12367 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12368
12369 let text_1 = edit_first_char_of_every_line(base_text_1);
12370 let text_2 = edit_first_char_of_every_line(base_text_2);
12371 let text_3 = edit_first_char_of_every_line(base_text_3);
12372
12373 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
12374 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
12375 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
12376
12377 let multibuffer = cx.new(|cx| {
12378 let mut multibuffer = MultiBuffer::new(ReadWrite);
12379 multibuffer.push_excerpts(
12380 buffer_1.clone(),
12381 [
12382 ExcerptRange {
12383 context: Point::new(0, 0)..Point::new(3, 0),
12384 primary: None,
12385 },
12386 ExcerptRange {
12387 context: Point::new(5, 0)..Point::new(7, 0),
12388 primary: None,
12389 },
12390 ExcerptRange {
12391 context: Point::new(9, 0)..Point::new(10, 4),
12392 primary: None,
12393 },
12394 ],
12395 cx,
12396 );
12397 multibuffer.push_excerpts(
12398 buffer_2.clone(),
12399 [
12400 ExcerptRange {
12401 context: Point::new(0, 0)..Point::new(3, 0),
12402 primary: None,
12403 },
12404 ExcerptRange {
12405 context: Point::new(5, 0)..Point::new(7, 0),
12406 primary: None,
12407 },
12408 ExcerptRange {
12409 context: Point::new(9, 0)..Point::new(10, 4),
12410 primary: None,
12411 },
12412 ],
12413 cx,
12414 );
12415 multibuffer.push_excerpts(
12416 buffer_3.clone(),
12417 [
12418 ExcerptRange {
12419 context: Point::new(0, 0)..Point::new(3, 0),
12420 primary: None,
12421 },
12422 ExcerptRange {
12423 context: Point::new(5, 0)..Point::new(7, 0),
12424 primary: None,
12425 },
12426 ExcerptRange {
12427 context: Point::new(9, 0)..Point::new(10, 4),
12428 primary: None,
12429 },
12430 ],
12431 cx,
12432 );
12433 multibuffer
12434 });
12435
12436 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
12437 editor.update_in(cx, |editor, _window, cx| {
12438 for (buffer, diff_base) in [
12439 (buffer_1.clone(), base_text_1),
12440 (buffer_2.clone(), base_text_2),
12441 (buffer_3.clone(), base_text_3),
12442 ] {
12443 let change_set = cx
12444 .new(|cx| BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx));
12445 editor
12446 .buffer
12447 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
12448 }
12449 });
12450 cx.executor().run_until_parked();
12451
12452 editor.update_in(cx, |editor, window, cx| {
12453 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}");
12454 editor.select_all(&SelectAll, window, cx);
12455 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
12456 });
12457 cx.executor().run_until_parked();
12458
12459 // When all ranges are selected, all buffer hunks are reverted.
12460 editor.update(cx, |editor, cx| {
12461 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");
12462 });
12463 buffer_1.update(cx, |buffer, _| {
12464 assert_eq!(buffer.text(), base_text_1);
12465 });
12466 buffer_2.update(cx, |buffer, _| {
12467 assert_eq!(buffer.text(), base_text_2);
12468 });
12469 buffer_3.update(cx, |buffer, _| {
12470 assert_eq!(buffer.text(), base_text_3);
12471 });
12472
12473 editor.update_in(cx, |editor, window, cx| {
12474 editor.undo(&Default::default(), window, cx);
12475 });
12476
12477 editor.update_in(cx, |editor, window, cx| {
12478 editor.change_selections(None, window, cx, |s| {
12479 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12480 });
12481 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
12482 });
12483
12484 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12485 // but not affect buffer_2 and its related excerpts.
12486 editor.update(cx, |editor, cx| {
12487 assert_eq!(
12488 editor.text(cx),
12489 "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}"
12490 );
12491 });
12492 buffer_1.update(cx, |buffer, _| {
12493 assert_eq!(buffer.text(), base_text_1);
12494 });
12495 buffer_2.update(cx, |buffer, _| {
12496 assert_eq!(
12497 buffer.text(),
12498 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12499 );
12500 });
12501 buffer_3.update(cx, |buffer, _| {
12502 assert_eq!(
12503 buffer.text(),
12504 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12505 );
12506 });
12507
12508 fn edit_first_char_of_every_line(text: &str) -> String {
12509 text.split('\n')
12510 .map(|line| format!("X{}", &line[1..]))
12511 .collect::<Vec<_>>()
12512 .join("\n")
12513 }
12514}
12515
12516#[gpui::test]
12517async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
12518 init_test(cx, |_| {});
12519
12520 let cols = 4;
12521 let rows = 10;
12522 let sample_text_1 = sample_text(rows, cols, 'a');
12523 assert_eq!(
12524 sample_text_1,
12525 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12526 );
12527 let sample_text_2 = sample_text(rows, cols, 'l');
12528 assert_eq!(
12529 sample_text_2,
12530 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12531 );
12532 let sample_text_3 = sample_text(rows, cols, 'v');
12533 assert_eq!(
12534 sample_text_3,
12535 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12536 );
12537
12538 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
12539 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
12540 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
12541
12542 let multi_buffer = cx.new(|cx| {
12543 let mut multibuffer = MultiBuffer::new(ReadWrite);
12544 multibuffer.push_excerpts(
12545 buffer_1.clone(),
12546 [
12547 ExcerptRange {
12548 context: Point::new(0, 0)..Point::new(3, 0),
12549 primary: None,
12550 },
12551 ExcerptRange {
12552 context: Point::new(5, 0)..Point::new(7, 0),
12553 primary: None,
12554 },
12555 ExcerptRange {
12556 context: Point::new(9, 0)..Point::new(10, 4),
12557 primary: None,
12558 },
12559 ],
12560 cx,
12561 );
12562 multibuffer.push_excerpts(
12563 buffer_2.clone(),
12564 [
12565 ExcerptRange {
12566 context: Point::new(0, 0)..Point::new(3, 0),
12567 primary: None,
12568 },
12569 ExcerptRange {
12570 context: Point::new(5, 0)..Point::new(7, 0),
12571 primary: None,
12572 },
12573 ExcerptRange {
12574 context: Point::new(9, 0)..Point::new(10, 4),
12575 primary: None,
12576 },
12577 ],
12578 cx,
12579 );
12580 multibuffer.push_excerpts(
12581 buffer_3.clone(),
12582 [
12583 ExcerptRange {
12584 context: Point::new(0, 0)..Point::new(3, 0),
12585 primary: None,
12586 },
12587 ExcerptRange {
12588 context: Point::new(5, 0)..Point::new(7, 0),
12589 primary: None,
12590 },
12591 ExcerptRange {
12592 context: Point::new(9, 0)..Point::new(10, 4),
12593 primary: None,
12594 },
12595 ],
12596 cx,
12597 );
12598 multibuffer
12599 });
12600
12601 let fs = FakeFs::new(cx.executor());
12602 fs.insert_tree(
12603 "/a",
12604 json!({
12605 "main.rs": sample_text_1,
12606 "other.rs": sample_text_2,
12607 "lib.rs": sample_text_3,
12608 }),
12609 )
12610 .await;
12611 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12612 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12613 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12614 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12615 Editor::new(
12616 EditorMode::Full,
12617 multi_buffer,
12618 Some(project.clone()),
12619 true,
12620 window,
12621 cx,
12622 )
12623 });
12624 let multibuffer_item_id = workspace
12625 .update(cx, |workspace, window, cx| {
12626 assert!(
12627 workspace.active_item(cx).is_none(),
12628 "active item should be None before the first item is added"
12629 );
12630 workspace.add_item_to_active_pane(
12631 Box::new(multi_buffer_editor.clone()),
12632 None,
12633 true,
12634 window,
12635 cx,
12636 );
12637 let active_item = workspace
12638 .active_item(cx)
12639 .expect("should have an active item after adding the multi buffer");
12640 assert!(
12641 !active_item.is_singleton(cx),
12642 "A multi buffer was expected to active after adding"
12643 );
12644 active_item.item_id()
12645 })
12646 .unwrap();
12647 cx.executor().run_until_parked();
12648
12649 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12650 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12651 s.select_ranges(Some(1..2))
12652 });
12653 editor.open_excerpts(&OpenExcerpts, window, cx);
12654 });
12655 cx.executor().run_until_parked();
12656 let first_item_id = workspace
12657 .update(cx, |workspace, window, cx| {
12658 let active_item = workspace
12659 .active_item(cx)
12660 .expect("should have an active item after navigating into the 1st buffer");
12661 let first_item_id = active_item.item_id();
12662 assert_ne!(
12663 first_item_id, multibuffer_item_id,
12664 "Should navigate into the 1st buffer and activate it"
12665 );
12666 assert!(
12667 active_item.is_singleton(cx),
12668 "New active item should be a singleton buffer"
12669 );
12670 assert_eq!(
12671 active_item
12672 .act_as::<Editor>(cx)
12673 .expect("should have navigated into an editor for the 1st buffer")
12674 .read(cx)
12675 .text(cx),
12676 sample_text_1
12677 );
12678
12679 workspace
12680 .go_back(workspace.active_pane().downgrade(), window, cx)
12681 .detach_and_log_err(cx);
12682
12683 first_item_id
12684 })
12685 .unwrap();
12686 cx.executor().run_until_parked();
12687 workspace
12688 .update(cx, |workspace, _, cx| {
12689 let active_item = workspace
12690 .active_item(cx)
12691 .expect("should have an active item after navigating back");
12692 assert_eq!(
12693 active_item.item_id(),
12694 multibuffer_item_id,
12695 "Should navigate back to the multi buffer"
12696 );
12697 assert!(!active_item.is_singleton(cx));
12698 })
12699 .unwrap();
12700
12701 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12702 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12703 s.select_ranges(Some(39..40))
12704 });
12705 editor.open_excerpts(&OpenExcerpts, window, cx);
12706 });
12707 cx.executor().run_until_parked();
12708 let second_item_id = workspace
12709 .update(cx, |workspace, window, cx| {
12710 let active_item = workspace
12711 .active_item(cx)
12712 .expect("should have an active item after navigating into the 2nd buffer");
12713 let second_item_id = active_item.item_id();
12714 assert_ne!(
12715 second_item_id, multibuffer_item_id,
12716 "Should navigate away from the multibuffer"
12717 );
12718 assert_ne!(
12719 second_item_id, first_item_id,
12720 "Should navigate into the 2nd buffer and activate it"
12721 );
12722 assert!(
12723 active_item.is_singleton(cx),
12724 "New active item should be a singleton buffer"
12725 );
12726 assert_eq!(
12727 active_item
12728 .act_as::<Editor>(cx)
12729 .expect("should have navigated into an editor")
12730 .read(cx)
12731 .text(cx),
12732 sample_text_2
12733 );
12734
12735 workspace
12736 .go_back(workspace.active_pane().downgrade(), window, cx)
12737 .detach_and_log_err(cx);
12738
12739 second_item_id
12740 })
12741 .unwrap();
12742 cx.executor().run_until_parked();
12743 workspace
12744 .update(cx, |workspace, _, cx| {
12745 let active_item = workspace
12746 .active_item(cx)
12747 .expect("should have an active item after navigating back from the 2nd buffer");
12748 assert_eq!(
12749 active_item.item_id(),
12750 multibuffer_item_id,
12751 "Should navigate back from the 2nd buffer to the multi buffer"
12752 );
12753 assert!(!active_item.is_singleton(cx));
12754 })
12755 .unwrap();
12756
12757 multi_buffer_editor.update_in(cx, |editor, window, cx| {
12758 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
12759 s.select_ranges(Some(70..70))
12760 });
12761 editor.open_excerpts(&OpenExcerpts, window, cx);
12762 });
12763 cx.executor().run_until_parked();
12764 workspace
12765 .update(cx, |workspace, window, cx| {
12766 let active_item = workspace
12767 .active_item(cx)
12768 .expect("should have an active item after navigating into the 3rd buffer");
12769 let third_item_id = active_item.item_id();
12770 assert_ne!(
12771 third_item_id, multibuffer_item_id,
12772 "Should navigate into the 3rd buffer and activate it"
12773 );
12774 assert_ne!(third_item_id, first_item_id);
12775 assert_ne!(third_item_id, second_item_id);
12776 assert!(
12777 active_item.is_singleton(cx),
12778 "New active item should be a singleton buffer"
12779 );
12780 assert_eq!(
12781 active_item
12782 .act_as::<Editor>(cx)
12783 .expect("should have navigated into an editor")
12784 .read(cx)
12785 .text(cx),
12786 sample_text_3
12787 );
12788
12789 workspace
12790 .go_back(workspace.active_pane().downgrade(), window, cx)
12791 .detach_and_log_err(cx);
12792 })
12793 .unwrap();
12794 cx.executor().run_until_parked();
12795 workspace
12796 .update(cx, |workspace, _, cx| {
12797 let active_item = workspace
12798 .active_item(cx)
12799 .expect("should have an active item after navigating back from the 3rd buffer");
12800 assert_eq!(
12801 active_item.item_id(),
12802 multibuffer_item_id,
12803 "Should navigate back from the 3rd buffer to the multi buffer"
12804 );
12805 assert!(!active_item.is_singleton(cx));
12806 })
12807 .unwrap();
12808}
12809
12810#[gpui::test]
12811async fn test_toggle_selected_diff_hunks(
12812 executor: BackgroundExecutor,
12813 cx: &mut gpui::TestAppContext,
12814) {
12815 init_test(cx, |_| {});
12816
12817 let mut cx = EditorTestContext::new(cx).await;
12818
12819 let diff_base = r#"
12820 use some::mod;
12821
12822 const A: u32 = 42;
12823
12824 fn main() {
12825 println!("hello");
12826
12827 println!("world");
12828 }
12829 "#
12830 .unindent();
12831
12832 cx.set_state(
12833 &r#"
12834 use some::modified;
12835
12836 ˇ
12837 fn main() {
12838 println!("hello there");
12839
12840 println!("around the");
12841 println!("world");
12842 }
12843 "#
12844 .unindent(),
12845 );
12846
12847 cx.set_diff_base(&diff_base);
12848 executor.run_until_parked();
12849
12850 cx.update_editor(|editor, window, cx| {
12851 editor.go_to_next_hunk(&GoToHunk, window, cx);
12852 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
12853 });
12854 executor.run_until_parked();
12855 cx.assert_state_with_diff(
12856 r#"
12857 use some::modified;
12858
12859
12860 fn main() {
12861 - println!("hello");
12862 + ˇ println!("hello there");
12863
12864 println!("around the");
12865 println!("world");
12866 }
12867 "#
12868 .unindent(),
12869 );
12870
12871 cx.update_editor(|editor, window, cx| {
12872 for _ in 0..2 {
12873 editor.go_to_next_hunk(&GoToHunk, window, cx);
12874 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
12875 }
12876 });
12877 executor.run_until_parked();
12878 cx.assert_state_with_diff(
12879 r#"
12880 - use some::mod;
12881 + ˇuse some::modified;
12882
12883
12884 fn main() {
12885 - println!("hello");
12886 + println!("hello there");
12887
12888 + println!("around the");
12889 println!("world");
12890 }
12891 "#
12892 .unindent(),
12893 );
12894
12895 cx.update_editor(|editor, window, cx| {
12896 editor.go_to_next_hunk(&GoToHunk, window, cx);
12897 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
12898 });
12899 executor.run_until_parked();
12900 cx.assert_state_with_diff(
12901 r#"
12902 - use some::mod;
12903 + use some::modified;
12904
12905 - const A: u32 = 42;
12906 ˇ
12907 fn main() {
12908 - println!("hello");
12909 + println!("hello there");
12910
12911 + println!("around the");
12912 println!("world");
12913 }
12914 "#
12915 .unindent(),
12916 );
12917
12918 cx.update_editor(|editor, window, cx| {
12919 editor.cancel(&Cancel, window, cx);
12920 });
12921
12922 cx.assert_state_with_diff(
12923 r#"
12924 use some::modified;
12925
12926 ˇ
12927 fn main() {
12928 println!("hello there");
12929
12930 println!("around the");
12931 println!("world");
12932 }
12933 "#
12934 .unindent(),
12935 );
12936}
12937
12938#[gpui::test]
12939async fn test_diff_base_change_with_expanded_diff_hunks(
12940 executor: BackgroundExecutor,
12941 cx: &mut gpui::TestAppContext,
12942) {
12943 init_test(cx, |_| {});
12944
12945 let mut cx = EditorTestContext::new(cx).await;
12946
12947 let diff_base = r#"
12948 use some::mod1;
12949 use some::mod2;
12950
12951 const A: u32 = 42;
12952 const B: u32 = 42;
12953 const C: u32 = 42;
12954
12955 fn main() {
12956 println!("hello");
12957
12958 println!("world");
12959 }
12960 "#
12961 .unindent();
12962
12963 cx.set_state(
12964 &r#"
12965 use some::mod2;
12966
12967 const A: u32 = 42;
12968 const C: u32 = 42;
12969
12970 fn main(ˇ) {
12971 //println!("hello");
12972
12973 println!("world");
12974 //
12975 //
12976 }
12977 "#
12978 .unindent(),
12979 );
12980
12981 cx.set_diff_base(&diff_base);
12982 executor.run_until_parked();
12983
12984 cx.update_editor(|editor, window, cx| {
12985 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
12986 });
12987 executor.run_until_parked();
12988 cx.assert_state_with_diff(
12989 r#"
12990 - use some::mod1;
12991 use some::mod2;
12992
12993 const A: u32 = 42;
12994 - const B: u32 = 42;
12995 const C: u32 = 42;
12996
12997 fn main(ˇ) {
12998 - println!("hello");
12999 + //println!("hello");
13000
13001 println!("world");
13002 + //
13003 + //
13004 }
13005 "#
13006 .unindent(),
13007 );
13008
13009 cx.set_diff_base("new diff base!");
13010 executor.run_until_parked();
13011 cx.assert_state_with_diff(
13012 r#"
13013 use some::mod2;
13014
13015 const A: u32 = 42;
13016 const C: u32 = 42;
13017
13018 fn main(ˇ) {
13019 //println!("hello");
13020
13021 println!("world");
13022 //
13023 //
13024 }
13025 "#
13026 .unindent(),
13027 );
13028
13029 cx.update_editor(|editor, window, cx| {
13030 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13031 });
13032 executor.run_until_parked();
13033 cx.assert_state_with_diff(
13034 r#"
13035 - new diff base!
13036 + use some::mod2;
13037 +
13038 + const A: u32 = 42;
13039 + const C: u32 = 42;
13040 +
13041 + fn main(ˇ) {
13042 + //println!("hello");
13043 +
13044 + println!("world");
13045 + //
13046 + //
13047 + }
13048 "#
13049 .unindent(),
13050 );
13051}
13052
13053#[gpui::test]
13054async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
13055 init_test(cx, |_| {});
13056
13057 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13058 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13059 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13060 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13061 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13062 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13063
13064 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13065 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13066 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13067
13068 let multi_buffer = cx.new(|cx| {
13069 let mut multibuffer = MultiBuffer::new(ReadWrite);
13070 multibuffer.push_excerpts(
13071 buffer_1.clone(),
13072 [
13073 ExcerptRange {
13074 context: Point::new(0, 0)..Point::new(3, 0),
13075 primary: None,
13076 },
13077 ExcerptRange {
13078 context: Point::new(5, 0)..Point::new(7, 0),
13079 primary: None,
13080 },
13081 ExcerptRange {
13082 context: Point::new(9, 0)..Point::new(10, 3),
13083 primary: None,
13084 },
13085 ],
13086 cx,
13087 );
13088 multibuffer.push_excerpts(
13089 buffer_2.clone(),
13090 [
13091 ExcerptRange {
13092 context: Point::new(0, 0)..Point::new(3, 0),
13093 primary: None,
13094 },
13095 ExcerptRange {
13096 context: Point::new(5, 0)..Point::new(7, 0),
13097 primary: None,
13098 },
13099 ExcerptRange {
13100 context: Point::new(9, 0)..Point::new(10, 3),
13101 primary: None,
13102 },
13103 ],
13104 cx,
13105 );
13106 multibuffer.push_excerpts(
13107 buffer_3.clone(),
13108 [
13109 ExcerptRange {
13110 context: Point::new(0, 0)..Point::new(3, 0),
13111 primary: None,
13112 },
13113 ExcerptRange {
13114 context: Point::new(5, 0)..Point::new(7, 0),
13115 primary: None,
13116 },
13117 ExcerptRange {
13118 context: Point::new(9, 0)..Point::new(10, 3),
13119 primary: None,
13120 },
13121 ],
13122 cx,
13123 );
13124 multibuffer
13125 });
13126
13127 let editor = cx.add_window(|window, cx| {
13128 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13129 });
13130 editor
13131 .update(cx, |editor, _window, cx| {
13132 for (buffer, diff_base) in [
13133 (buffer_1.clone(), file_1_old),
13134 (buffer_2.clone(), file_2_old),
13135 (buffer_3.clone(), file_3_old),
13136 ] {
13137 let change_set = cx.new(|cx| {
13138 BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
13139 });
13140 editor
13141 .buffer
13142 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
13143 }
13144 })
13145 .unwrap();
13146
13147 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13148 cx.run_until_parked();
13149
13150 cx.assert_editor_state(
13151 &"
13152 ˇaaa
13153 ccc
13154 ddd
13155
13156 ggg
13157 hhh
13158
13159
13160 lll
13161 mmm
13162 NNN
13163
13164 qqq
13165 rrr
13166
13167 uuu
13168 111
13169 222
13170 333
13171
13172 666
13173 777
13174
13175 000
13176 !!!"
13177 .unindent(),
13178 );
13179
13180 cx.update_editor(|editor, window, cx| {
13181 editor.select_all(&SelectAll, window, cx);
13182 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13183 });
13184 cx.executor().run_until_parked();
13185
13186 cx.assert_state_with_diff(
13187 "
13188 «aaa
13189 - bbb
13190 ccc
13191 ddd
13192
13193 ggg
13194 hhh
13195
13196
13197 lll
13198 mmm
13199 - nnn
13200 + NNN
13201
13202 qqq
13203 rrr
13204
13205 uuu
13206 111
13207 222
13208 333
13209
13210 + 666
13211 777
13212
13213 000
13214 !!!ˇ»"
13215 .unindent(),
13216 );
13217}
13218
13219#[gpui::test]
13220async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
13221 init_test(cx, |_| {});
13222
13223 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13224 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
13225
13226 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13227 let multi_buffer = cx.new(|cx| {
13228 let mut multibuffer = MultiBuffer::new(ReadWrite);
13229 multibuffer.push_excerpts(
13230 buffer.clone(),
13231 [
13232 ExcerptRange {
13233 context: Point::new(0, 0)..Point::new(2, 0),
13234 primary: None,
13235 },
13236 ExcerptRange {
13237 context: Point::new(5, 0)..Point::new(7, 0),
13238 primary: None,
13239 },
13240 ],
13241 cx,
13242 );
13243 multibuffer
13244 });
13245
13246 let editor = cx.add_window(|window, cx| {
13247 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13248 });
13249 editor
13250 .update(cx, |editor, _window, cx| {
13251 let change_set =
13252 cx.new(|cx| BufferChangeSet::new_with_base_text(base.to_string(), &buffer, cx));
13253 editor
13254 .buffer
13255 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx))
13256 })
13257 .unwrap();
13258
13259 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13260 cx.run_until_parked();
13261
13262 cx.update_editor(|editor, window, cx| {
13263 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13264 });
13265 cx.executor().run_until_parked();
13266
13267 cx.assert_state_with_diff(
13268 "
13269 ˇaaa
13270 - bbb
13271 + BBB
13272
13273 + EEE
13274 fff
13275 "
13276 .unindent(),
13277 );
13278}
13279
13280#[gpui::test]
13281async fn test_edits_around_expanded_insertion_hunks(
13282 executor: BackgroundExecutor,
13283 cx: &mut gpui::TestAppContext,
13284) {
13285 init_test(cx, |_| {});
13286
13287 let mut cx = EditorTestContext::new(cx).await;
13288
13289 let diff_base = r#"
13290 use some::mod1;
13291 use some::mod2;
13292
13293 const A: u32 = 42;
13294
13295 fn main() {
13296 println!("hello");
13297
13298 println!("world");
13299 }
13300 "#
13301 .unindent();
13302 executor.run_until_parked();
13303 cx.set_state(
13304 &r#"
13305 use some::mod1;
13306 use some::mod2;
13307
13308 const A: u32 = 42;
13309 const B: u32 = 42;
13310 const C: u32 = 42;
13311 ˇ
13312
13313 fn main() {
13314 println!("hello");
13315
13316 println!("world");
13317 }
13318 "#
13319 .unindent(),
13320 );
13321
13322 cx.set_diff_base(&diff_base);
13323 executor.run_until_parked();
13324
13325 cx.update_editor(|editor, window, cx| {
13326 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13327 });
13328 executor.run_until_parked();
13329
13330 cx.assert_state_with_diff(
13331 r#"
13332 use some::mod1;
13333 use some::mod2;
13334
13335 const A: u32 = 42;
13336 + const B: u32 = 42;
13337 + const C: u32 = 42;
13338 + ˇ
13339
13340 fn main() {
13341 println!("hello");
13342
13343 println!("world");
13344 }
13345 "#
13346 .unindent(),
13347 );
13348
13349 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
13350 executor.run_until_parked();
13351
13352 cx.assert_state_with_diff(
13353 r#"
13354 use some::mod1;
13355 use some::mod2;
13356
13357 const A: u32 = 42;
13358 + const B: u32 = 42;
13359 + const C: u32 = 42;
13360 + const D: u32 = 42;
13361 + ˇ
13362
13363 fn main() {
13364 println!("hello");
13365
13366 println!("world");
13367 }
13368 "#
13369 .unindent(),
13370 );
13371
13372 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
13373 executor.run_until_parked();
13374
13375 cx.assert_state_with_diff(
13376 r#"
13377 use some::mod1;
13378 use some::mod2;
13379
13380 const A: u32 = 42;
13381 + const B: u32 = 42;
13382 + const C: u32 = 42;
13383 + const D: u32 = 42;
13384 + const E: u32 = 42;
13385 + ˇ
13386
13387 fn main() {
13388 println!("hello");
13389
13390 println!("world");
13391 }
13392 "#
13393 .unindent(),
13394 );
13395
13396 cx.update_editor(|editor, window, cx| {
13397 editor.delete_line(&DeleteLine, window, cx);
13398 });
13399 executor.run_until_parked();
13400
13401 cx.assert_state_with_diff(
13402 r#"
13403 use some::mod1;
13404 use some::mod2;
13405
13406 const A: u32 = 42;
13407 + const B: u32 = 42;
13408 + const C: u32 = 42;
13409 + const D: u32 = 42;
13410 + const E: u32 = 42;
13411 ˇ
13412 fn main() {
13413 println!("hello");
13414
13415 println!("world");
13416 }
13417 "#
13418 .unindent(),
13419 );
13420
13421 cx.update_editor(|editor, window, cx| {
13422 editor.move_up(&MoveUp, window, cx);
13423 editor.delete_line(&DeleteLine, window, cx);
13424 editor.move_up(&MoveUp, window, cx);
13425 editor.delete_line(&DeleteLine, window, cx);
13426 editor.move_up(&MoveUp, window, cx);
13427 editor.delete_line(&DeleteLine, window, cx);
13428 });
13429 executor.run_until_parked();
13430 cx.assert_state_with_diff(
13431 r#"
13432 use some::mod1;
13433 use some::mod2;
13434
13435 const A: u32 = 42;
13436 + const B: u32 = 42;
13437 ˇ
13438 fn main() {
13439 println!("hello");
13440
13441 println!("world");
13442 }
13443 "#
13444 .unindent(),
13445 );
13446
13447 cx.update_editor(|editor, window, cx| {
13448 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
13449 editor.delete_line(&DeleteLine, window, cx);
13450 });
13451 executor.run_until_parked();
13452 cx.assert_state_with_diff(
13453 r#"
13454 ˇ
13455 fn main() {
13456 println!("hello");
13457
13458 println!("world");
13459 }
13460 "#
13461 .unindent(),
13462 );
13463}
13464
13465#[gpui::test]
13466async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13467 init_test(cx, |_| {});
13468
13469 let mut cx = EditorTestContext::new(cx).await;
13470 cx.set_diff_base(indoc! { "
13471 one
13472 two
13473 three
13474 four
13475 five
13476 "
13477 });
13478 cx.set_state(indoc! { "
13479 one
13480 ˇthree
13481 five
13482 "});
13483 cx.run_until_parked();
13484 cx.update_editor(|editor, window, cx| {
13485 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13486 });
13487 cx.assert_state_with_diff(
13488 indoc! { "
13489 one
13490 - two
13491 ˇthree
13492 - four
13493 five
13494 "}
13495 .to_string(),
13496 );
13497 cx.update_editor(|editor, window, cx| {
13498 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13499 });
13500
13501 cx.assert_state_with_diff(
13502 indoc! { "
13503 one
13504 ˇthree
13505 five
13506 "}
13507 .to_string(),
13508 );
13509
13510 cx.set_state(indoc! { "
13511 one
13512 TWO
13513 ˇthree
13514 four
13515 five
13516 "});
13517 cx.run_until_parked();
13518 cx.update_editor(|editor, window, cx| {
13519 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13520 });
13521
13522 cx.assert_state_with_diff(
13523 indoc! { "
13524 one
13525 - two
13526 + TWO
13527 ˇthree
13528 four
13529 five
13530 "}
13531 .to_string(),
13532 );
13533 cx.update_editor(|editor, window, cx| {
13534 editor.move_up(&Default::default(), window, cx);
13535 editor.move_up(&Default::default(), window, cx);
13536 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
13537 });
13538 cx.assert_state_with_diff(
13539 indoc! { "
13540 one
13541 ˇTWO
13542 three
13543 four
13544 five
13545 "}
13546 .to_string(),
13547 );
13548}
13549
13550#[gpui::test]
13551async fn test_edits_around_expanded_deletion_hunks(
13552 executor: BackgroundExecutor,
13553 cx: &mut gpui::TestAppContext,
13554) {
13555 init_test(cx, |_| {});
13556
13557 let mut cx = EditorTestContext::new(cx).await;
13558
13559 let diff_base = r#"
13560 use some::mod1;
13561 use some::mod2;
13562
13563 const A: u32 = 42;
13564 const B: u32 = 42;
13565 const C: u32 = 42;
13566
13567
13568 fn main() {
13569 println!("hello");
13570
13571 println!("world");
13572 }
13573 "#
13574 .unindent();
13575 executor.run_until_parked();
13576 cx.set_state(
13577 &r#"
13578 use some::mod1;
13579 use some::mod2;
13580
13581 ˇconst B: u32 = 42;
13582 const C: u32 = 42;
13583
13584
13585 fn main() {
13586 println!("hello");
13587
13588 println!("world");
13589 }
13590 "#
13591 .unindent(),
13592 );
13593
13594 cx.set_diff_base(&diff_base);
13595 executor.run_until_parked();
13596
13597 cx.update_editor(|editor, window, cx| {
13598 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13599 });
13600 executor.run_until_parked();
13601
13602 cx.assert_state_with_diff(
13603 r#"
13604 use some::mod1;
13605 use some::mod2;
13606
13607 - const A: u32 = 42;
13608 ˇconst B: u32 = 42;
13609 const C: u32 = 42;
13610
13611
13612 fn main() {
13613 println!("hello");
13614
13615 println!("world");
13616 }
13617 "#
13618 .unindent(),
13619 );
13620
13621 cx.update_editor(|editor, window, cx| {
13622 editor.delete_line(&DeleteLine, window, cx);
13623 });
13624 executor.run_until_parked();
13625 cx.assert_state_with_diff(
13626 r#"
13627 use some::mod1;
13628 use some::mod2;
13629
13630 - const A: u32 = 42;
13631 - const B: u32 = 42;
13632 ˇconst C: u32 = 42;
13633
13634
13635 fn main() {
13636 println!("hello");
13637
13638 println!("world");
13639 }
13640 "#
13641 .unindent(),
13642 );
13643
13644 cx.update_editor(|editor, window, cx| {
13645 editor.delete_line(&DeleteLine, window, cx);
13646 });
13647 executor.run_until_parked();
13648 cx.assert_state_with_diff(
13649 r#"
13650 use some::mod1;
13651 use some::mod2;
13652
13653 - const A: u32 = 42;
13654 - const B: u32 = 42;
13655 - const C: u32 = 42;
13656 ˇ
13657
13658 fn main() {
13659 println!("hello");
13660
13661 println!("world");
13662 }
13663 "#
13664 .unindent(),
13665 );
13666
13667 cx.update_editor(|editor, window, cx| {
13668 editor.handle_input("replacement", window, cx);
13669 });
13670 executor.run_until_parked();
13671 cx.assert_state_with_diff(
13672 r#"
13673 use some::mod1;
13674 use some::mod2;
13675
13676 - const A: u32 = 42;
13677 - const B: u32 = 42;
13678 - const C: u32 = 42;
13679 -
13680 + replacementˇ
13681
13682 fn main() {
13683 println!("hello");
13684
13685 println!("world");
13686 }
13687 "#
13688 .unindent(),
13689 );
13690}
13691
13692#[gpui::test]
13693async fn test_backspace_after_deletion_hunk(
13694 executor: BackgroundExecutor,
13695 cx: &mut gpui::TestAppContext,
13696) {
13697 init_test(cx, |_| {});
13698
13699 let mut cx = EditorTestContext::new(cx).await;
13700
13701 let base_text = r#"
13702 one
13703 two
13704 three
13705 four
13706 five
13707 "#
13708 .unindent();
13709 executor.run_until_parked();
13710 cx.set_state(
13711 &r#"
13712 one
13713 two
13714 fˇour
13715 five
13716 "#
13717 .unindent(),
13718 );
13719
13720 cx.set_diff_base(&base_text);
13721 executor.run_until_parked();
13722
13723 cx.update_editor(|editor, window, cx| {
13724 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13725 });
13726 executor.run_until_parked();
13727
13728 cx.assert_state_with_diff(
13729 r#"
13730 one
13731 two
13732 - three
13733 fˇour
13734 five
13735 "#
13736 .unindent(),
13737 );
13738
13739 cx.update_editor(|editor, window, cx| {
13740 editor.backspace(&Backspace, window, cx);
13741 editor.backspace(&Backspace, window, cx);
13742 });
13743 executor.run_until_parked();
13744 cx.assert_state_with_diff(
13745 r#"
13746 one
13747 two
13748 - threeˇ
13749 - four
13750 + our
13751 five
13752 "#
13753 .unindent(),
13754 );
13755}
13756
13757#[gpui::test]
13758async fn test_edit_after_expanded_modification_hunk(
13759 executor: BackgroundExecutor,
13760 cx: &mut gpui::TestAppContext,
13761) {
13762 init_test(cx, |_| {});
13763
13764 let mut cx = EditorTestContext::new(cx).await;
13765
13766 let diff_base = r#"
13767 use some::mod1;
13768 use some::mod2;
13769
13770 const A: u32 = 42;
13771 const B: u32 = 42;
13772 const C: u32 = 42;
13773 const D: u32 = 42;
13774
13775
13776 fn main() {
13777 println!("hello");
13778
13779 println!("world");
13780 }"#
13781 .unindent();
13782
13783 cx.set_state(
13784 &r#"
13785 use some::mod1;
13786 use some::mod2;
13787
13788 const A: u32 = 42;
13789 const B: u32 = 42;
13790 const C: u32 = 43ˇ
13791 const D: u32 = 42;
13792
13793
13794 fn main() {
13795 println!("hello");
13796
13797 println!("world");
13798 }"#
13799 .unindent(),
13800 );
13801
13802 cx.set_diff_base(&diff_base);
13803 executor.run_until_parked();
13804 cx.update_editor(|editor, window, cx| {
13805 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, window, cx);
13806 });
13807 executor.run_until_parked();
13808
13809 cx.assert_state_with_diff(
13810 r#"
13811 use some::mod1;
13812 use some::mod2;
13813
13814 const A: u32 = 42;
13815 const B: u32 = 42;
13816 - const C: u32 = 42;
13817 + const C: u32 = 43ˇ
13818 const D: u32 = 42;
13819
13820
13821 fn main() {
13822 println!("hello");
13823
13824 println!("world");
13825 }"#
13826 .unindent(),
13827 );
13828
13829 cx.update_editor(|editor, window, cx| {
13830 editor.handle_input("\nnew_line\n", window, cx);
13831 });
13832 executor.run_until_parked();
13833
13834 cx.assert_state_with_diff(
13835 r#"
13836 use some::mod1;
13837 use some::mod2;
13838
13839 const A: u32 = 42;
13840 const B: u32 = 42;
13841 - const C: u32 = 42;
13842 + const C: u32 = 43
13843 + new_line
13844 + ˇ
13845 const D: u32 = 42;
13846
13847
13848 fn main() {
13849 println!("hello");
13850
13851 println!("world");
13852 }"#
13853 .unindent(),
13854 );
13855}
13856
13857async fn setup_indent_guides_editor(
13858 text: &str,
13859 cx: &mut gpui::TestAppContext,
13860) -> (BufferId, EditorTestContext) {
13861 init_test(cx, |_| {});
13862
13863 let mut cx = EditorTestContext::new(cx).await;
13864
13865 let buffer_id = cx.update_editor(|editor, window, cx| {
13866 editor.set_text(text, window, cx);
13867 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13868
13869 buffer_ids[0]
13870 });
13871
13872 (buffer_id, cx)
13873}
13874
13875fn assert_indent_guides(
13876 range: Range<u32>,
13877 expected: Vec<IndentGuide>,
13878 active_indices: Option<Vec<usize>>,
13879 cx: &mut EditorTestContext,
13880) {
13881 let indent_guides = cx.update_editor(|editor, window, cx| {
13882 let snapshot = editor.snapshot(window, cx).display_snapshot;
13883 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13884 editor,
13885 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13886 true,
13887 &snapshot,
13888 cx,
13889 );
13890
13891 indent_guides.sort_by(|a, b| {
13892 a.depth.cmp(&b.depth).then(
13893 a.start_row
13894 .cmp(&b.start_row)
13895 .then(a.end_row.cmp(&b.end_row)),
13896 )
13897 });
13898 indent_guides
13899 });
13900
13901 if let Some(expected) = active_indices {
13902 let active_indices = cx.update_editor(|editor, window, cx| {
13903 let snapshot = editor.snapshot(window, cx).display_snapshot;
13904 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
13905 });
13906
13907 assert_eq!(
13908 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13909 expected,
13910 "Active indent guide indices do not match"
13911 );
13912 }
13913
13914 assert_eq!(indent_guides, expected, "Indent guides do not match");
13915}
13916
13917fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13918 IndentGuide {
13919 buffer_id,
13920 start_row: MultiBufferRow(start_row),
13921 end_row: MultiBufferRow(end_row),
13922 depth,
13923 tab_size: 4,
13924 settings: IndentGuideSettings {
13925 enabled: true,
13926 line_width: 1,
13927 active_line_width: 1,
13928 ..Default::default()
13929 },
13930 }
13931}
13932
13933#[gpui::test]
13934async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13935 let (buffer_id, mut cx) = setup_indent_guides_editor(
13936 &"
13937 fn main() {
13938 let a = 1;
13939 }"
13940 .unindent(),
13941 cx,
13942 )
13943 .await;
13944
13945 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13946}
13947
13948#[gpui::test]
13949async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13950 let (buffer_id, mut cx) = setup_indent_guides_editor(
13951 &"
13952 fn main() {
13953 let a = 1;
13954 let b = 2;
13955 }"
13956 .unindent(),
13957 cx,
13958 )
13959 .await;
13960
13961 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13962}
13963
13964#[gpui::test]
13965async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13966 let (buffer_id, mut cx) = setup_indent_guides_editor(
13967 &"
13968 fn main() {
13969 let a = 1;
13970 if a == 3 {
13971 let b = 2;
13972 } else {
13973 let c = 3;
13974 }
13975 }"
13976 .unindent(),
13977 cx,
13978 )
13979 .await;
13980
13981 assert_indent_guides(
13982 0..8,
13983 vec![
13984 indent_guide(buffer_id, 1, 6, 0),
13985 indent_guide(buffer_id, 3, 3, 1),
13986 indent_guide(buffer_id, 5, 5, 1),
13987 ],
13988 None,
13989 &mut cx,
13990 );
13991}
13992
13993#[gpui::test]
13994async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13995 let (buffer_id, mut cx) = setup_indent_guides_editor(
13996 &"
13997 fn main() {
13998 let a = 1;
13999 let b = 2;
14000 let c = 3;
14001 }"
14002 .unindent(),
14003 cx,
14004 )
14005 .await;
14006
14007 assert_indent_guides(
14008 0..5,
14009 vec![
14010 indent_guide(buffer_id, 1, 3, 0),
14011 indent_guide(buffer_id, 2, 2, 1),
14012 ],
14013 None,
14014 &mut cx,
14015 );
14016}
14017
14018#[gpui::test]
14019async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
14020 let (buffer_id, mut cx) = setup_indent_guides_editor(
14021 &"
14022 fn main() {
14023 let a = 1;
14024
14025 let c = 3;
14026 }"
14027 .unindent(),
14028 cx,
14029 )
14030 .await;
14031
14032 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14033}
14034
14035#[gpui::test]
14036async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
14037 let (buffer_id, mut cx) = setup_indent_guides_editor(
14038 &"
14039 fn main() {
14040 let a = 1;
14041
14042 let c = 3;
14043
14044 if a == 3 {
14045 let b = 2;
14046 } else {
14047 let c = 3;
14048 }
14049 }"
14050 .unindent(),
14051 cx,
14052 )
14053 .await;
14054
14055 assert_indent_guides(
14056 0..11,
14057 vec![
14058 indent_guide(buffer_id, 1, 9, 0),
14059 indent_guide(buffer_id, 6, 6, 1),
14060 indent_guide(buffer_id, 8, 8, 1),
14061 ],
14062 None,
14063 &mut cx,
14064 );
14065}
14066
14067#[gpui::test]
14068async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
14069 let (buffer_id, mut cx) = setup_indent_guides_editor(
14070 &"
14071 fn main() {
14072 let a = 1;
14073
14074 let c = 3;
14075
14076 if a == 3 {
14077 let b = 2;
14078 } else {
14079 let c = 3;
14080 }
14081 }"
14082 .unindent(),
14083 cx,
14084 )
14085 .await;
14086
14087 assert_indent_guides(
14088 1..11,
14089 vec![
14090 indent_guide(buffer_id, 1, 9, 0),
14091 indent_guide(buffer_id, 6, 6, 1),
14092 indent_guide(buffer_id, 8, 8, 1),
14093 ],
14094 None,
14095 &mut cx,
14096 );
14097}
14098
14099#[gpui::test]
14100async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
14101 let (buffer_id, mut cx) = setup_indent_guides_editor(
14102 &"
14103 fn main() {
14104 let a = 1;
14105
14106 let c = 3;
14107
14108 if a == 3 {
14109 let b = 2;
14110 } else {
14111 let c = 3;
14112 }
14113 }"
14114 .unindent(),
14115 cx,
14116 )
14117 .await;
14118
14119 assert_indent_guides(
14120 1..10,
14121 vec![
14122 indent_guide(buffer_id, 1, 9, 0),
14123 indent_guide(buffer_id, 6, 6, 1),
14124 indent_guide(buffer_id, 8, 8, 1),
14125 ],
14126 None,
14127 &mut cx,
14128 );
14129}
14130
14131#[gpui::test]
14132async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
14133 let (buffer_id, mut cx) = setup_indent_guides_editor(
14134 &"
14135 block1
14136 block2
14137 block3
14138 block4
14139 block2
14140 block1
14141 block1"
14142 .unindent(),
14143 cx,
14144 )
14145 .await;
14146
14147 assert_indent_guides(
14148 1..10,
14149 vec![
14150 indent_guide(buffer_id, 1, 4, 0),
14151 indent_guide(buffer_id, 2, 3, 1),
14152 indent_guide(buffer_id, 3, 3, 2),
14153 ],
14154 None,
14155 &mut cx,
14156 );
14157}
14158
14159#[gpui::test]
14160async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
14161 let (buffer_id, mut cx) = setup_indent_guides_editor(
14162 &"
14163 block1
14164 block2
14165 block3
14166
14167 block1
14168 block1"
14169 .unindent(),
14170 cx,
14171 )
14172 .await;
14173
14174 assert_indent_guides(
14175 0..6,
14176 vec![
14177 indent_guide(buffer_id, 1, 2, 0),
14178 indent_guide(buffer_id, 2, 2, 1),
14179 ],
14180 None,
14181 &mut cx,
14182 );
14183}
14184
14185#[gpui::test]
14186async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
14187 let (buffer_id, mut cx) = setup_indent_guides_editor(
14188 &"
14189 block1
14190
14191
14192
14193 block2
14194 "
14195 .unindent(),
14196 cx,
14197 )
14198 .await;
14199
14200 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14201}
14202
14203#[gpui::test]
14204async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
14205 let (buffer_id, mut cx) = setup_indent_guides_editor(
14206 &"
14207 def a:
14208 \tb = 3
14209 \tif True:
14210 \t\tc = 4
14211 \t\td = 5
14212 \tprint(b)
14213 "
14214 .unindent(),
14215 cx,
14216 )
14217 .await;
14218
14219 assert_indent_guides(
14220 0..6,
14221 vec![
14222 indent_guide(buffer_id, 1, 6, 0),
14223 indent_guide(buffer_id, 3, 4, 1),
14224 ],
14225 None,
14226 &mut cx,
14227 );
14228}
14229
14230#[gpui::test]
14231async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
14232 let (buffer_id, mut cx) = setup_indent_guides_editor(
14233 &"
14234 fn main() {
14235 let a = 1;
14236 }"
14237 .unindent(),
14238 cx,
14239 )
14240 .await;
14241
14242 cx.update_editor(|editor, window, cx| {
14243 editor.change_selections(None, window, cx, |s| {
14244 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14245 });
14246 });
14247
14248 assert_indent_guides(
14249 0..3,
14250 vec![indent_guide(buffer_id, 1, 1, 0)],
14251 Some(vec![0]),
14252 &mut cx,
14253 );
14254}
14255
14256#[gpui::test]
14257async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
14258 let (buffer_id, mut cx) = setup_indent_guides_editor(
14259 &"
14260 fn main() {
14261 if 1 == 2 {
14262 let a = 1;
14263 }
14264 }"
14265 .unindent(),
14266 cx,
14267 )
14268 .await;
14269
14270 cx.update_editor(|editor, window, cx| {
14271 editor.change_selections(None, window, cx, |s| {
14272 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14273 });
14274 });
14275
14276 assert_indent_guides(
14277 0..4,
14278 vec![
14279 indent_guide(buffer_id, 1, 3, 0),
14280 indent_guide(buffer_id, 2, 2, 1),
14281 ],
14282 Some(vec![1]),
14283 &mut cx,
14284 );
14285
14286 cx.update_editor(|editor, window, cx| {
14287 editor.change_selections(None, window, cx, |s| {
14288 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14289 });
14290 });
14291
14292 assert_indent_guides(
14293 0..4,
14294 vec![
14295 indent_guide(buffer_id, 1, 3, 0),
14296 indent_guide(buffer_id, 2, 2, 1),
14297 ],
14298 Some(vec![1]),
14299 &mut cx,
14300 );
14301
14302 cx.update_editor(|editor, window, cx| {
14303 editor.change_selections(None, window, cx, |s| {
14304 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
14305 });
14306 });
14307
14308 assert_indent_guides(
14309 0..4,
14310 vec![
14311 indent_guide(buffer_id, 1, 3, 0),
14312 indent_guide(buffer_id, 2, 2, 1),
14313 ],
14314 Some(vec![0]),
14315 &mut cx,
14316 );
14317}
14318
14319#[gpui::test]
14320async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
14321 let (buffer_id, mut cx) = setup_indent_guides_editor(
14322 &"
14323 fn main() {
14324 let a = 1;
14325
14326 let b = 2;
14327 }"
14328 .unindent(),
14329 cx,
14330 )
14331 .await;
14332
14333 cx.update_editor(|editor, window, cx| {
14334 editor.change_selections(None, window, cx, |s| {
14335 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14336 });
14337 });
14338
14339 assert_indent_guides(
14340 0..5,
14341 vec![indent_guide(buffer_id, 1, 3, 0)],
14342 Some(vec![0]),
14343 &mut cx,
14344 );
14345}
14346
14347#[gpui::test]
14348async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
14349 let (buffer_id, mut cx) = setup_indent_guides_editor(
14350 &"
14351 def m:
14352 a = 1
14353 pass"
14354 .unindent(),
14355 cx,
14356 )
14357 .await;
14358
14359 cx.update_editor(|editor, window, cx| {
14360 editor.change_selections(None, window, cx, |s| {
14361 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14362 });
14363 });
14364
14365 assert_indent_guides(
14366 0..3,
14367 vec![indent_guide(buffer_id, 1, 2, 0)],
14368 Some(vec![0]),
14369 &mut cx,
14370 );
14371}
14372
14373#[gpui::test]
14374async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContext) {
14375 init_test(cx, |_| {});
14376 let mut cx = EditorTestContext::new(cx).await;
14377 let text = indoc! {
14378 "
14379 impl A {
14380 fn b() {
14381 0;
14382 3;
14383 5;
14384 6;
14385 7;
14386 }
14387 }
14388 "
14389 };
14390 let base_text = indoc! {
14391 "
14392 impl A {
14393 fn b() {
14394 0;
14395 1;
14396 2;
14397 3;
14398 4;
14399 }
14400 fn c() {
14401 5;
14402 6;
14403 7;
14404 }
14405 }
14406 "
14407 };
14408
14409 cx.update_editor(|editor, window, cx| {
14410 editor.set_text(text, window, cx);
14411
14412 editor.buffer().update(cx, |multibuffer, cx| {
14413 let buffer = multibuffer.as_singleton().unwrap();
14414 let change_set = cx.new(|cx| {
14415 let mut change_set = BufferChangeSet::new(&buffer, cx);
14416 let _ =
14417 change_set.set_base_text(base_text.into(), buffer.read(cx).text_snapshot(), cx);
14418 change_set
14419 });
14420
14421 multibuffer.set_all_diff_hunks_expanded(cx);
14422 multibuffer.add_change_set(change_set, cx);
14423
14424 buffer.read(cx).remote_id()
14425 })
14426 });
14427 cx.run_until_parked();
14428
14429 cx.assert_state_with_diff(
14430 indoc! { "
14431 impl A {
14432 fn b() {
14433 0;
14434 - 1;
14435 - 2;
14436 3;
14437 - 4;
14438 - }
14439 - fn c() {
14440 5;
14441 6;
14442 7;
14443 }
14444 }
14445 ˇ"
14446 }
14447 .to_string(),
14448 );
14449
14450 let mut actual_guides = cx.update_editor(|editor, window, cx| {
14451 editor
14452 .snapshot(window, cx)
14453 .buffer_snapshot
14454 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14455 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14456 .collect::<Vec<_>>()
14457 });
14458 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14459 assert_eq!(
14460 actual_guides,
14461 vec![
14462 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14463 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14464 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14465 ]
14466 );
14467}
14468
14469#[gpui::test]
14470fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
14471 init_test(cx, |_| {});
14472
14473 let editor = cx.add_window(|window, cx| {
14474 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
14475 build_editor(buffer, window, cx)
14476 });
14477
14478 let render_args = Arc::new(Mutex::new(None));
14479 let snapshot = editor
14480 .update(cx, |editor, window, cx| {
14481 let snapshot = editor.buffer().read(cx).snapshot(cx);
14482 let range =
14483 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
14484
14485 struct RenderArgs {
14486 row: MultiBufferRow,
14487 folded: bool,
14488 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
14489 }
14490
14491 let crease = Crease::inline(
14492 range,
14493 FoldPlaceholder::test(),
14494 {
14495 let toggle_callback = render_args.clone();
14496 move |row, folded, callback, _window, _cx| {
14497 *toggle_callback.lock() = Some(RenderArgs {
14498 row,
14499 folded,
14500 callback,
14501 });
14502 div()
14503 }
14504 },
14505 |_row, _folded, _window, _cx| div(),
14506 );
14507
14508 editor.insert_creases(Some(crease), cx);
14509 let snapshot = editor.snapshot(window, cx);
14510 let _div = snapshot.render_crease_toggle(
14511 MultiBufferRow(1),
14512 false,
14513 cx.entity().clone(),
14514 window,
14515 cx,
14516 );
14517 snapshot
14518 })
14519 .unwrap();
14520
14521 let render_args = render_args.lock().take().unwrap();
14522 assert_eq!(render_args.row, MultiBufferRow(1));
14523 assert!(!render_args.folded);
14524 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14525
14526 cx.update_window(*editor, |_, window, cx| {
14527 (render_args.callback)(true, window, cx)
14528 })
14529 .unwrap();
14530 let snapshot = editor
14531 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14532 .unwrap();
14533 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
14534
14535 cx.update_window(*editor, |_, window, cx| {
14536 (render_args.callback)(false, window, cx)
14537 })
14538 .unwrap();
14539 let snapshot = editor
14540 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
14541 .unwrap();
14542 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14543}
14544
14545#[gpui::test]
14546async fn test_input_text(cx: &mut gpui::TestAppContext) {
14547 init_test(cx, |_| {});
14548 let mut cx = EditorTestContext::new(cx).await;
14549
14550 cx.set_state(
14551 &r#"ˇone
14552 two
14553
14554 three
14555 fourˇ
14556 five
14557
14558 siˇx"#
14559 .unindent(),
14560 );
14561
14562 cx.dispatch_action(HandleInput(String::new()));
14563 cx.assert_editor_state(
14564 &r#"ˇone
14565 two
14566
14567 three
14568 fourˇ
14569 five
14570
14571 siˇx"#
14572 .unindent(),
14573 );
14574
14575 cx.dispatch_action(HandleInput("AAAA".to_string()));
14576 cx.assert_editor_state(
14577 &r#"AAAAˇone
14578 two
14579
14580 three
14581 fourAAAAˇ
14582 five
14583
14584 siAAAAˇx"#
14585 .unindent(),
14586 );
14587}
14588
14589#[gpui::test]
14590async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
14591 init_test(cx, |_| {});
14592
14593 let mut cx = EditorTestContext::new(cx).await;
14594 cx.set_state(
14595 r#"let foo = 1;
14596let foo = 2;
14597let foo = 3;
14598let fooˇ = 4;
14599let foo = 5;
14600let foo = 6;
14601let foo = 7;
14602let foo = 8;
14603let foo = 9;
14604let foo = 10;
14605let foo = 11;
14606let foo = 12;
14607let foo = 13;
14608let foo = 14;
14609let foo = 15;"#,
14610 );
14611
14612 cx.update_editor(|e, window, cx| {
14613 assert_eq!(
14614 e.next_scroll_position,
14615 NextScrollCursorCenterTopBottom::Center,
14616 "Default next scroll direction is center",
14617 );
14618
14619 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14620 assert_eq!(
14621 e.next_scroll_position,
14622 NextScrollCursorCenterTopBottom::Top,
14623 "After center, next scroll direction should be top",
14624 );
14625
14626 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14627 assert_eq!(
14628 e.next_scroll_position,
14629 NextScrollCursorCenterTopBottom::Bottom,
14630 "After top, next scroll direction should be bottom",
14631 );
14632
14633 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14634 assert_eq!(
14635 e.next_scroll_position,
14636 NextScrollCursorCenterTopBottom::Center,
14637 "After bottom, scrolling should start over",
14638 );
14639
14640 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
14641 assert_eq!(
14642 e.next_scroll_position,
14643 NextScrollCursorCenterTopBottom::Top,
14644 "Scrolling continues if retriggered fast enough"
14645 );
14646 });
14647
14648 cx.executor()
14649 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
14650 cx.executor().run_until_parked();
14651 cx.update_editor(|e, _, _| {
14652 assert_eq!(
14653 e.next_scroll_position,
14654 NextScrollCursorCenterTopBottom::Center,
14655 "If scrolling is not triggered fast enough, it should reset"
14656 );
14657 });
14658}
14659
14660#[gpui::test]
14661async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
14662 init_test(cx, |_| {});
14663 let mut cx = EditorLspTestContext::new_rust(
14664 lsp::ServerCapabilities {
14665 definition_provider: Some(lsp::OneOf::Left(true)),
14666 references_provider: Some(lsp::OneOf::Left(true)),
14667 ..lsp::ServerCapabilities::default()
14668 },
14669 cx,
14670 )
14671 .await;
14672
14673 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
14674 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
14675 move |params, _| async move {
14676 if empty_go_to_definition {
14677 Ok(None)
14678 } else {
14679 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
14680 uri: params.text_document_position_params.text_document.uri,
14681 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
14682 })))
14683 }
14684 },
14685 );
14686 let references =
14687 cx.lsp
14688 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
14689 Ok(Some(vec![lsp::Location {
14690 uri: params.text_document_position.text_document.uri,
14691 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
14692 }]))
14693 });
14694 (go_to_definition, references)
14695 };
14696
14697 cx.set_state(
14698 &r#"fn one() {
14699 let mut a = ˇtwo();
14700 }
14701
14702 fn two() {}"#
14703 .unindent(),
14704 );
14705 set_up_lsp_handlers(false, &mut cx);
14706 let navigated = cx
14707 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
14708 .await
14709 .expect("Failed to navigate to definition");
14710 assert_eq!(
14711 navigated,
14712 Navigated::Yes,
14713 "Should have navigated to definition from the GetDefinition response"
14714 );
14715 cx.assert_editor_state(
14716 &r#"fn one() {
14717 let mut a = two();
14718 }
14719
14720 fn «twoˇ»() {}"#
14721 .unindent(),
14722 );
14723
14724 let editors = cx.update_workspace(|workspace, _, cx| {
14725 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14726 });
14727 cx.update_editor(|_, _, test_editor_cx| {
14728 assert_eq!(
14729 editors.len(),
14730 1,
14731 "Initially, only one, test, editor should be open in the workspace"
14732 );
14733 assert_eq!(
14734 test_editor_cx.entity(),
14735 editors.last().expect("Asserted len is 1").clone()
14736 );
14737 });
14738
14739 set_up_lsp_handlers(true, &mut cx);
14740 let navigated = cx
14741 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
14742 .await
14743 .expect("Failed to navigate to lookup references");
14744 assert_eq!(
14745 navigated,
14746 Navigated::Yes,
14747 "Should have navigated to references as a fallback after empty GoToDefinition response"
14748 );
14749 // We should not change the selections in the existing file,
14750 // if opening another milti buffer with the references
14751 cx.assert_editor_state(
14752 &r#"fn one() {
14753 let mut a = two();
14754 }
14755
14756 fn «twoˇ»() {}"#
14757 .unindent(),
14758 );
14759 let editors = cx.update_workspace(|workspace, _, cx| {
14760 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14761 });
14762 cx.update_editor(|_, _, test_editor_cx| {
14763 assert_eq!(
14764 editors.len(),
14765 2,
14766 "After falling back to references search, we open a new editor with the results"
14767 );
14768 let references_fallback_text = editors
14769 .into_iter()
14770 .find(|new_editor| *new_editor != test_editor_cx.entity())
14771 .expect("Should have one non-test editor now")
14772 .read(test_editor_cx)
14773 .text(test_editor_cx);
14774 assert_eq!(
14775 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14776 "Should use the range from the references response and not the GoToDefinition one"
14777 );
14778 });
14779}
14780
14781#[gpui::test]
14782async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14783 init_test(cx, |_| {});
14784
14785 let language = Arc::new(Language::new(
14786 LanguageConfig::default(),
14787 Some(tree_sitter_rust::LANGUAGE.into()),
14788 ));
14789
14790 let text = r#"
14791 #[cfg(test)]
14792 mod tests() {
14793 #[test]
14794 fn runnable_1() {
14795 let a = 1;
14796 }
14797
14798 #[test]
14799 fn runnable_2() {
14800 let a = 1;
14801 let b = 2;
14802 }
14803 }
14804 "#
14805 .unindent();
14806
14807 let fs = FakeFs::new(cx.executor());
14808 fs.insert_file("/file.rs", Default::default()).await;
14809
14810 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14811 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14812 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14813 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14814 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14815
14816 let editor = cx.new_window_entity(|window, cx| {
14817 Editor::new(
14818 EditorMode::Full,
14819 multi_buffer,
14820 Some(project.clone()),
14821 true,
14822 window,
14823 cx,
14824 )
14825 });
14826
14827 editor.update_in(cx, |editor, window, cx| {
14828 editor.tasks.insert(
14829 (buffer.read(cx).remote_id(), 3),
14830 RunnableTasks {
14831 templates: vec![],
14832 offset: MultiBufferOffset(43),
14833 column: 0,
14834 extra_variables: HashMap::default(),
14835 context_range: BufferOffset(43)..BufferOffset(85),
14836 },
14837 );
14838 editor.tasks.insert(
14839 (buffer.read(cx).remote_id(), 8),
14840 RunnableTasks {
14841 templates: vec![],
14842 offset: MultiBufferOffset(86),
14843 column: 0,
14844 extra_variables: HashMap::default(),
14845 context_range: BufferOffset(86)..BufferOffset(191),
14846 },
14847 );
14848
14849 // Test finding task when cursor is inside function body
14850 editor.change_selections(None, window, cx, |s| {
14851 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14852 });
14853 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14854 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14855
14856 // Test finding task when cursor is on function name
14857 editor.change_selections(None, window, cx, |s| {
14858 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14859 });
14860 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14861 assert_eq!(row, 8, "Should find task when cursor is on function name");
14862 });
14863}
14864
14865#[gpui::test]
14866async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14867 init_test(cx, |_| {});
14868
14869 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14870 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14871 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14872
14873 let fs = FakeFs::new(cx.executor());
14874 fs.insert_tree(
14875 "/a",
14876 json!({
14877 "first.rs": sample_text_1,
14878 "second.rs": sample_text_2,
14879 "third.rs": sample_text_3,
14880 }),
14881 )
14882 .await;
14883 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14884 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14885 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14886 let worktree = project.update(cx, |project, cx| {
14887 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14888 assert_eq!(worktrees.len(), 1);
14889 worktrees.pop().unwrap()
14890 });
14891 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14892
14893 let buffer_1 = project
14894 .update(cx, |project, cx| {
14895 project.open_buffer((worktree_id, "first.rs"), cx)
14896 })
14897 .await
14898 .unwrap();
14899 let buffer_2 = project
14900 .update(cx, |project, cx| {
14901 project.open_buffer((worktree_id, "second.rs"), cx)
14902 })
14903 .await
14904 .unwrap();
14905 let buffer_3 = project
14906 .update(cx, |project, cx| {
14907 project.open_buffer((worktree_id, "third.rs"), cx)
14908 })
14909 .await
14910 .unwrap();
14911
14912 let multi_buffer = cx.new(|cx| {
14913 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14914 multi_buffer.push_excerpts(
14915 buffer_1.clone(),
14916 [
14917 ExcerptRange {
14918 context: Point::new(0, 0)..Point::new(3, 0),
14919 primary: None,
14920 },
14921 ExcerptRange {
14922 context: Point::new(5, 0)..Point::new(7, 0),
14923 primary: None,
14924 },
14925 ExcerptRange {
14926 context: Point::new(9, 0)..Point::new(10, 4),
14927 primary: None,
14928 },
14929 ],
14930 cx,
14931 );
14932 multi_buffer.push_excerpts(
14933 buffer_2.clone(),
14934 [
14935 ExcerptRange {
14936 context: Point::new(0, 0)..Point::new(3, 0),
14937 primary: None,
14938 },
14939 ExcerptRange {
14940 context: Point::new(5, 0)..Point::new(7, 0),
14941 primary: None,
14942 },
14943 ExcerptRange {
14944 context: Point::new(9, 0)..Point::new(10, 4),
14945 primary: None,
14946 },
14947 ],
14948 cx,
14949 );
14950 multi_buffer.push_excerpts(
14951 buffer_3.clone(),
14952 [
14953 ExcerptRange {
14954 context: Point::new(0, 0)..Point::new(3, 0),
14955 primary: None,
14956 },
14957 ExcerptRange {
14958 context: Point::new(5, 0)..Point::new(7, 0),
14959 primary: None,
14960 },
14961 ExcerptRange {
14962 context: Point::new(9, 0)..Point::new(10, 4),
14963 primary: None,
14964 },
14965 ],
14966 cx,
14967 );
14968 multi_buffer
14969 });
14970 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
14971 Editor::new(
14972 EditorMode::Full,
14973 multi_buffer,
14974 Some(project.clone()),
14975 true,
14976 window,
14977 cx,
14978 )
14979 });
14980
14981 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";
14982 assert_eq!(
14983 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14984 full_text,
14985 );
14986
14987 multi_buffer_editor.update(cx, |editor, cx| {
14988 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14989 });
14990 assert_eq!(
14991 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14992 "\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",
14993 "After folding the first buffer, its text should not be displayed"
14994 );
14995
14996 multi_buffer_editor.update(cx, |editor, cx| {
14997 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14998 });
14999 assert_eq!(
15000 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15001 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
15002 "After folding the second buffer, its text should not be displayed"
15003 );
15004
15005 multi_buffer_editor.update(cx, |editor, cx| {
15006 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15007 });
15008 assert_eq!(
15009 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15010 "\n\n\n\n\n",
15011 "After folding the third buffer, its text should not be displayed"
15012 );
15013
15014 // Emulate selection inside the fold logic, that should work
15015 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15016 editor
15017 .snapshot(window, cx)
15018 .next_line_boundary(Point::new(0, 4));
15019 });
15020
15021 multi_buffer_editor.update(cx, |editor, cx| {
15022 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15023 });
15024 assert_eq!(
15025 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15026 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
15027 "After unfolding the second buffer, its text should be displayed"
15028 );
15029
15030 multi_buffer_editor.update(cx, |editor, cx| {
15031 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15032 });
15033 assert_eq!(
15034 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15035 "\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",
15036 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
15037 );
15038
15039 multi_buffer_editor.update(cx, |editor, cx| {
15040 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15041 });
15042 assert_eq!(
15043 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15044 full_text,
15045 "After unfolding the all buffers, all original text should be displayed"
15046 );
15047}
15048
15049#[gpui::test]
15050async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
15051 init_test(cx, |_| {});
15052
15053 let sample_text_1 = "1111\n2222\n3333".to_string();
15054 let sample_text_2 = "4444\n5555\n6666".to_string();
15055 let sample_text_3 = "7777\n8888\n9999".to_string();
15056
15057 let fs = FakeFs::new(cx.executor());
15058 fs.insert_tree(
15059 "/a",
15060 json!({
15061 "first.rs": sample_text_1,
15062 "second.rs": sample_text_2,
15063 "third.rs": sample_text_3,
15064 }),
15065 )
15066 .await;
15067 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15068 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15069 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15070 let worktree = project.update(cx, |project, cx| {
15071 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15072 assert_eq!(worktrees.len(), 1);
15073 worktrees.pop().unwrap()
15074 });
15075 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15076
15077 let buffer_1 = project
15078 .update(cx, |project, cx| {
15079 project.open_buffer((worktree_id, "first.rs"), cx)
15080 })
15081 .await
15082 .unwrap();
15083 let buffer_2 = project
15084 .update(cx, |project, cx| {
15085 project.open_buffer((worktree_id, "second.rs"), cx)
15086 })
15087 .await
15088 .unwrap();
15089 let buffer_3 = project
15090 .update(cx, |project, cx| {
15091 project.open_buffer((worktree_id, "third.rs"), cx)
15092 })
15093 .await
15094 .unwrap();
15095
15096 let multi_buffer = cx.new(|cx| {
15097 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15098 multi_buffer.push_excerpts(
15099 buffer_1.clone(),
15100 [ExcerptRange {
15101 context: Point::new(0, 0)..Point::new(3, 0),
15102 primary: None,
15103 }],
15104 cx,
15105 );
15106 multi_buffer.push_excerpts(
15107 buffer_2.clone(),
15108 [ExcerptRange {
15109 context: Point::new(0, 0)..Point::new(3, 0),
15110 primary: None,
15111 }],
15112 cx,
15113 );
15114 multi_buffer.push_excerpts(
15115 buffer_3.clone(),
15116 [ExcerptRange {
15117 context: Point::new(0, 0)..Point::new(3, 0),
15118 primary: None,
15119 }],
15120 cx,
15121 );
15122 multi_buffer
15123 });
15124
15125 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15126 Editor::new(
15127 EditorMode::Full,
15128 multi_buffer,
15129 Some(project.clone()),
15130 true,
15131 window,
15132 cx,
15133 )
15134 });
15135
15136 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
15137 assert_eq!(
15138 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15139 full_text,
15140 );
15141
15142 multi_buffer_editor.update(cx, |editor, cx| {
15143 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
15144 });
15145 assert_eq!(
15146 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15147 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
15148 "After folding the first buffer, its text should not be displayed"
15149 );
15150
15151 multi_buffer_editor.update(cx, |editor, cx| {
15152 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
15153 });
15154
15155 assert_eq!(
15156 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15157 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
15158 "After folding the second buffer, its text should not be displayed"
15159 );
15160
15161 multi_buffer_editor.update(cx, |editor, cx| {
15162 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
15163 });
15164 assert_eq!(
15165 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15166 "\n\n\n\n\n",
15167 "After folding the third buffer, its text should not be displayed"
15168 );
15169
15170 multi_buffer_editor.update(cx, |editor, cx| {
15171 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
15172 });
15173 assert_eq!(
15174 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15175 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
15176 "After unfolding the second buffer, its text should be displayed"
15177 );
15178
15179 multi_buffer_editor.update(cx, |editor, cx| {
15180 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
15181 });
15182 assert_eq!(
15183 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15184 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
15185 "After unfolding the first buffer, its text should be displayed"
15186 );
15187
15188 multi_buffer_editor.update(cx, |editor, cx| {
15189 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
15190 });
15191 assert_eq!(
15192 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15193 full_text,
15194 "After unfolding all buffers, all original text should be displayed"
15195 );
15196}
15197
15198#[gpui::test]
15199async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
15200 init_test(cx, |_| {});
15201
15202 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15203
15204 let fs = FakeFs::new(cx.executor());
15205 fs.insert_tree(
15206 "/a",
15207 json!({
15208 "main.rs": sample_text,
15209 }),
15210 )
15211 .await;
15212 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15213 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15214 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15215 let worktree = project.update(cx, |project, cx| {
15216 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15217 assert_eq!(worktrees.len(), 1);
15218 worktrees.pop().unwrap()
15219 });
15220 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15221
15222 let buffer_1 = project
15223 .update(cx, |project, cx| {
15224 project.open_buffer((worktree_id, "main.rs"), cx)
15225 })
15226 .await
15227 .unwrap();
15228
15229 let multi_buffer = cx.new(|cx| {
15230 let mut multi_buffer = MultiBuffer::new(ReadWrite);
15231 multi_buffer.push_excerpts(
15232 buffer_1.clone(),
15233 [ExcerptRange {
15234 context: Point::new(0, 0)
15235 ..Point::new(
15236 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
15237 0,
15238 ),
15239 primary: None,
15240 }],
15241 cx,
15242 );
15243 multi_buffer
15244 });
15245 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
15246 Editor::new(
15247 EditorMode::Full,
15248 multi_buffer,
15249 Some(project.clone()),
15250 true,
15251 window,
15252 cx,
15253 )
15254 });
15255
15256 let selection_range = Point::new(1, 0)..Point::new(2, 0);
15257 multi_buffer_editor.update_in(cx, |editor, window, cx| {
15258 enum TestHighlight {}
15259 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
15260 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
15261 editor.highlight_text::<TestHighlight>(
15262 vec![highlight_range.clone()],
15263 HighlightStyle::color(Hsla::green()),
15264 cx,
15265 );
15266 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
15267 });
15268
15269 let full_text = format!("\n\n\n{sample_text}\n");
15270 assert_eq!(
15271 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
15272 full_text,
15273 );
15274}
15275
15276#[gpui::test]
15277async fn test_inline_completion_text(cx: &mut TestAppContext) {
15278 init_test(cx, |_| {});
15279
15280 // Simple insertion
15281 assert_highlighted_edits(
15282 "Hello, world!",
15283 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
15284 true,
15285 cx,
15286 |highlighted_edits, cx| {
15287 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
15288 assert_eq!(highlighted_edits.highlights.len(), 1);
15289 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
15290 assert_eq!(
15291 highlighted_edits.highlights[0].1.background_color,
15292 Some(cx.theme().status().created_background)
15293 );
15294 },
15295 )
15296 .await;
15297
15298 // Replacement
15299 assert_highlighted_edits(
15300 "This is a test.",
15301 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
15302 false,
15303 cx,
15304 |highlighted_edits, cx| {
15305 assert_eq!(highlighted_edits.text, "That is a test.");
15306 assert_eq!(highlighted_edits.highlights.len(), 1);
15307 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
15308 assert_eq!(
15309 highlighted_edits.highlights[0].1.background_color,
15310 Some(cx.theme().status().created_background)
15311 );
15312 },
15313 )
15314 .await;
15315
15316 // Multiple edits
15317 assert_highlighted_edits(
15318 "Hello, world!",
15319 vec![
15320 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
15321 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
15322 ],
15323 false,
15324 cx,
15325 |highlighted_edits, cx| {
15326 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
15327 assert_eq!(highlighted_edits.highlights.len(), 2);
15328 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
15329 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
15330 assert_eq!(
15331 highlighted_edits.highlights[0].1.background_color,
15332 Some(cx.theme().status().created_background)
15333 );
15334 assert_eq!(
15335 highlighted_edits.highlights[1].1.background_color,
15336 Some(cx.theme().status().created_background)
15337 );
15338 },
15339 )
15340 .await;
15341
15342 // Multiple lines with edits
15343 assert_highlighted_edits(
15344 "First line\nSecond line\nThird line\nFourth line",
15345 vec![
15346 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
15347 (
15348 Point::new(2, 0)..Point::new(2, 10),
15349 "New third line".to_string(),
15350 ),
15351 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
15352 ],
15353 false,
15354 cx,
15355 |highlighted_edits, cx| {
15356 assert_eq!(
15357 highlighted_edits.text,
15358 "Second modified\nNew third line\nFourth updated line"
15359 );
15360 assert_eq!(highlighted_edits.highlights.len(), 3);
15361 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
15362 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
15363 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
15364 for highlight in &highlighted_edits.highlights {
15365 assert_eq!(
15366 highlight.1.background_color,
15367 Some(cx.theme().status().created_background)
15368 );
15369 }
15370 },
15371 )
15372 .await;
15373}
15374
15375#[gpui::test]
15376async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
15377 init_test(cx, |_| {});
15378
15379 // Deletion
15380 assert_highlighted_edits(
15381 "Hello, world!",
15382 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
15383 true,
15384 cx,
15385 |highlighted_edits, cx| {
15386 assert_eq!(highlighted_edits.text, "Hello, world!");
15387 assert_eq!(highlighted_edits.highlights.len(), 1);
15388 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
15389 assert_eq!(
15390 highlighted_edits.highlights[0].1.background_color,
15391 Some(cx.theme().status().deleted_background)
15392 );
15393 },
15394 )
15395 .await;
15396
15397 // Insertion
15398 assert_highlighted_edits(
15399 "Hello, world!",
15400 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
15401 true,
15402 cx,
15403 |highlighted_edits, cx| {
15404 assert_eq!(highlighted_edits.highlights.len(), 1);
15405 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
15406 assert_eq!(
15407 highlighted_edits.highlights[0].1.background_color,
15408 Some(cx.theme().status().created_background)
15409 );
15410 },
15411 )
15412 .await;
15413}
15414
15415async fn assert_highlighted_edits(
15416 text: &str,
15417 edits: Vec<(Range<Point>, String)>,
15418 include_deletions: bool,
15419 cx: &mut TestAppContext,
15420 assertion_fn: impl Fn(HighlightedText, &App),
15421) {
15422 let window = cx.add_window(|window, cx| {
15423 let buffer = MultiBuffer::build_simple(text, cx);
15424 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
15425 });
15426 let cx = &mut VisualTestContext::from_window(*window, cx);
15427
15428 let (buffer, snapshot) = window
15429 .update(cx, |editor, _window, cx| {
15430 (
15431 editor.buffer().clone(),
15432 editor.buffer().read(cx).snapshot(cx),
15433 )
15434 })
15435 .unwrap();
15436
15437 let edits = edits
15438 .into_iter()
15439 .map(|(range, edit)| {
15440 (
15441 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
15442 edit,
15443 )
15444 })
15445 .collect::<Vec<_>>();
15446
15447 let text_anchor_edits = edits
15448 .clone()
15449 .into_iter()
15450 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
15451 .collect::<Vec<_>>();
15452
15453 let edit_preview = window
15454 .update(cx, |_, _window, cx| {
15455 buffer
15456 .read(cx)
15457 .as_singleton()
15458 .unwrap()
15459 .read(cx)
15460 .preview_edits(text_anchor_edits.into(), cx)
15461 })
15462 .unwrap()
15463 .await;
15464
15465 cx.update(|_window, cx| {
15466 let highlighted_edits = inline_completion_edit_text(
15467 &snapshot.as_singleton().unwrap().2,
15468 &edits,
15469 &edit_preview,
15470 include_deletions,
15471 cx,
15472 )
15473 .expect("Missing highlighted edits");
15474 assertion_fn(highlighted_edits, cx)
15475 });
15476}
15477
15478#[gpui::test]
15479async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
15480 init_test(cx, |_| {});
15481 let capabilities = lsp::ServerCapabilities {
15482 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
15483 prepare_provider: Some(true),
15484 work_done_progress_options: Default::default(),
15485 })),
15486 ..Default::default()
15487 };
15488 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15489
15490 cx.set_state(indoc! {"
15491 struct Fˇoo {}
15492 "});
15493
15494 cx.update_editor(|editor, _, cx| {
15495 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15496 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15497 editor.highlight_background::<DocumentHighlightRead>(
15498 &[highlight_range],
15499 |c| c.editor_document_highlight_read_background,
15500 cx,
15501 );
15502 });
15503
15504 let mut prepare_rename_handler =
15505 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
15506 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
15507 start: lsp::Position {
15508 line: 0,
15509 character: 7,
15510 },
15511 end: lsp::Position {
15512 line: 0,
15513 character: 10,
15514 },
15515 })))
15516 });
15517 let prepare_rename_task = cx
15518 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15519 .expect("Prepare rename was not started");
15520 prepare_rename_handler.next().await.unwrap();
15521 prepare_rename_task.await.expect("Prepare rename failed");
15522
15523 let mut rename_handler =
15524 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15525 let edit = lsp::TextEdit {
15526 range: lsp::Range {
15527 start: lsp::Position {
15528 line: 0,
15529 character: 7,
15530 },
15531 end: lsp::Position {
15532 line: 0,
15533 character: 10,
15534 },
15535 },
15536 new_text: "FooRenamed".to_string(),
15537 };
15538 Ok(Some(lsp::WorkspaceEdit::new(
15539 // Specify the same edit twice
15540 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
15541 )))
15542 });
15543 let rename_task = cx
15544 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15545 .expect("Confirm rename was not started");
15546 rename_handler.next().await.unwrap();
15547 rename_task.await.expect("Confirm rename failed");
15548 cx.run_until_parked();
15549
15550 // Despite two edits, only one is actually applied as those are identical
15551 cx.assert_editor_state(indoc! {"
15552 struct FooRenamedˇ {}
15553 "});
15554}
15555
15556#[gpui::test]
15557async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
15558 init_test(cx, |_| {});
15559 // These capabilities indicate that the server does not support prepare rename.
15560 let capabilities = lsp::ServerCapabilities {
15561 rename_provider: Some(lsp::OneOf::Left(true)),
15562 ..Default::default()
15563 };
15564 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15565
15566 cx.set_state(indoc! {"
15567 struct Fˇoo {}
15568 "});
15569
15570 cx.update_editor(|editor, _window, cx| {
15571 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15572 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15573 editor.highlight_background::<DocumentHighlightRead>(
15574 &[highlight_range],
15575 |c| c.editor_document_highlight_read_background,
15576 cx,
15577 );
15578 });
15579
15580 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
15581 .expect("Prepare rename was not started")
15582 .await
15583 .expect("Prepare rename failed");
15584
15585 let mut rename_handler =
15586 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15587 let edit = lsp::TextEdit {
15588 range: lsp::Range {
15589 start: lsp::Position {
15590 line: 0,
15591 character: 7,
15592 },
15593 end: lsp::Position {
15594 line: 0,
15595 character: 10,
15596 },
15597 },
15598 new_text: "FooRenamed".to_string(),
15599 };
15600 Ok(Some(lsp::WorkspaceEdit::new(
15601 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
15602 )))
15603 });
15604 let rename_task = cx
15605 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
15606 .expect("Confirm rename was not started");
15607 rename_handler.next().await.unwrap();
15608 rename_task.await.expect("Confirm rename failed");
15609 cx.run_until_parked();
15610
15611 // Correct range is renamed, as `surrounding_word` is used to find it.
15612 cx.assert_editor_state(indoc! {"
15613 struct FooRenamedˇ {}
15614 "});
15615}
15616
15617fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
15618 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
15619 point..point
15620}
15621
15622fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
15623 let (text, ranges) = marked_text_ranges(marked_text, true);
15624 assert_eq!(editor.text(cx), text);
15625 assert_eq!(
15626 editor.selections.ranges(cx),
15627 ranges,
15628 "Assert selections are {}",
15629 marked_text
15630 );
15631}
15632
15633pub fn handle_signature_help_request(
15634 cx: &mut EditorLspTestContext,
15635 mocked_response: lsp::SignatureHelp,
15636) -> impl Future<Output = ()> {
15637 let mut request =
15638 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
15639 let mocked_response = mocked_response.clone();
15640 async move { Ok(Some(mocked_response)) }
15641 });
15642
15643 async move {
15644 request.next().await;
15645 }
15646}
15647
15648/// Handle completion request passing a marked string specifying where the completion
15649/// should be triggered from using '|' character, what range should be replaced, and what completions
15650/// should be returned using '<' and '>' to delimit the range
15651pub fn handle_completion_request(
15652 cx: &mut EditorLspTestContext,
15653 marked_string: &str,
15654 completions: Vec<&'static str>,
15655 counter: Arc<AtomicUsize>,
15656) -> impl Future<Output = ()> {
15657 let complete_from_marker: TextRangeMarker = '|'.into();
15658 let replace_range_marker: TextRangeMarker = ('<', '>').into();
15659 let (_, mut marked_ranges) = marked_text_ranges_by(
15660 marked_string,
15661 vec![complete_from_marker.clone(), replace_range_marker.clone()],
15662 );
15663
15664 let complete_from_position =
15665 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
15666 let replace_range =
15667 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
15668
15669 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
15670 let completions = completions.clone();
15671 counter.fetch_add(1, atomic::Ordering::Release);
15672 async move {
15673 assert_eq!(params.text_document_position.text_document.uri, url.clone());
15674 assert_eq!(
15675 params.text_document_position.position,
15676 complete_from_position
15677 );
15678 Ok(Some(lsp::CompletionResponse::Array(
15679 completions
15680 .iter()
15681 .map(|completion_text| lsp::CompletionItem {
15682 label: completion_text.to_string(),
15683 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15684 range: replace_range,
15685 new_text: completion_text.to_string(),
15686 })),
15687 ..Default::default()
15688 })
15689 .collect(),
15690 )))
15691 }
15692 });
15693
15694 async move {
15695 request.next().await;
15696 }
15697}
15698
15699fn handle_resolve_completion_request(
15700 cx: &mut EditorLspTestContext,
15701 edits: Option<Vec<(&'static str, &'static str)>>,
15702) -> impl Future<Output = ()> {
15703 let edits = edits.map(|edits| {
15704 edits
15705 .iter()
15706 .map(|(marked_string, new_text)| {
15707 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
15708 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
15709 lsp::TextEdit::new(replace_range, new_text.to_string())
15710 })
15711 .collect::<Vec<_>>()
15712 });
15713
15714 let mut request =
15715 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15716 let edits = edits.clone();
15717 async move {
15718 Ok(lsp::CompletionItem {
15719 additional_text_edits: edits,
15720 ..Default::default()
15721 })
15722 }
15723 });
15724
15725 async move {
15726 request.next().await;
15727 }
15728}
15729
15730pub(crate) fn update_test_language_settings(
15731 cx: &mut TestAppContext,
15732 f: impl Fn(&mut AllLanguageSettingsContent),
15733) {
15734 cx.update(|cx| {
15735 SettingsStore::update_global(cx, |store, cx| {
15736 store.update_user_settings::<AllLanguageSettings>(cx, f);
15737 });
15738 });
15739}
15740
15741pub(crate) fn update_test_project_settings(
15742 cx: &mut TestAppContext,
15743 f: impl Fn(&mut ProjectSettings),
15744) {
15745 cx.update(|cx| {
15746 SettingsStore::update_global(cx, |store, cx| {
15747 store.update_user_settings::<ProjectSettings>(cx, f);
15748 });
15749 });
15750}
15751
15752pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
15753 cx.update(|cx| {
15754 assets::Assets.load_test_fonts(cx);
15755 let store = SettingsStore::test(cx);
15756 cx.set_global(store);
15757 theme::init(theme::LoadThemes::JustBase, cx);
15758 release_channel::init(SemanticVersion::default(), cx);
15759 client::init_settings(cx);
15760 language::init(cx);
15761 Project::init_settings(cx);
15762 workspace::init_settings(cx);
15763 crate::init(cx);
15764 });
15765
15766 update_test_language_settings(cx, f);
15767}
15768
15769#[track_caller]
15770fn assert_hunk_revert(
15771 not_reverted_text_with_selections: &str,
15772 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
15773 expected_reverted_text_with_selections: &str,
15774 base_text: &str,
15775 cx: &mut EditorLspTestContext,
15776) {
15777 cx.set_state(not_reverted_text_with_selections);
15778 cx.set_diff_base(base_text);
15779 cx.executor().run_until_parked();
15780
15781 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
15782 let snapshot = editor.snapshot(window, cx);
15783 let reverted_hunk_statuses = snapshot
15784 .buffer_snapshot
15785 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
15786 .map(|hunk| hunk.status())
15787 .collect::<Vec<_>>();
15788
15789 editor.revert_selected_hunks(&RevertSelectedHunks, window, cx);
15790 reverted_hunk_statuses
15791 });
15792 cx.executor().run_until_parked();
15793 cx.assert_editor_state(expected_reverted_text_with_selections);
15794 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
15795}