1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
24 Override, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::{IndentGuide, PathKey};
28use parking_lot::Mutex;
29use pretty_assertions::{assert_eq, assert_ne};
30use project::project_settings::{LspSettings, ProjectSettings};
31use project::FakeFs;
32use serde_json::{self, json};
33use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
34use std::{
35 iter,
36 sync::atomic::{self, AtomicUsize},
37};
38use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
39use text::ToPoint as _;
40use unindent::Unindent;
41use util::{
42 assert_set_eq, path,
43 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
44 uri,
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |window, cx| {
65 let entity = cx.entity().clone();
66 cx.subscribe_in(
67 &entity,
68 window,
69 move |_, _, event: &EditorEvent, _, _| match event {
70 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
71 EditorEvent::BufferEdited => {
72 events.borrow_mut().push(("editor1", "buffer edited"))
73 }
74 _ => {}
75 },
76 )
77 .detach();
78 Editor::for_buffer(buffer.clone(), None, window, cx)
79 }
80 });
81
82 let editor2 = cx.add_window({
83 let events = events.clone();
84 |window, cx| {
85 cx.subscribe_in(
86 &cx.entity().clone(),
87 window,
88 move |_, _, event: &EditorEvent, _, _| match event {
89 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
90 EditorEvent::BufferEdited => {
91 events.borrow_mut().push(("editor2", "buffer edited"))
92 }
93 _ => {}
94 },
95 )
96 .detach();
97 Editor::for_buffer(buffer.clone(), None, window, cx)
98 }
99 });
100
101 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
102
103 // Mutating editor 1 will emit an `Edited` event only for that editor.
104 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor1", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Mutating editor 2 will emit an `Edited` event only for that editor.
115 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor2", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Undoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Redoing on editor 1 will emit an `Edited` event only for that editor.
137 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor1", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Undoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // Redoing on editor 2 will emit an `Edited` event only for that editor.
159 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
160 assert_eq!(
161 mem::take(&mut *events.borrow_mut()),
162 [
163 ("editor2", "edited"),
164 ("editor1", "buffer edited"),
165 ("editor2", "buffer edited"),
166 ]
167 );
168
169 // No event is emitted when the mutation is a no-op.
170 _ = editor2.update(cx, |editor, window, cx| {
171 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
172
173 editor.backspace(&Backspace, window, cx);
174 });
175 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
176}
177
178#[gpui::test]
179fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
180 init_test(cx, |_| {});
181
182 let mut now = Instant::now();
183 let group_interval = Duration::from_millis(1);
184 let buffer = cx.new(|cx| {
185 let mut buf = language::Buffer::local("123456", cx);
186 buf.set_group_interval(group_interval);
187 buf
188 });
189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
190 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
191
192 _ = editor.update(cx, |editor, window, cx| {
193 editor.start_transaction_at(now, window, cx);
194 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
195
196 editor.insert("cd", window, cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cd56");
199 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
200
201 editor.start_transaction_at(now, window, cx);
202 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
203 editor.insert("e", window, cx);
204 editor.end_transaction_at(now, cx);
205 assert_eq!(editor.text(cx), "12cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
207
208 now += group_interval + Duration::from_millis(1);
209 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
210
211 // Simulate an edit in another editor
212 buffer.update(cx, |buffer, cx| {
213 buffer.start_transaction_at(now, cx);
214 buffer.edit([(0..1, "a")], None, cx);
215 buffer.edit([(1..1, "b")], None, cx);
216 buffer.end_transaction_at(now, cx);
217 });
218
219 assert_eq!(editor.text(cx), "ab2cde6");
220 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
221
222 // Last transaction happened past the group interval in a different editor.
223 // Undo it individually and don't restore selections.
224 editor.undo(&Undo, window, cx);
225 assert_eq!(editor.text(cx), "12cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
227
228 // First two transactions happened within the group interval in this editor.
229 // Undo them together and restore selections.
230 editor.undo(&Undo, window, cx);
231 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
232 assert_eq!(editor.text(cx), "123456");
233 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
234
235 // Redo the first two transactions together.
236 editor.redo(&Redo, window, cx);
237 assert_eq!(editor.text(cx), "12cde6");
238 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
239
240 // Redo the last transaction on its own.
241 editor.redo(&Redo, window, cx);
242 assert_eq!(editor.text(cx), "ab2cde6");
243 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
244
245 // Test empty transactions.
246 editor.start_transaction_at(now, window, cx);
247 editor.end_transaction_at(now, cx);
248 editor.undo(&Undo, window, cx);
249 assert_eq!(editor.text(cx), "12cde6");
250 });
251}
252
253#[gpui::test]
254fn test_ime_composition(cx: &mut TestAppContext) {
255 init_test(cx, |_| {});
256
257 let buffer = cx.new(|cx| {
258 let mut buffer = language::Buffer::local("abcde", cx);
259 // Ensure automatic grouping doesn't occur.
260 buffer.set_group_interval(Duration::ZERO);
261 buffer
262 });
263
264 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
265 cx.add_window(|window, cx| {
266 let mut editor = build_editor(buffer.clone(), window, cx);
267
268 // Start a new IME composition.
269 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
270 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
272 assert_eq!(editor.text(cx), "äbcde");
273 assert_eq!(
274 editor.marked_text_ranges(cx),
275 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
276 );
277
278 // Finalize IME composition.
279 editor.replace_text_in_range(None, "ā", window, cx);
280 assert_eq!(editor.text(cx), "ābcde");
281 assert_eq!(editor.marked_text_ranges(cx), None);
282
283 // IME composition edits are grouped and are undone/redone at once.
284 editor.undo(&Default::default(), window, cx);
285 assert_eq!(editor.text(cx), "abcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287 editor.redo(&Default::default(), window, cx);
288 assert_eq!(editor.text(cx), "ābcde");
289 assert_eq!(editor.marked_text_ranges(cx), None);
290
291 // Start a new IME composition.
292 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
293 assert_eq!(
294 editor.marked_text_ranges(cx),
295 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
296 );
297
298 // Undoing during an IME composition cancels it.
299 editor.undo(&Default::default(), window, cx);
300 assert_eq!(editor.text(cx), "ābcde");
301 assert_eq!(editor.marked_text_ranges(cx), None);
302
303 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
304 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
305 assert_eq!(editor.text(cx), "ābcdè");
306 assert_eq!(
307 editor.marked_text_ranges(cx),
308 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
309 );
310
311 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
312 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
313 assert_eq!(editor.text(cx), "ābcdę");
314 assert_eq!(editor.marked_text_ranges(cx), None);
315
316 // Start a new IME composition with multiple cursors.
317 editor.change_selections(None, window, cx, |s| {
318 s.select_ranges([
319 OffsetUtf16(1)..OffsetUtf16(1),
320 OffsetUtf16(3)..OffsetUtf16(3),
321 OffsetUtf16(5)..OffsetUtf16(5),
322 ])
323 });
324 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
325 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
326 assert_eq!(
327 editor.marked_text_ranges(cx),
328 Some(vec![
329 OffsetUtf16(0)..OffsetUtf16(3),
330 OffsetUtf16(4)..OffsetUtf16(7),
331 OffsetUtf16(8)..OffsetUtf16(11)
332 ])
333 );
334
335 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
336 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
337 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
338 assert_eq!(
339 editor.marked_text_ranges(cx),
340 Some(vec![
341 OffsetUtf16(1)..OffsetUtf16(2),
342 OffsetUtf16(5)..OffsetUtf16(6),
343 OffsetUtf16(9)..OffsetUtf16(10)
344 ])
345 );
346
347 // Finalize IME composition with multiple cursors.
348 editor.replace_text_in_range(Some(9..10), "2", window, cx);
349 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
350 assert_eq!(editor.marked_text_ranges(cx), None);
351
352 editor
353 });
354}
355
356#[gpui::test]
357fn test_selection_with_mouse(cx: &mut TestAppContext) {
358 init_test(cx, |_| {});
359
360 let editor = cx.add_window(|window, cx| {
361 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
362 build_editor(buffer, window, cx)
363 });
364
365 _ = editor.update(cx, |editor, window, cx| {
366 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
367 });
368 assert_eq!(
369 editor
370 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
371 .unwrap(),
372 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
373 );
374
375 _ = editor.update(cx, |editor, window, cx| {
376 editor.update_selection(
377 DisplayPoint::new(DisplayRow(3), 3),
378 0,
379 gpui::Point::<f32>::default(),
380 window,
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
390 );
391
392 _ = editor.update(cx, |editor, window, cx| {
393 editor.update_selection(
394 DisplayPoint::new(DisplayRow(1), 1),
395 0,
396 gpui::Point::<f32>::default(),
397 window,
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |editor, window, cx| {
410 editor.end_selection(window, cx);
411 editor.update_selection(
412 DisplayPoint::new(DisplayRow(3), 3),
413 0,
414 gpui::Point::<f32>::default(),
415 window,
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
423 .unwrap(),
424 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
425 );
426
427 _ = editor.update(cx, |editor, window, cx| {
428 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
429 editor.update_selection(
430 DisplayPoint::new(DisplayRow(0), 0),
431 0,
432 gpui::Point::<f32>::default(),
433 window,
434 cx,
435 );
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
441 .unwrap(),
442 [
443 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
444 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
445 ]
446 );
447
448 _ = editor.update(cx, |editor, window, cx| {
449 editor.end_selection(window, cx);
450 });
451
452 assert_eq!(
453 editor
454 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
455 .unwrap(),
456 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
457 );
458}
459
460#[gpui::test]
461fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
462 init_test(cx, |_| {});
463
464 let editor = cx.add_window(|window, cx| {
465 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
466 build_editor(buffer, window, cx)
467 });
468
469 _ = editor.update(cx, |editor, window, cx| {
470 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
471 });
472
473 _ = editor.update(cx, |editor, window, cx| {
474 editor.end_selection(window, cx);
475 });
476
477 _ = editor.update(cx, |editor, window, cx| {
478 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
479 });
480
481 _ = editor.update(cx, |editor, window, cx| {
482 editor.end_selection(window, cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
488 .unwrap(),
489 [
490 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
491 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
492 ]
493 );
494
495 _ = editor.update(cx, |editor, window, cx| {
496 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
497 });
498
499 _ = editor.update(cx, |editor, window, cx| {
500 editor.end_selection(window, cx);
501 });
502
503 assert_eq!(
504 editor
505 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
506 .unwrap(),
507 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
508 );
509}
510
511#[gpui::test]
512fn test_canceling_pending_selection(cx: &mut TestAppContext) {
513 init_test(cx, |_| {});
514
515 let editor = cx.add_window(|window, cx| {
516 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
517 build_editor(buffer, window, cx)
518 });
519
520 _ = editor.update(cx, |editor, window, cx| {
521 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
522 assert_eq!(
523 editor.selections.display_ranges(cx),
524 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
525 );
526 });
527
528 _ = editor.update(cx, |editor, window, cx| {
529 editor.update_selection(
530 DisplayPoint::new(DisplayRow(3), 3),
531 0,
532 gpui::Point::<f32>::default(),
533 window,
534 cx,
535 );
536 assert_eq!(
537 editor.selections.display_ranges(cx),
538 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
539 );
540 });
541
542 _ = editor.update(cx, |editor, window, cx| {
543 editor.cancel(&Cancel, window, cx);
544 editor.update_selection(
545 DisplayPoint::new(DisplayRow(1), 1),
546 0,
547 gpui::Point::<f32>::default(),
548 window,
549 cx,
550 );
551 assert_eq!(
552 editor.selections.display_ranges(cx),
553 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
554 );
555 });
556}
557
558#[gpui::test]
559fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
560 init_test(cx, |_| {});
561
562 let editor = cx.add_window(|window, cx| {
563 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
564 build_editor(buffer, window, cx)
565 });
566
567 _ = editor.update(cx, |editor, window, cx| {
568 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
569 assert_eq!(
570 editor.selections.display_ranges(cx),
571 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
572 );
573
574 editor.move_down(&Default::default(), window, cx);
575 assert_eq!(
576 editor.selections.display_ranges(cx),
577 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
578 );
579
580 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
581 assert_eq!(
582 editor.selections.display_ranges(cx),
583 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
584 );
585
586 editor.move_up(&Default::default(), window, cx);
587 assert_eq!(
588 editor.selections.display_ranges(cx),
589 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
590 );
591 });
592}
593
594#[gpui::test]
595fn test_clone(cx: &mut TestAppContext) {
596 init_test(cx, |_| {});
597
598 let (text, selection_ranges) = marked_text_ranges(
599 indoc! {"
600 one
601 two
602 threeˇ
603 four
604 fiveˇ
605 "},
606 true,
607 );
608
609 let editor = cx.add_window(|window, cx| {
610 let buffer = MultiBuffer::build_simple(&text, cx);
611 build_editor(buffer, window, cx)
612 });
613
614 _ = editor.update(cx, |editor, window, cx| {
615 editor.change_selections(None, window, cx, |s| {
616 s.select_ranges(selection_ranges.clone())
617 });
618 editor.fold_creases(
619 vec![
620 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
621 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
622 ],
623 true,
624 window,
625 cx,
626 );
627 });
628
629 let cloned_editor = editor
630 .update(cx, |editor, _, cx| {
631 cx.open_window(Default::default(), |window, cx| {
632 cx.new(|cx| editor.clone(window, cx))
633 })
634 })
635 .unwrap()
636 .unwrap();
637
638 let snapshot = editor
639 .update(cx, |e, window, cx| e.snapshot(window, cx))
640 .unwrap();
641 let cloned_snapshot = cloned_editor
642 .update(cx, |e, window, cx| e.snapshot(window, cx))
643 .unwrap();
644
645 assert_eq!(
646 cloned_editor
647 .update(cx, |e, _, cx| e.display_text(cx))
648 .unwrap(),
649 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
650 );
651 assert_eq!(
652 cloned_snapshot
653 .folds_in_range(0..text.len())
654 .collect::<Vec<_>>(),
655 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
656 );
657 assert_set_eq!(
658 cloned_editor
659 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
660 .unwrap(),
661 editor
662 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
663 .unwrap()
664 );
665 assert_set_eq!(
666 cloned_editor
667 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
668 .unwrap(),
669 editor
670 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
671 .unwrap()
672 );
673}
674
675#[gpui::test]
676async fn test_navigation_history(cx: &mut TestAppContext) {
677 init_test(cx, |_| {});
678
679 use workspace::item::Item;
680
681 let fs = FakeFs::new(cx.executor());
682 let project = Project::test(fs, [], cx).await;
683 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
684 let pane = workspace
685 .update(cx, |workspace, _, _| workspace.active_pane().clone())
686 .unwrap();
687
688 _ = workspace.update(cx, |_v, window, cx| {
689 cx.new(|cx| {
690 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
691 let mut editor = build_editor(buffer.clone(), window, cx);
692 let handle = cx.entity();
693 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
694
695 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
696 editor.nav_history.as_mut().unwrap().pop_backward(cx)
697 }
698
699 // Move the cursor a small distance.
700 // Nothing is added to the navigation history.
701 editor.change_selections(None, window, cx, |s| {
702 s.select_display_ranges([
703 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
704 ])
705 });
706 editor.change_selections(None, window, cx, |s| {
707 s.select_display_ranges([
708 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
709 ])
710 });
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance.
714 // The history can jump back to the previous position.
715 editor.change_selections(None, window, cx, |s| {
716 s.select_display_ranges([
717 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
718 ])
719 });
720 let nav_entry = pop_history(&mut editor, cx).unwrap();
721 editor.navigate(nav_entry.data.unwrap(), window, cx);
722 assert_eq!(nav_entry.item.id(), cx.entity_id());
723 assert_eq!(
724 editor.selections.display_ranges(cx),
725 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
726 );
727 assert!(pop_history(&mut editor, cx).is_none());
728
729 // Move the cursor a small distance via the mouse.
730 // Nothing is added to the navigation history.
731 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
732 editor.end_selection(window, cx);
733 assert_eq!(
734 editor.selections.display_ranges(cx),
735 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
736 );
737 assert!(pop_history(&mut editor, cx).is_none());
738
739 // Move the cursor a large distance via the mouse.
740 // The history can jump back to the previous position.
741 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
742 editor.end_selection(window, cx);
743 assert_eq!(
744 editor.selections.display_ranges(cx),
745 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
746 );
747 let nav_entry = pop_history(&mut editor, cx).unwrap();
748 editor.navigate(nav_entry.data.unwrap(), window, cx);
749 assert_eq!(nav_entry.item.id(), cx.entity_id());
750 assert_eq!(
751 editor.selections.display_ranges(cx),
752 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
753 );
754 assert!(pop_history(&mut editor, cx).is_none());
755
756 // Set scroll position to check later
757 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
758 let original_scroll_position = editor.scroll_manager.anchor();
759
760 // Jump to the end of the document and adjust scroll
761 editor.move_to_end(&MoveToEnd, window, cx);
762 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
763 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
764
765 let nav_entry = pop_history(&mut editor, cx).unwrap();
766 editor.navigate(nav_entry.data.unwrap(), window, cx);
767 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
768
769 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
770 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
771 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
772 let invalid_point = Point::new(9999, 0);
773 editor.navigate(
774 Box::new(NavigationData {
775 cursor_anchor: invalid_anchor,
776 cursor_position: invalid_point,
777 scroll_anchor: ScrollAnchor {
778 anchor: invalid_anchor,
779 offset: Default::default(),
780 },
781 scroll_top_row: invalid_point.row,
782 }),
783 window,
784 cx,
785 );
786 assert_eq!(
787 editor.selections.display_ranges(cx),
788 &[editor.max_point(cx)..editor.max_point(cx)]
789 );
790 assert_eq!(
791 editor.scroll_position(cx),
792 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
793 );
794
795 editor
796 })
797 });
798}
799
800#[gpui::test]
801fn test_cancel(cx: &mut TestAppContext) {
802 init_test(cx, |_| {});
803
804 let editor = cx.add_window(|window, cx| {
805 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
806 build_editor(buffer, window, cx)
807 });
808
809 _ = editor.update(cx, |editor, window, cx| {
810 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
811 editor.update_selection(
812 DisplayPoint::new(DisplayRow(1), 1),
813 0,
814 gpui::Point::<f32>::default(),
815 window,
816 cx,
817 );
818 editor.end_selection(window, cx);
819
820 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
821 editor.update_selection(
822 DisplayPoint::new(DisplayRow(0), 3),
823 0,
824 gpui::Point::<f32>::default(),
825 window,
826 cx,
827 );
828 editor.end_selection(window, cx);
829 assert_eq!(
830 editor.selections.display_ranges(cx),
831 [
832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
833 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
834 ]
835 );
836 });
837
838 _ = editor.update(cx, |editor, window, cx| {
839 editor.cancel(&Cancel, window, cx);
840 assert_eq!(
841 editor.selections.display_ranges(cx),
842 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
843 );
844 });
845
846 _ = editor.update(cx, |editor, window, cx| {
847 editor.cancel(&Cancel, window, cx);
848 assert_eq!(
849 editor.selections.display_ranges(cx),
850 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
851 );
852 });
853}
854
855#[gpui::test]
856fn test_fold_action(cx: &mut TestAppContext) {
857 init_test(cx, |_| {});
858
859 let editor = cx.add_window(|window, cx| {
860 let buffer = MultiBuffer::build_simple(
861 &"
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {
870 2
871 }
872
873 fn c() {
874 3
875 }
876 }
877 "
878 .unindent(),
879 cx,
880 );
881 build_editor(buffer.clone(), window, cx)
882 });
883
884 _ = editor.update(cx, |editor, window, cx| {
885 editor.change_selections(None, window, cx, |s| {
886 s.select_display_ranges([
887 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
888 ]);
889 });
890 editor.fold(&Fold, window, cx);
891 assert_eq!(
892 editor.display_text(cx),
893 "
894 impl Foo {
895 // Hello!
896
897 fn a() {
898 1
899 }
900
901 fn b() {⋯
902 }
903
904 fn c() {⋯
905 }
906 }
907 "
908 .unindent(),
909 );
910
911 editor.fold(&Fold, window, cx);
912 assert_eq!(
913 editor.display_text(cx),
914 "
915 impl Foo {⋯
916 }
917 "
918 .unindent(),
919 );
920
921 editor.unfold_lines(&UnfoldLines, window, cx);
922 assert_eq!(
923 editor.display_text(cx),
924 "
925 impl Foo {
926 // Hello!
927
928 fn a() {
929 1
930 }
931
932 fn b() {⋯
933 }
934
935 fn c() {⋯
936 }
937 }
938 "
939 .unindent(),
940 );
941
942 editor.unfold_lines(&UnfoldLines, window, cx);
943 assert_eq!(
944 editor.display_text(cx),
945 editor.buffer.read(cx).read(cx).text()
946 );
947 });
948}
949
950#[gpui::test]
951fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
952 init_test(cx, |_| {});
953
954 let editor = cx.add_window(|window, cx| {
955 let buffer = MultiBuffer::build_simple(
956 &"
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():
964 print(2)
965
966 def c():
967 print(3)
968 "
969 .unindent(),
970 cx,
971 );
972 build_editor(buffer.clone(), window, cx)
973 });
974
975 _ = editor.update(cx, |editor, window, cx| {
976 editor.change_selections(None, window, cx, |s| {
977 s.select_display_ranges([
978 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
979 ]);
980 });
981 editor.fold(&Fold, window, cx);
982 assert_eq!(
983 editor.display_text(cx),
984 "
985 class Foo:
986 # Hello!
987
988 def a():
989 print(1)
990
991 def b():⋯
992
993 def c():⋯
994 "
995 .unindent(),
996 );
997
998 editor.fold(&Fold, window, cx);
999 assert_eq!(
1000 editor.display_text(cx),
1001 "
1002 class Foo:⋯
1003 "
1004 .unindent(),
1005 );
1006
1007 editor.unfold_lines(&UnfoldLines, window, cx);
1008 assert_eq!(
1009 editor.display_text(cx),
1010 "
1011 class Foo:
1012 # Hello!
1013
1014 def a():
1015 print(1)
1016
1017 def b():⋯
1018
1019 def c():⋯
1020 "
1021 .unindent(),
1022 );
1023
1024 editor.unfold_lines(&UnfoldLines, window, cx);
1025 assert_eq!(
1026 editor.display_text(cx),
1027 editor.buffer.read(cx).read(cx).text()
1028 );
1029 });
1030}
1031
1032#[gpui::test]
1033fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1034 init_test(cx, |_| {});
1035
1036 let editor = cx.add_window(|window, cx| {
1037 let buffer = MultiBuffer::build_simple(
1038 &"
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():
1046 print(2)
1047
1048
1049 def c():
1050 print(3)
1051
1052
1053 "
1054 .unindent(),
1055 cx,
1056 );
1057 build_editor(buffer.clone(), window, cx)
1058 });
1059
1060 _ = editor.update(cx, |editor, window, cx| {
1061 editor.change_selections(None, window, cx, |s| {
1062 s.select_display_ranges([
1063 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1064 ]);
1065 });
1066 editor.fold(&Fold, window, cx);
1067 assert_eq!(
1068 editor.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 editor.fold(&Fold, window, cx);
1087 assert_eq!(
1088 editor.display_text(cx),
1089 "
1090 class Foo:⋯
1091
1092
1093 "
1094 .unindent(),
1095 );
1096
1097 editor.unfold_lines(&UnfoldLines, window, cx);
1098 assert_eq!(
1099 editor.display_text(cx),
1100 "
1101 class Foo:
1102 # Hello!
1103
1104 def a():
1105 print(1)
1106
1107 def b():⋯
1108
1109
1110 def c():⋯
1111
1112
1113 "
1114 .unindent(),
1115 );
1116
1117 editor.unfold_lines(&UnfoldLines, window, cx);
1118 assert_eq!(
1119 editor.display_text(cx),
1120 editor.buffer.read(cx).read(cx).text()
1121 );
1122 });
1123}
1124
1125#[gpui::test]
1126fn test_fold_at_level(cx: &mut TestAppContext) {
1127 init_test(cx, |_| {});
1128
1129 let editor = cx.add_window(|window, cx| {
1130 let buffer = MultiBuffer::build_simple(
1131 &"
1132 class Foo:
1133 # Hello!
1134
1135 def a():
1136 print(1)
1137
1138 def b():
1139 print(2)
1140
1141
1142 class Bar:
1143 # World!
1144
1145 def a():
1146 print(1)
1147
1148 def b():
1149 print(2)
1150
1151
1152 "
1153 .unindent(),
1154 cx,
1155 );
1156 build_editor(buffer.clone(), window, cx)
1157 });
1158
1159 _ = editor.update(cx, |editor, window, cx| {
1160 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1161 assert_eq!(
1162 editor.display_text(cx),
1163 "
1164 class Foo:
1165 # Hello!
1166
1167 def a():⋯
1168
1169 def b():⋯
1170
1171
1172 class Bar:
1173 # World!
1174
1175 def a():⋯
1176
1177 def b():⋯
1178
1179
1180 "
1181 .unindent(),
1182 );
1183
1184 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1185 assert_eq!(
1186 editor.display_text(cx),
1187 "
1188 class Foo:⋯
1189
1190
1191 class Bar:⋯
1192
1193
1194 "
1195 .unindent(),
1196 );
1197
1198 editor.unfold_all(&UnfoldAll, window, cx);
1199 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1200 assert_eq!(
1201 editor.display_text(cx),
1202 "
1203 class Foo:
1204 # Hello!
1205
1206 def a():
1207 print(1)
1208
1209 def b():
1210 print(2)
1211
1212
1213 class Bar:
1214 # World!
1215
1216 def a():
1217 print(1)
1218
1219 def b():
1220 print(2)
1221
1222
1223 "
1224 .unindent(),
1225 );
1226
1227 assert_eq!(
1228 editor.display_text(cx),
1229 editor.buffer.read(cx).read(cx).text()
1230 );
1231 });
1232}
1233
1234#[gpui::test]
1235fn test_move_cursor(cx: &mut TestAppContext) {
1236 init_test(cx, |_| {});
1237
1238 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1239 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1240
1241 buffer.update(cx, |buffer, cx| {
1242 buffer.edit(
1243 vec![
1244 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1245 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1246 ],
1247 None,
1248 cx,
1249 );
1250 });
1251 _ = editor.update(cx, |editor, window, cx| {
1252 assert_eq!(
1253 editor.selections.display_ranges(cx),
1254 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1255 );
1256
1257 editor.move_down(&MoveDown, window, cx);
1258 assert_eq!(
1259 editor.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1261 );
1262
1263 editor.move_right(&MoveRight, window, cx);
1264 assert_eq!(
1265 editor.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1267 );
1268
1269 editor.move_left(&MoveLeft, window, cx);
1270 assert_eq!(
1271 editor.selections.display_ranges(cx),
1272 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1273 );
1274
1275 editor.move_up(&MoveUp, window, cx);
1276 assert_eq!(
1277 editor.selections.display_ranges(cx),
1278 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1279 );
1280
1281 editor.move_to_end(&MoveToEnd, window, cx);
1282 assert_eq!(
1283 editor.selections.display_ranges(cx),
1284 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1285 );
1286
1287 editor.move_to_beginning(&MoveToBeginning, window, cx);
1288 assert_eq!(
1289 editor.selections.display_ranges(cx),
1290 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1291 );
1292
1293 editor.change_selections(None, window, cx, |s| {
1294 s.select_display_ranges([
1295 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1296 ]);
1297 });
1298 editor.select_to_beginning(&SelectToBeginning, window, cx);
1299 assert_eq!(
1300 editor.selections.display_ranges(cx),
1301 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1302 );
1303
1304 editor.select_to_end(&SelectToEnd, window, cx);
1305 assert_eq!(
1306 editor.selections.display_ranges(cx),
1307 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1308 );
1309 });
1310}
1311
1312// TODO: Re-enable this test
1313#[cfg(target_os = "macos")]
1314#[gpui::test]
1315fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1316 init_test(cx, |_| {});
1317
1318 let editor = cx.add_window(|window, cx| {
1319 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1320 build_editor(buffer.clone(), window, cx)
1321 });
1322
1323 assert_eq!('🟥'.len_utf8(), 4);
1324 assert_eq!('α'.len_utf8(), 2);
1325
1326 _ = editor.update(cx, |editor, window, cx| {
1327 editor.fold_creases(
1328 vec![
1329 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1330 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1332 ],
1333 true,
1334 window,
1335 cx,
1336 );
1337 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1338
1339 editor.move_right(&MoveRight, window, cx);
1340 assert_eq!(
1341 editor.selections.display_ranges(cx),
1342 &[empty_range(0, "🟥".len())]
1343 );
1344 editor.move_right(&MoveRight, window, cx);
1345 assert_eq!(
1346 editor.selections.display_ranges(cx),
1347 &[empty_range(0, "🟥🟧".len())]
1348 );
1349 editor.move_right(&MoveRight, window, cx);
1350 assert_eq!(
1351 editor.selections.display_ranges(cx),
1352 &[empty_range(0, "🟥🟧⋯".len())]
1353 );
1354
1355 editor.move_down(&MoveDown, window, cx);
1356 assert_eq!(
1357 editor.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 editor.move_left(&MoveLeft, window, cx);
1361 assert_eq!(
1362 editor.selections.display_ranges(cx),
1363 &[empty_range(1, "ab⋯".len())]
1364 );
1365 editor.move_left(&MoveLeft, window, cx);
1366 assert_eq!(
1367 editor.selections.display_ranges(cx),
1368 &[empty_range(1, "ab".len())]
1369 );
1370 editor.move_left(&MoveLeft, window, cx);
1371 assert_eq!(
1372 editor.selections.display_ranges(cx),
1373 &[empty_range(1, "a".len())]
1374 );
1375
1376 editor.move_down(&MoveDown, window, cx);
1377 assert_eq!(
1378 editor.selections.display_ranges(cx),
1379 &[empty_range(2, "α".len())]
1380 );
1381 editor.move_right(&MoveRight, window, cx);
1382 assert_eq!(
1383 editor.selections.display_ranges(cx),
1384 &[empty_range(2, "αβ".len())]
1385 );
1386 editor.move_right(&MoveRight, window, cx);
1387 assert_eq!(
1388 editor.selections.display_ranges(cx),
1389 &[empty_range(2, "αβ⋯".len())]
1390 );
1391 editor.move_right(&MoveRight, window, cx);
1392 assert_eq!(
1393 editor.selections.display_ranges(cx),
1394 &[empty_range(2, "αβ⋯ε".len())]
1395 );
1396
1397 editor.move_up(&MoveUp, window, cx);
1398 assert_eq!(
1399 editor.selections.display_ranges(cx),
1400 &[empty_range(1, "ab⋯e".len())]
1401 );
1402 editor.move_down(&MoveDown, window, cx);
1403 assert_eq!(
1404 editor.selections.display_ranges(cx),
1405 &[empty_range(2, "αβ⋯ε".len())]
1406 );
1407 editor.move_up(&MoveUp, window, cx);
1408 assert_eq!(
1409 editor.selections.display_ranges(cx),
1410 &[empty_range(1, "ab⋯e".len())]
1411 );
1412
1413 editor.move_up(&MoveUp, window, cx);
1414 assert_eq!(
1415 editor.selections.display_ranges(cx),
1416 &[empty_range(0, "🟥🟧".len())]
1417 );
1418 editor.move_left(&MoveLeft, window, cx);
1419 assert_eq!(
1420 editor.selections.display_ranges(cx),
1421 &[empty_range(0, "🟥".len())]
1422 );
1423 editor.move_left(&MoveLeft, window, cx);
1424 assert_eq!(
1425 editor.selections.display_ranges(cx),
1426 &[empty_range(0, "".len())]
1427 );
1428 });
1429}
1430
1431#[gpui::test]
1432fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1433 init_test(cx, |_| {});
1434
1435 let editor = cx.add_window(|window, cx| {
1436 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1437 build_editor(buffer.clone(), window, cx)
1438 });
1439 _ = editor.update(cx, |editor, window, cx| {
1440 editor.change_selections(None, window, cx, |s| {
1441 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1442 });
1443
1444 // moving above start of document should move selection to start of document,
1445 // but the next move down should still be at the original goal_x
1446 editor.move_up(&MoveUp, window, cx);
1447 assert_eq!(
1448 editor.selections.display_ranges(cx),
1449 &[empty_range(0, "".len())]
1450 );
1451
1452 editor.move_down(&MoveDown, window, cx);
1453 assert_eq!(
1454 editor.selections.display_ranges(cx),
1455 &[empty_range(1, "abcd".len())]
1456 );
1457
1458 editor.move_down(&MoveDown, window, cx);
1459 assert_eq!(
1460 editor.selections.display_ranges(cx),
1461 &[empty_range(2, "αβγ".len())]
1462 );
1463
1464 editor.move_down(&MoveDown, window, cx);
1465 assert_eq!(
1466 editor.selections.display_ranges(cx),
1467 &[empty_range(3, "abcd".len())]
1468 );
1469
1470 editor.move_down(&MoveDown, window, cx);
1471 assert_eq!(
1472 editor.selections.display_ranges(cx),
1473 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1474 );
1475
1476 // moving past end of document should not change goal_x
1477 editor.move_down(&MoveDown, window, cx);
1478 assert_eq!(
1479 editor.selections.display_ranges(cx),
1480 &[empty_range(5, "".len())]
1481 );
1482
1483 editor.move_down(&MoveDown, window, cx);
1484 assert_eq!(
1485 editor.selections.display_ranges(cx),
1486 &[empty_range(5, "".len())]
1487 );
1488
1489 editor.move_up(&MoveUp, window, cx);
1490 assert_eq!(
1491 editor.selections.display_ranges(cx),
1492 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1493 );
1494
1495 editor.move_up(&MoveUp, window, cx);
1496 assert_eq!(
1497 editor.selections.display_ranges(cx),
1498 &[empty_range(3, "abcd".len())]
1499 );
1500
1501 editor.move_up(&MoveUp, window, cx);
1502 assert_eq!(
1503 editor.selections.display_ranges(cx),
1504 &[empty_range(2, "αβγ".len())]
1505 );
1506 });
1507}
1508
1509#[gpui::test]
1510fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1511 init_test(cx, |_| {});
1512 let move_to_beg = MoveToBeginningOfLine {
1513 stop_at_soft_wraps: true,
1514 stop_at_indent: true,
1515 };
1516
1517 let delete_to_beg = DeleteToBeginningOfLine {
1518 stop_at_indent: false,
1519 };
1520
1521 let move_to_end = MoveToEndOfLine {
1522 stop_at_soft_wraps: true,
1523 };
1524
1525 let editor = cx.add_window(|window, cx| {
1526 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1527 build_editor(buffer, window, cx)
1528 });
1529 _ = editor.update(cx, |editor, window, cx| {
1530 editor.change_selections(None, window, cx, |s| {
1531 s.select_display_ranges([
1532 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1533 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1534 ]);
1535 });
1536 });
1537
1538 _ = editor.update(cx, |editor, window, cx| {
1539 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1540 assert_eq!(
1541 editor.selections.display_ranges(cx),
1542 &[
1543 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1544 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1545 ]
1546 );
1547 });
1548
1549 _ = editor.update(cx, |editor, window, cx| {
1550 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1551 assert_eq!(
1552 editor.selections.display_ranges(cx),
1553 &[
1554 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1555 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1556 ]
1557 );
1558 });
1559
1560 _ = editor.update(cx, |editor, window, cx| {
1561 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1562 assert_eq!(
1563 editor.selections.display_ranges(cx),
1564 &[
1565 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1566 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1567 ]
1568 );
1569 });
1570
1571 _ = editor.update(cx, |editor, window, cx| {
1572 editor.move_to_end_of_line(&move_to_end, window, cx);
1573 assert_eq!(
1574 editor.selections.display_ranges(cx),
1575 &[
1576 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1577 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1578 ]
1579 );
1580 });
1581
1582 // Moving to the end of line again is a no-op.
1583 _ = editor.update(cx, |editor, window, cx| {
1584 editor.move_to_end_of_line(&move_to_end, window, cx);
1585 assert_eq!(
1586 editor.selections.display_ranges(cx),
1587 &[
1588 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1589 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1590 ]
1591 );
1592 });
1593
1594 _ = editor.update(cx, |editor, window, cx| {
1595 editor.move_left(&MoveLeft, window, cx);
1596 editor.select_to_beginning_of_line(
1597 &SelectToBeginningOfLine {
1598 stop_at_soft_wraps: true,
1599 stop_at_indent: true,
1600 },
1601 window,
1602 cx,
1603 );
1604 assert_eq!(
1605 editor.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1609 ]
1610 );
1611 });
1612
1613 _ = editor.update(cx, |editor, window, cx| {
1614 editor.select_to_beginning_of_line(
1615 &SelectToBeginningOfLine {
1616 stop_at_soft_wraps: true,
1617 stop_at_indent: true,
1618 },
1619 window,
1620 cx,
1621 );
1622 assert_eq!(
1623 editor.selections.display_ranges(cx),
1624 &[
1625 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1626 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1627 ]
1628 );
1629 });
1630
1631 _ = editor.update(cx, |editor, window, cx| {
1632 editor.select_to_beginning_of_line(
1633 &SelectToBeginningOfLine {
1634 stop_at_soft_wraps: true,
1635 stop_at_indent: true,
1636 },
1637 window,
1638 cx,
1639 );
1640 assert_eq!(
1641 editor.selections.display_ranges(cx),
1642 &[
1643 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1644 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1645 ]
1646 );
1647 });
1648
1649 _ = editor.update(cx, |editor, window, cx| {
1650 editor.select_to_end_of_line(
1651 &SelectToEndOfLine {
1652 stop_at_soft_wraps: true,
1653 },
1654 window,
1655 cx,
1656 );
1657 assert_eq!(
1658 editor.selections.display_ranges(cx),
1659 &[
1660 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1661 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1662 ]
1663 );
1664 });
1665
1666 _ = editor.update(cx, |editor, window, cx| {
1667 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1668 assert_eq!(editor.display_text(cx), "ab\n de");
1669 assert_eq!(
1670 editor.selections.display_ranges(cx),
1671 &[
1672 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1673 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1674 ]
1675 );
1676 });
1677
1678 _ = editor.update(cx, |editor, window, cx| {
1679 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1680 assert_eq!(editor.display_text(cx), "\n");
1681 assert_eq!(
1682 editor.selections.display_ranges(cx),
1683 &[
1684 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1685 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1686 ]
1687 );
1688 });
1689}
1690
1691#[gpui::test]
1692fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1693 init_test(cx, |_| {});
1694 let move_to_beg = MoveToBeginningOfLine {
1695 stop_at_soft_wraps: false,
1696 stop_at_indent: false,
1697 };
1698
1699 let move_to_end = MoveToEndOfLine {
1700 stop_at_soft_wraps: false,
1701 };
1702
1703 let editor = cx.add_window(|window, cx| {
1704 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1705 build_editor(buffer, window, cx)
1706 });
1707
1708 _ = editor.update(cx, |editor, window, cx| {
1709 editor.set_wrap_width(Some(140.0.into()), cx);
1710
1711 // We expect the following lines after wrapping
1712 // ```
1713 // thequickbrownfox
1714 // jumpedoverthelazydo
1715 // gs
1716 // ```
1717 // The final `gs` was soft-wrapped onto a new line.
1718 assert_eq!(
1719 "thequickbrownfox\njumpedoverthelaz\nydogs",
1720 editor.display_text(cx),
1721 );
1722
1723 // First, let's assert behavior on the first line, that was not soft-wrapped.
1724 // Start the cursor at the `k` on the first line
1725 editor.change_selections(None, window, cx, |s| {
1726 s.select_display_ranges([
1727 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1728 ]);
1729 });
1730
1731 // Moving to the beginning of the line should put us at the beginning of the line.
1732 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1733 assert_eq!(
1734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1735 editor.selections.display_ranges(cx)
1736 );
1737
1738 // Moving to the end of the line should put us at the end of the line.
1739 editor.move_to_end_of_line(&move_to_end, window, cx);
1740 assert_eq!(
1741 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1742 editor.selections.display_ranges(cx)
1743 );
1744
1745 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1746 // Start the cursor at the last line (`y` that was wrapped to a new line)
1747 editor.change_selections(None, window, cx, |s| {
1748 s.select_display_ranges([
1749 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1750 ]);
1751 });
1752
1753 // Moving to the beginning of the line should put us at the start of the second line of
1754 // display text, i.e., the `j`.
1755 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1756 assert_eq!(
1757 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1758 editor.selections.display_ranges(cx)
1759 );
1760
1761 // Moving to the beginning of the line again should be a no-op.
1762 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1763 assert_eq!(
1764 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1765 editor.selections.display_ranges(cx)
1766 );
1767
1768 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1769 // next display line.
1770 editor.move_to_end_of_line(&move_to_end, window, cx);
1771 assert_eq!(
1772 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1773 editor.selections.display_ranges(cx)
1774 );
1775
1776 // Moving to the end of the line again should be a no-op.
1777 editor.move_to_end_of_line(&move_to_end, window, cx);
1778 assert_eq!(
1779 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1780 editor.selections.display_ranges(cx)
1781 );
1782 });
1783}
1784
1785#[gpui::test]
1786fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1787 init_test(cx, |_| {});
1788
1789 let move_to_beg = MoveToBeginningOfLine {
1790 stop_at_soft_wraps: true,
1791 stop_at_indent: true,
1792 };
1793
1794 let select_to_beg = SelectToBeginningOfLine {
1795 stop_at_soft_wraps: true,
1796 stop_at_indent: true,
1797 };
1798
1799 let delete_to_beg = DeleteToBeginningOfLine {
1800 stop_at_indent: true,
1801 };
1802
1803 let move_to_end = MoveToEndOfLine {
1804 stop_at_soft_wraps: false,
1805 };
1806
1807 let editor = cx.add_window(|window, cx| {
1808 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1809 build_editor(buffer, window, cx)
1810 });
1811
1812 _ = editor.update(cx, |editor, window, cx| {
1813 editor.change_selections(None, window, cx, |s| {
1814 s.select_display_ranges([
1815 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1816 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1817 ]);
1818 });
1819
1820 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1821 // and the second cursor at the first non-whitespace character in the line.
1822 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1823 assert_eq!(
1824 editor.selections.display_ranges(cx),
1825 &[
1826 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1827 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1828 ]
1829 );
1830
1831 // Moving to the beginning of the line again should be a no-op for the first cursor,
1832 // and should move the second cursor to the beginning of the line.
1833 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1834 assert_eq!(
1835 editor.selections.display_ranges(cx),
1836 &[
1837 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1838 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1839 ]
1840 );
1841
1842 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1843 // and should move the second cursor back to the first non-whitespace character in the line.
1844 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1845 assert_eq!(
1846 editor.selections.display_ranges(cx),
1847 &[
1848 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1849 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1850 ]
1851 );
1852
1853 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1854 // and to the first non-whitespace character in the line for the second cursor.
1855 editor.move_to_end_of_line(&move_to_end, window, cx);
1856 editor.move_left(&MoveLeft, window, cx);
1857 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1858 assert_eq!(
1859 editor.selections.display_ranges(cx),
1860 &[
1861 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1862 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1863 ]
1864 );
1865
1866 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1867 // and should select to the beginning of the line for the second cursor.
1868 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1869 assert_eq!(
1870 editor.selections.display_ranges(cx),
1871 &[
1872 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1873 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1874 ]
1875 );
1876
1877 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1878 // and should delete to the first non-whitespace character in the line for the second cursor.
1879 editor.move_to_end_of_line(&move_to_end, window, cx);
1880 editor.move_left(&MoveLeft, window, cx);
1881 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1882 assert_eq!(editor.text(cx), "c\n f");
1883 });
1884}
1885
1886#[gpui::test]
1887fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1888 init_test(cx, |_| {});
1889
1890 let editor = cx.add_window(|window, cx| {
1891 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1892 build_editor(buffer, window, cx)
1893 });
1894 _ = editor.update(cx, |editor, window, cx| {
1895 editor.change_selections(None, window, cx, |s| {
1896 s.select_display_ranges([
1897 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1898 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1899 ])
1900 });
1901
1902 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1903 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1904
1905 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1906 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1907
1908 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1909 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1910
1911 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1912 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1913
1914 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1915 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1916
1917 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1918 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1919
1920 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1921 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1922
1923 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1924 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1925
1926 editor.move_right(&MoveRight, window, cx);
1927 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1928 assert_selection_ranges(
1929 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1930 editor,
1931 cx,
1932 );
1933
1934 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1935 assert_selection_ranges(
1936 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1937 editor,
1938 cx,
1939 );
1940
1941 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1942 assert_selection_ranges(
1943 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1944 editor,
1945 cx,
1946 );
1947 });
1948}
1949
1950#[gpui::test]
1951fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1952 init_test(cx, |_| {});
1953
1954 let editor = cx.add_window(|window, cx| {
1955 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1956 build_editor(buffer, window, cx)
1957 });
1958
1959 _ = editor.update(cx, |editor, window, cx| {
1960 editor.set_wrap_width(Some(140.0.into()), cx);
1961 assert_eq!(
1962 editor.display_text(cx),
1963 "use one::{\n two::three::\n four::five\n};"
1964 );
1965
1966 editor.change_selections(None, window, cx, |s| {
1967 s.select_display_ranges([
1968 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1969 ]);
1970 });
1971
1972 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1973 assert_eq!(
1974 editor.selections.display_ranges(cx),
1975 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1976 );
1977
1978 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1979 assert_eq!(
1980 editor.selections.display_ranges(cx),
1981 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1982 );
1983
1984 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1985 assert_eq!(
1986 editor.selections.display_ranges(cx),
1987 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1988 );
1989
1990 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1991 assert_eq!(
1992 editor.selections.display_ranges(cx),
1993 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1994 );
1995
1996 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1997 assert_eq!(
1998 editor.selections.display_ranges(cx),
1999 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2000 );
2001
2002 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2003 assert_eq!(
2004 editor.selections.display_ranges(cx),
2005 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.editor(|editor, window, _| {
2016 editor
2017 .style()
2018 .unwrap()
2019 .text
2020 .line_height_in_pixels(window.rem_size())
2021 });
2022 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2023
2024 cx.set_state(
2025 &r#"ˇone
2026 two
2027
2028 three
2029 fourˇ
2030 five
2031
2032 six"#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, window, cx| {
2037 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2038 });
2039 cx.assert_editor_state(
2040 &r#"one
2041 two
2042 ˇ
2043 three
2044 four
2045 five
2046 ˇ
2047 six"#
2048 .unindent(),
2049 );
2050
2051 cx.update_editor(|editor, window, cx| {
2052 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2053 });
2054 cx.assert_editor_state(
2055 &r#"one
2056 two
2057
2058 three
2059 four
2060 five
2061 ˇ
2062 sixˇ"#
2063 .unindent(),
2064 );
2065
2066 cx.update_editor(|editor, window, cx| {
2067 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2068 });
2069 cx.assert_editor_state(
2070 &r#"one
2071 two
2072
2073 three
2074 four
2075 five
2076
2077 sixˇ"#
2078 .unindent(),
2079 );
2080
2081 cx.update_editor(|editor, window, cx| {
2082 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2083 });
2084 cx.assert_editor_state(
2085 &r#"one
2086 two
2087
2088 three
2089 four
2090 five
2091 ˇ
2092 six"#
2093 .unindent(),
2094 );
2095
2096 cx.update_editor(|editor, window, cx| {
2097 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2098 });
2099 cx.assert_editor_state(
2100 &r#"one
2101 two
2102 ˇ
2103 three
2104 four
2105 five
2106
2107 six"#
2108 .unindent(),
2109 );
2110
2111 cx.update_editor(|editor, window, cx| {
2112 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2113 });
2114 cx.assert_editor_state(
2115 &r#"ˇone
2116 two
2117
2118 three
2119 four
2120 five
2121
2122 six"#
2123 .unindent(),
2124 );
2125}
2126
2127#[gpui::test]
2128async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2129 init_test(cx, |_| {});
2130 let mut cx = EditorTestContext::new(cx).await;
2131 let line_height = cx.editor(|editor, window, _| {
2132 editor
2133 .style()
2134 .unwrap()
2135 .text
2136 .line_height_in_pixels(window.rem_size())
2137 });
2138 let window = cx.window;
2139 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2140
2141 cx.set_state(
2142 r#"ˇone
2143 two
2144 three
2145 four
2146 five
2147 six
2148 seven
2149 eight
2150 nine
2151 ten
2152 "#,
2153 );
2154
2155 cx.update_editor(|editor, window, cx| {
2156 assert_eq!(
2157 editor.snapshot(window, cx).scroll_position(),
2158 gpui::Point::new(0., 0.)
2159 );
2160 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2161 assert_eq!(
2162 editor.snapshot(window, cx).scroll_position(),
2163 gpui::Point::new(0., 3.)
2164 );
2165 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2166 assert_eq!(
2167 editor.snapshot(window, cx).scroll_position(),
2168 gpui::Point::new(0., 6.)
2169 );
2170 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2171 assert_eq!(
2172 editor.snapshot(window, cx).scroll_position(),
2173 gpui::Point::new(0., 3.)
2174 );
2175
2176 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2177 assert_eq!(
2178 editor.snapshot(window, cx).scroll_position(),
2179 gpui::Point::new(0., 1.)
2180 );
2181 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2182 assert_eq!(
2183 editor.snapshot(window, cx).scroll_position(),
2184 gpui::Point::new(0., 3.)
2185 );
2186 });
2187}
2188
2189#[gpui::test]
2190async fn test_autoscroll(cx: &mut TestAppContext) {
2191 init_test(cx, |_| {});
2192 let mut cx = EditorTestContext::new(cx).await;
2193
2194 let line_height = cx.update_editor(|editor, window, cx| {
2195 editor.set_vertical_scroll_margin(2, cx);
2196 editor
2197 .style()
2198 .unwrap()
2199 .text
2200 .line_height_in_pixels(window.rem_size())
2201 });
2202 let window = cx.window;
2203 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2204
2205 cx.set_state(
2206 r#"ˇone
2207 two
2208 three
2209 four
2210 five
2211 six
2212 seven
2213 eight
2214 nine
2215 ten
2216 "#,
2217 );
2218 cx.update_editor(|editor, window, cx| {
2219 assert_eq!(
2220 editor.snapshot(window, cx).scroll_position(),
2221 gpui::Point::new(0., 0.0)
2222 );
2223 });
2224
2225 // Add a cursor below the visible area. Since both cursors cannot fit
2226 // on screen, the editor autoscrolls to reveal the newest cursor, and
2227 // allows the vertical scroll margin below that cursor.
2228 cx.update_editor(|editor, window, cx| {
2229 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2230 selections.select_ranges([
2231 Point::new(0, 0)..Point::new(0, 0),
2232 Point::new(6, 0)..Point::new(6, 0),
2233 ]);
2234 })
2235 });
2236 cx.update_editor(|editor, window, cx| {
2237 assert_eq!(
2238 editor.snapshot(window, cx).scroll_position(),
2239 gpui::Point::new(0., 3.0)
2240 );
2241 });
2242
2243 // Move down. The editor cursor scrolls down to track the newest cursor.
2244 cx.update_editor(|editor, window, cx| {
2245 editor.move_down(&Default::default(), window, cx);
2246 });
2247 cx.update_editor(|editor, window, cx| {
2248 assert_eq!(
2249 editor.snapshot(window, cx).scroll_position(),
2250 gpui::Point::new(0., 4.0)
2251 );
2252 });
2253
2254 // Add a cursor above the visible area. Since both cursors fit on screen,
2255 // the editor scrolls to show both.
2256 cx.update_editor(|editor, window, cx| {
2257 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2258 selections.select_ranges([
2259 Point::new(1, 0)..Point::new(1, 0),
2260 Point::new(6, 0)..Point::new(6, 0),
2261 ]);
2262 })
2263 });
2264 cx.update_editor(|editor, window, cx| {
2265 assert_eq!(
2266 editor.snapshot(window, cx).scroll_position(),
2267 gpui::Point::new(0., 1.0)
2268 );
2269 });
2270}
2271
2272#[gpui::test]
2273async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2274 init_test(cx, |_| {});
2275 let mut cx = EditorTestContext::new(cx).await;
2276
2277 let line_height = cx.editor(|editor, window, _cx| {
2278 editor
2279 .style()
2280 .unwrap()
2281 .text
2282 .line_height_in_pixels(window.rem_size())
2283 });
2284 let window = cx.window;
2285 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2286 cx.set_state(
2287 &r#"
2288 ˇone
2289 two
2290 threeˇ
2291 four
2292 five
2293 six
2294 seven
2295 eight
2296 nine
2297 ten
2298 "#
2299 .unindent(),
2300 );
2301
2302 cx.update_editor(|editor, window, cx| {
2303 editor.move_page_down(&MovePageDown::default(), window, cx)
2304 });
2305 cx.assert_editor_state(
2306 &r#"
2307 one
2308 two
2309 three
2310 ˇfour
2311 five
2312 sixˇ
2313 seven
2314 eight
2315 nine
2316 ten
2317 "#
2318 .unindent(),
2319 );
2320
2321 cx.update_editor(|editor, window, cx| {
2322 editor.move_page_down(&MovePageDown::default(), window, cx)
2323 });
2324 cx.assert_editor_state(
2325 &r#"
2326 one
2327 two
2328 three
2329 four
2330 five
2331 six
2332 ˇseven
2333 eight
2334 nineˇ
2335 ten
2336 "#
2337 .unindent(),
2338 );
2339
2340 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2341 cx.assert_editor_state(
2342 &r#"
2343 one
2344 two
2345 three
2346 ˇfour
2347 five
2348 sixˇ
2349 seven
2350 eight
2351 nine
2352 ten
2353 "#
2354 .unindent(),
2355 );
2356
2357 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2358 cx.assert_editor_state(
2359 &r#"
2360 ˇone
2361 two
2362 threeˇ
2363 four
2364 five
2365 six
2366 seven
2367 eight
2368 nine
2369 ten
2370 "#
2371 .unindent(),
2372 );
2373
2374 // Test select collapsing
2375 cx.update_editor(|editor, window, cx| {
2376 editor.move_page_down(&MovePageDown::default(), window, cx);
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 });
2380 cx.assert_editor_state(
2381 &r#"
2382 one
2383 two
2384 three
2385 four
2386 five
2387 six
2388 seven
2389 eight
2390 nine
2391 ˇten
2392 ˇ"#
2393 .unindent(),
2394 );
2395}
2396
2397#[gpui::test]
2398async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2399 init_test(cx, |_| {});
2400 let mut cx = EditorTestContext::new(cx).await;
2401 cx.set_state("one «two threeˇ» four");
2402 cx.update_editor(|editor, window, cx| {
2403 editor.delete_to_beginning_of_line(
2404 &DeleteToBeginningOfLine {
2405 stop_at_indent: false,
2406 },
2407 window,
2408 cx,
2409 );
2410 assert_eq!(editor.text(cx), " four");
2411 });
2412}
2413
2414#[gpui::test]
2415fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2416 init_test(cx, |_| {});
2417
2418 let editor = cx.add_window(|window, cx| {
2419 let buffer = MultiBuffer::build_simple("one two three four", cx);
2420 build_editor(buffer.clone(), window, cx)
2421 });
2422
2423 _ = editor.update(cx, |editor, window, cx| {
2424 editor.change_selections(None, window, cx, |s| {
2425 s.select_display_ranges([
2426 // an empty selection - the preceding word fragment is deleted
2427 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2428 // characters selected - they are deleted
2429 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2430 ])
2431 });
2432 editor.delete_to_previous_word_start(
2433 &DeleteToPreviousWordStart {
2434 ignore_newlines: false,
2435 },
2436 window,
2437 cx,
2438 );
2439 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2440 });
2441
2442 _ = editor.update(cx, |editor, window, cx| {
2443 editor.change_selections(None, window, cx, |s| {
2444 s.select_display_ranges([
2445 // an empty selection - the following word fragment is deleted
2446 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2447 // characters selected - they are deleted
2448 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2449 ])
2450 });
2451 editor.delete_to_next_word_end(
2452 &DeleteToNextWordEnd {
2453 ignore_newlines: false,
2454 },
2455 window,
2456 cx,
2457 );
2458 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2459 });
2460}
2461
2462#[gpui::test]
2463fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2464 init_test(cx, |_| {});
2465
2466 let editor = cx.add_window(|window, cx| {
2467 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2468 build_editor(buffer.clone(), window, cx)
2469 });
2470 let del_to_prev_word_start = DeleteToPreviousWordStart {
2471 ignore_newlines: false,
2472 };
2473 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2474 ignore_newlines: true,
2475 };
2476
2477 _ = editor.update(cx, |editor, window, cx| {
2478 editor.change_selections(None, window, cx, |s| {
2479 s.select_display_ranges([
2480 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2481 ])
2482 });
2483 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2484 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2485 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2486 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2487 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2488 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2489 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2490 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2491 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2492 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2493 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2494 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2495 });
2496}
2497
2498#[gpui::test]
2499fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2500 init_test(cx, |_| {});
2501
2502 let editor = cx.add_window(|window, cx| {
2503 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2504 build_editor(buffer.clone(), window, cx)
2505 });
2506 let del_to_next_word_end = DeleteToNextWordEnd {
2507 ignore_newlines: false,
2508 };
2509 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2510 ignore_newlines: true,
2511 };
2512
2513 _ = editor.update(cx, |editor, window, cx| {
2514 editor.change_selections(None, window, cx, |s| {
2515 s.select_display_ranges([
2516 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2517 ])
2518 });
2519 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2520 assert_eq!(
2521 editor.buffer.read(cx).read(cx).text(),
2522 "one\n two\nthree\n four"
2523 );
2524 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2525 assert_eq!(
2526 editor.buffer.read(cx).read(cx).text(),
2527 "\n two\nthree\n four"
2528 );
2529 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2530 assert_eq!(
2531 editor.buffer.read(cx).read(cx).text(),
2532 "two\nthree\n four"
2533 );
2534 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2535 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2536 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2537 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2538 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2539 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2540 });
2541}
2542
2543#[gpui::test]
2544fn test_newline(cx: &mut TestAppContext) {
2545 init_test(cx, |_| {});
2546
2547 let editor = cx.add_window(|window, cx| {
2548 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2549 build_editor(buffer.clone(), window, cx)
2550 });
2551
2552 _ = editor.update(cx, |editor, window, cx| {
2553 editor.change_selections(None, window, cx, |s| {
2554 s.select_display_ranges([
2555 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2556 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2557 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2558 ])
2559 });
2560
2561 editor.newline(&Newline, window, cx);
2562 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2563 });
2564}
2565
2566#[gpui::test]
2567fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2568 init_test(cx, |_| {});
2569
2570 let editor = cx.add_window(|window, cx| {
2571 let buffer = MultiBuffer::build_simple(
2572 "
2573 a
2574 b(
2575 X
2576 )
2577 c(
2578 X
2579 )
2580 "
2581 .unindent()
2582 .as_str(),
2583 cx,
2584 );
2585 let mut editor = build_editor(buffer.clone(), window, cx);
2586 editor.change_selections(None, window, cx, |s| {
2587 s.select_ranges([
2588 Point::new(2, 4)..Point::new(2, 5),
2589 Point::new(5, 4)..Point::new(5, 5),
2590 ])
2591 });
2592 editor
2593 });
2594
2595 _ = editor.update(cx, |editor, window, cx| {
2596 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2597 editor.buffer.update(cx, |buffer, cx| {
2598 buffer.edit(
2599 [
2600 (Point::new(1, 2)..Point::new(3, 0), ""),
2601 (Point::new(4, 2)..Point::new(6, 0), ""),
2602 ],
2603 None,
2604 cx,
2605 );
2606 assert_eq!(
2607 buffer.read(cx).text(),
2608 "
2609 a
2610 b()
2611 c()
2612 "
2613 .unindent()
2614 );
2615 });
2616 assert_eq!(
2617 editor.selections.ranges(cx),
2618 &[
2619 Point::new(1, 2)..Point::new(1, 2),
2620 Point::new(2, 2)..Point::new(2, 2),
2621 ],
2622 );
2623
2624 editor.newline(&Newline, window, cx);
2625 assert_eq!(
2626 editor.text(cx),
2627 "
2628 a
2629 b(
2630 )
2631 c(
2632 )
2633 "
2634 .unindent()
2635 );
2636
2637 // The selections are moved after the inserted newlines
2638 assert_eq!(
2639 editor.selections.ranges(cx),
2640 &[
2641 Point::new(2, 0)..Point::new(2, 0),
2642 Point::new(4, 0)..Point::new(4, 0),
2643 ],
2644 );
2645 });
2646}
2647
2648#[gpui::test]
2649async fn test_newline_above(cx: &mut TestAppContext) {
2650 init_test(cx, |settings| {
2651 settings.defaults.tab_size = NonZeroU32::new(4)
2652 });
2653
2654 let language = Arc::new(
2655 Language::new(
2656 LanguageConfig::default(),
2657 Some(tree_sitter_rust::LANGUAGE.into()),
2658 )
2659 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2660 .unwrap(),
2661 );
2662
2663 let mut cx = EditorTestContext::new(cx).await;
2664 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2665 cx.set_state(indoc! {"
2666 const a: ˇA = (
2667 (ˇ
2668 «const_functionˇ»(ˇ),
2669 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2670 )ˇ
2671 ˇ);ˇ
2672 "});
2673
2674 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2675 cx.assert_editor_state(indoc! {"
2676 ˇ
2677 const a: A = (
2678 ˇ
2679 (
2680 ˇ
2681 ˇ
2682 const_function(),
2683 ˇ
2684 ˇ
2685 ˇ
2686 ˇ
2687 something_else,
2688 ˇ
2689 )
2690 ˇ
2691 ˇ
2692 );
2693 "});
2694}
2695
2696#[gpui::test]
2697async fn test_newline_below(cx: &mut TestAppContext) {
2698 init_test(cx, |settings| {
2699 settings.defaults.tab_size = NonZeroU32::new(4)
2700 });
2701
2702 let language = Arc::new(
2703 Language::new(
2704 LanguageConfig::default(),
2705 Some(tree_sitter_rust::LANGUAGE.into()),
2706 )
2707 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2708 .unwrap(),
2709 );
2710
2711 let mut cx = EditorTestContext::new(cx).await;
2712 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2713 cx.set_state(indoc! {"
2714 const a: ˇA = (
2715 (ˇ
2716 «const_functionˇ»(ˇ),
2717 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2718 )ˇ
2719 ˇ);ˇ
2720 "});
2721
2722 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2723 cx.assert_editor_state(indoc! {"
2724 const a: A = (
2725 ˇ
2726 (
2727 ˇ
2728 const_function(),
2729 ˇ
2730 ˇ
2731 something_else,
2732 ˇ
2733 ˇ
2734 ˇ
2735 ˇ
2736 )
2737 ˇ
2738 );
2739 ˇ
2740 ˇ
2741 "});
2742}
2743
2744#[gpui::test]
2745async fn test_newline_comments(cx: &mut TestAppContext) {
2746 init_test(cx, |settings| {
2747 settings.defaults.tab_size = NonZeroU32::new(4)
2748 });
2749
2750 let language = Arc::new(Language::new(
2751 LanguageConfig {
2752 line_comments: vec!["//".into()],
2753 ..LanguageConfig::default()
2754 },
2755 None,
2756 ));
2757 {
2758 let mut cx = EditorTestContext::new(cx).await;
2759 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2760 cx.set_state(indoc! {"
2761 // Fooˇ
2762 "});
2763
2764 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2765 cx.assert_editor_state(indoc! {"
2766 // Foo
2767 //ˇ
2768 "});
2769 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2770 cx.set_state(indoc! {"
2771 ˇ// Foo
2772 "});
2773 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2774 cx.assert_editor_state(indoc! {"
2775
2776 ˇ// Foo
2777 "});
2778 }
2779 // Ensure that comment continuations can be disabled.
2780 update_test_language_settings(cx, |settings| {
2781 settings.defaults.extend_comment_on_newline = Some(false);
2782 });
2783 let mut cx = EditorTestContext::new(cx).await;
2784 cx.set_state(indoc! {"
2785 // Fooˇ
2786 "});
2787 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2788 cx.assert_editor_state(indoc! {"
2789 // Foo
2790 ˇ
2791 "});
2792}
2793
2794#[gpui::test]
2795fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2796 init_test(cx, |_| {});
2797
2798 let editor = cx.add_window(|window, cx| {
2799 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2800 let mut editor = build_editor(buffer.clone(), window, cx);
2801 editor.change_selections(None, window, cx, |s| {
2802 s.select_ranges([3..4, 11..12, 19..20])
2803 });
2804 editor
2805 });
2806
2807 _ = editor.update(cx, |editor, window, cx| {
2808 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2809 editor.buffer.update(cx, |buffer, cx| {
2810 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2811 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2812 });
2813 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2814
2815 editor.insert("Z", window, cx);
2816 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2817
2818 // The selections are moved after the inserted characters
2819 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2820 });
2821}
2822
2823#[gpui::test]
2824async fn test_tab(cx: &mut TestAppContext) {
2825 init_test(cx, |settings| {
2826 settings.defaults.tab_size = NonZeroU32::new(3)
2827 });
2828
2829 let mut cx = EditorTestContext::new(cx).await;
2830 cx.set_state(indoc! {"
2831 ˇabˇc
2832 ˇ🏀ˇ🏀ˇefg
2833 dˇ
2834 "});
2835 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2836 cx.assert_editor_state(indoc! {"
2837 ˇab ˇc
2838 ˇ🏀 ˇ🏀 ˇefg
2839 d ˇ
2840 "});
2841
2842 cx.set_state(indoc! {"
2843 a
2844 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2845 "});
2846 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2847 cx.assert_editor_state(indoc! {"
2848 a
2849 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2850 "});
2851}
2852
2853#[gpui::test]
2854async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2855 init_test(cx, |_| {});
2856
2857 let mut cx = EditorTestContext::new(cx).await;
2858 let language = Arc::new(
2859 Language::new(
2860 LanguageConfig::default(),
2861 Some(tree_sitter_rust::LANGUAGE.into()),
2862 )
2863 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2864 .unwrap(),
2865 );
2866 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2867
2868 // cursors that are already at the suggested indent level insert
2869 // a soft tab. cursors that are to the left of the suggested indent
2870 // auto-indent their line.
2871 cx.set_state(indoc! {"
2872 ˇ
2873 const a: B = (
2874 c(
2875 d(
2876 ˇ
2877 )
2878 ˇ
2879 ˇ )
2880 );
2881 "});
2882 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2883 cx.assert_editor_state(indoc! {"
2884 ˇ
2885 const a: B = (
2886 c(
2887 d(
2888 ˇ
2889 )
2890 ˇ
2891 ˇ)
2892 );
2893 "});
2894
2895 // handle auto-indent when there are multiple cursors on the same line
2896 cx.set_state(indoc! {"
2897 const a: B = (
2898 c(
2899 ˇ ˇ
2900 ˇ )
2901 );
2902 "});
2903 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2904 cx.assert_editor_state(indoc! {"
2905 const a: B = (
2906 c(
2907 ˇ
2908 ˇ)
2909 );
2910 "});
2911}
2912
2913#[gpui::test]
2914async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2915 init_test(cx, |settings| {
2916 settings.defaults.tab_size = NonZeroU32::new(4)
2917 });
2918
2919 let language = Arc::new(
2920 Language::new(
2921 LanguageConfig::default(),
2922 Some(tree_sitter_rust::LANGUAGE.into()),
2923 )
2924 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2925 .unwrap(),
2926 );
2927
2928 let mut cx = EditorTestContext::new(cx).await;
2929 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2930 cx.set_state(indoc! {"
2931 fn a() {
2932 if b {
2933 \t ˇc
2934 }
2935 }
2936 "});
2937
2938 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2939 cx.assert_editor_state(indoc! {"
2940 fn a() {
2941 if b {
2942 ˇc
2943 }
2944 }
2945 "});
2946}
2947
2948#[gpui::test]
2949async fn test_indent_outdent(cx: &mut TestAppContext) {
2950 init_test(cx, |settings| {
2951 settings.defaults.tab_size = NonZeroU32::new(4);
2952 });
2953
2954 let mut cx = EditorTestContext::new(cx).await;
2955
2956 cx.set_state(indoc! {"
2957 «oneˇ» «twoˇ»
2958 three
2959 four
2960 "});
2961 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2962 cx.assert_editor_state(indoc! {"
2963 «oneˇ» «twoˇ»
2964 three
2965 four
2966 "});
2967
2968 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 «oneˇ» «twoˇ»
2971 three
2972 four
2973 "});
2974
2975 // select across line ending
2976 cx.set_state(indoc! {"
2977 one two
2978 t«hree
2979 ˇ» four
2980 "});
2981 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2982 cx.assert_editor_state(indoc! {"
2983 one two
2984 t«hree
2985 ˇ» four
2986 "});
2987
2988 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2989 cx.assert_editor_state(indoc! {"
2990 one two
2991 t«hree
2992 ˇ» four
2993 "});
2994
2995 // Ensure that indenting/outdenting works when the cursor is at column 0.
2996 cx.set_state(indoc! {"
2997 one two
2998 ˇthree
2999 four
3000 "});
3001 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3002 cx.assert_editor_state(indoc! {"
3003 one two
3004 ˇthree
3005 four
3006 "});
3007
3008 cx.set_state(indoc! {"
3009 one two
3010 ˇ three
3011 four
3012 "});
3013 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3014 cx.assert_editor_state(indoc! {"
3015 one two
3016 ˇthree
3017 four
3018 "});
3019}
3020
3021#[gpui::test]
3022async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3023 init_test(cx, |settings| {
3024 settings.defaults.hard_tabs = Some(true);
3025 });
3026
3027 let mut cx = EditorTestContext::new(cx).await;
3028
3029 // select two ranges on one line
3030 cx.set_state(indoc! {"
3031 «oneˇ» «twoˇ»
3032 three
3033 four
3034 "});
3035 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3036 cx.assert_editor_state(indoc! {"
3037 \t«oneˇ» «twoˇ»
3038 three
3039 four
3040 "});
3041 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3042 cx.assert_editor_state(indoc! {"
3043 \t\t«oneˇ» «twoˇ»
3044 three
3045 four
3046 "});
3047 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3048 cx.assert_editor_state(indoc! {"
3049 \t«oneˇ» «twoˇ»
3050 three
3051 four
3052 "});
3053 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3054 cx.assert_editor_state(indoc! {"
3055 «oneˇ» «twoˇ»
3056 three
3057 four
3058 "});
3059
3060 // select across a line ending
3061 cx.set_state(indoc! {"
3062 one two
3063 t«hree
3064 ˇ»four
3065 "});
3066 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3067 cx.assert_editor_state(indoc! {"
3068 one two
3069 \tt«hree
3070 ˇ»four
3071 "});
3072 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3073 cx.assert_editor_state(indoc! {"
3074 one two
3075 \t\tt«hree
3076 ˇ»four
3077 "});
3078 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3079 cx.assert_editor_state(indoc! {"
3080 one two
3081 \tt«hree
3082 ˇ»four
3083 "});
3084 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3085 cx.assert_editor_state(indoc! {"
3086 one two
3087 t«hree
3088 ˇ»four
3089 "});
3090
3091 // Ensure that indenting/outdenting works when the cursor is at column 0.
3092 cx.set_state(indoc! {"
3093 one two
3094 ˇthree
3095 four
3096 "});
3097 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3098 cx.assert_editor_state(indoc! {"
3099 one two
3100 ˇthree
3101 four
3102 "});
3103 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3104 cx.assert_editor_state(indoc! {"
3105 one two
3106 \tˇthree
3107 four
3108 "});
3109 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3110 cx.assert_editor_state(indoc! {"
3111 one two
3112 ˇthree
3113 four
3114 "});
3115}
3116
3117#[gpui::test]
3118fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3119 init_test(cx, |settings| {
3120 settings.languages.extend([
3121 (
3122 "TOML".into(),
3123 LanguageSettingsContent {
3124 tab_size: NonZeroU32::new(2),
3125 ..Default::default()
3126 },
3127 ),
3128 (
3129 "Rust".into(),
3130 LanguageSettingsContent {
3131 tab_size: NonZeroU32::new(4),
3132 ..Default::default()
3133 },
3134 ),
3135 ]);
3136 });
3137
3138 let toml_language = Arc::new(Language::new(
3139 LanguageConfig {
3140 name: "TOML".into(),
3141 ..Default::default()
3142 },
3143 None,
3144 ));
3145 let rust_language = Arc::new(Language::new(
3146 LanguageConfig {
3147 name: "Rust".into(),
3148 ..Default::default()
3149 },
3150 None,
3151 ));
3152
3153 let toml_buffer =
3154 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3155 let rust_buffer =
3156 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3157 let multibuffer = cx.new(|cx| {
3158 let mut multibuffer = MultiBuffer::new(ReadWrite);
3159 multibuffer.push_excerpts(
3160 toml_buffer.clone(),
3161 [ExcerptRange {
3162 context: Point::new(0, 0)..Point::new(2, 0),
3163 primary: None,
3164 }],
3165 cx,
3166 );
3167 multibuffer.push_excerpts(
3168 rust_buffer.clone(),
3169 [ExcerptRange {
3170 context: Point::new(0, 0)..Point::new(1, 0),
3171 primary: None,
3172 }],
3173 cx,
3174 );
3175 multibuffer
3176 });
3177
3178 cx.add_window(|window, cx| {
3179 let mut editor = build_editor(multibuffer, window, cx);
3180
3181 assert_eq!(
3182 editor.text(cx),
3183 indoc! {"
3184 a = 1
3185 b = 2
3186
3187 const c: usize = 3;
3188 "}
3189 );
3190
3191 select_ranges(
3192 &mut editor,
3193 indoc! {"
3194 «aˇ» = 1
3195 b = 2
3196
3197 «const c:ˇ» usize = 3;
3198 "},
3199 window,
3200 cx,
3201 );
3202
3203 editor.tab(&Tab, window, cx);
3204 assert_text_with_selections(
3205 &mut editor,
3206 indoc! {"
3207 «aˇ» = 1
3208 b = 2
3209
3210 «const c:ˇ» usize = 3;
3211 "},
3212 cx,
3213 );
3214 editor.backtab(&Backtab, window, cx);
3215 assert_text_with_selections(
3216 &mut editor,
3217 indoc! {"
3218 «aˇ» = 1
3219 b = 2
3220
3221 «const c:ˇ» usize = 3;
3222 "},
3223 cx,
3224 );
3225
3226 editor
3227 });
3228}
3229
3230#[gpui::test]
3231async fn test_backspace(cx: &mut TestAppContext) {
3232 init_test(cx, |_| {});
3233
3234 let mut cx = EditorTestContext::new(cx).await;
3235
3236 // Basic backspace
3237 cx.set_state(indoc! {"
3238 onˇe two three
3239 fou«rˇ» five six
3240 seven «ˇeight nine
3241 »ten
3242 "});
3243 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3244 cx.assert_editor_state(indoc! {"
3245 oˇe two three
3246 fouˇ five six
3247 seven ˇten
3248 "});
3249
3250 // Test backspace inside and around indents
3251 cx.set_state(indoc! {"
3252 zero
3253 ˇone
3254 ˇtwo
3255 ˇ ˇ ˇ three
3256 ˇ ˇ four
3257 "});
3258 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3259 cx.assert_editor_state(indoc! {"
3260 zero
3261 ˇone
3262 ˇtwo
3263 ˇ threeˇ four
3264 "});
3265
3266 // Test backspace with line_mode set to true
3267 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3268 cx.set_state(indoc! {"
3269 The ˇquick ˇbrown
3270 fox jumps over
3271 the lazy dog
3272 ˇThe qu«ick bˇ»rown"});
3273 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3274 cx.assert_editor_state(indoc! {"
3275 ˇfox jumps over
3276 the lazy dogˇ"});
3277}
3278
3279#[gpui::test]
3280async fn test_delete(cx: &mut TestAppContext) {
3281 init_test(cx, |_| {});
3282
3283 let mut cx = EditorTestContext::new(cx).await;
3284 cx.set_state(indoc! {"
3285 onˇe two three
3286 fou«rˇ» five six
3287 seven «ˇeight nine
3288 »ten
3289 "});
3290 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3291 cx.assert_editor_state(indoc! {"
3292 onˇ two three
3293 fouˇ five six
3294 seven ˇten
3295 "});
3296
3297 // Test backspace with line_mode set to true
3298 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3299 cx.set_state(indoc! {"
3300 The ˇquick ˇbrown
3301 fox «ˇjum»ps over
3302 the lazy dog
3303 ˇThe qu«ick bˇ»rown"});
3304 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3305 cx.assert_editor_state("ˇthe lazy dogˇ");
3306}
3307
3308#[gpui::test]
3309fn test_delete_line(cx: &mut TestAppContext) {
3310 init_test(cx, |_| {});
3311
3312 let editor = cx.add_window(|window, cx| {
3313 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3314 build_editor(buffer, window, cx)
3315 });
3316 _ = editor.update(cx, |editor, window, cx| {
3317 editor.change_selections(None, window, cx, |s| {
3318 s.select_display_ranges([
3319 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3320 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3321 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3322 ])
3323 });
3324 editor.delete_line(&DeleteLine, window, cx);
3325 assert_eq!(editor.display_text(cx), "ghi");
3326 assert_eq!(
3327 editor.selections.display_ranges(cx),
3328 vec![
3329 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3330 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3331 ]
3332 );
3333 });
3334
3335 let editor = cx.add_window(|window, cx| {
3336 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3337 build_editor(buffer, window, cx)
3338 });
3339 _ = editor.update(cx, |editor, window, cx| {
3340 editor.change_selections(None, window, cx, |s| {
3341 s.select_display_ranges([
3342 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3343 ])
3344 });
3345 editor.delete_line(&DeleteLine, window, cx);
3346 assert_eq!(editor.display_text(cx), "ghi\n");
3347 assert_eq!(
3348 editor.selections.display_ranges(cx),
3349 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3350 );
3351 });
3352}
3353
3354#[gpui::test]
3355fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3356 init_test(cx, |_| {});
3357
3358 cx.add_window(|window, cx| {
3359 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3360 let mut editor = build_editor(buffer.clone(), window, cx);
3361 let buffer = buffer.read(cx).as_singleton().unwrap();
3362
3363 assert_eq!(
3364 editor.selections.ranges::<Point>(cx),
3365 &[Point::new(0, 0)..Point::new(0, 0)]
3366 );
3367
3368 // When on single line, replace newline at end by space
3369 editor.join_lines(&JoinLines, window, cx);
3370 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3371 assert_eq!(
3372 editor.selections.ranges::<Point>(cx),
3373 &[Point::new(0, 3)..Point::new(0, 3)]
3374 );
3375
3376 // When multiple lines are selected, remove newlines that are spanned by the selection
3377 editor.change_selections(None, window, cx, |s| {
3378 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3379 });
3380 editor.join_lines(&JoinLines, window, cx);
3381 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3382 assert_eq!(
3383 editor.selections.ranges::<Point>(cx),
3384 &[Point::new(0, 11)..Point::new(0, 11)]
3385 );
3386
3387 // Undo should be transactional
3388 editor.undo(&Undo, window, cx);
3389 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3390 assert_eq!(
3391 editor.selections.ranges::<Point>(cx),
3392 &[Point::new(0, 5)..Point::new(2, 2)]
3393 );
3394
3395 // When joining an empty line don't insert a space
3396 editor.change_selections(None, window, cx, |s| {
3397 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3398 });
3399 editor.join_lines(&JoinLines, window, cx);
3400 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3401 assert_eq!(
3402 editor.selections.ranges::<Point>(cx),
3403 [Point::new(2, 3)..Point::new(2, 3)]
3404 );
3405
3406 // We can remove trailing newlines
3407 editor.join_lines(&JoinLines, window, cx);
3408 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3409 assert_eq!(
3410 editor.selections.ranges::<Point>(cx),
3411 [Point::new(2, 3)..Point::new(2, 3)]
3412 );
3413
3414 // We don't blow up on the last line
3415 editor.join_lines(&JoinLines, window, cx);
3416 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3417 assert_eq!(
3418 editor.selections.ranges::<Point>(cx),
3419 [Point::new(2, 3)..Point::new(2, 3)]
3420 );
3421
3422 // reset to test indentation
3423 editor.buffer.update(cx, |buffer, cx| {
3424 buffer.edit(
3425 [
3426 (Point::new(1, 0)..Point::new(1, 2), " "),
3427 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3428 ],
3429 None,
3430 cx,
3431 )
3432 });
3433
3434 // We remove any leading spaces
3435 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3436 editor.change_selections(None, window, cx, |s| {
3437 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3438 });
3439 editor.join_lines(&JoinLines, window, cx);
3440 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3441
3442 // We don't insert a space for a line containing only spaces
3443 editor.join_lines(&JoinLines, window, cx);
3444 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3445
3446 // We ignore any leading tabs
3447 editor.join_lines(&JoinLines, window, cx);
3448 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3449
3450 editor
3451 });
3452}
3453
3454#[gpui::test]
3455fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3456 init_test(cx, |_| {});
3457
3458 cx.add_window(|window, cx| {
3459 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3460 let mut editor = build_editor(buffer.clone(), window, cx);
3461 let buffer = buffer.read(cx).as_singleton().unwrap();
3462
3463 editor.change_selections(None, window, cx, |s| {
3464 s.select_ranges([
3465 Point::new(0, 2)..Point::new(1, 1),
3466 Point::new(1, 2)..Point::new(1, 2),
3467 Point::new(3, 1)..Point::new(3, 2),
3468 ])
3469 });
3470
3471 editor.join_lines(&JoinLines, window, cx);
3472 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3473
3474 assert_eq!(
3475 editor.selections.ranges::<Point>(cx),
3476 [
3477 Point::new(0, 7)..Point::new(0, 7),
3478 Point::new(1, 3)..Point::new(1, 3)
3479 ]
3480 );
3481 editor
3482 });
3483}
3484
3485#[gpui::test]
3486async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3487 init_test(cx, |_| {});
3488
3489 let mut cx = EditorTestContext::new(cx).await;
3490
3491 let diff_base = r#"
3492 Line 0
3493 Line 1
3494 Line 2
3495 Line 3
3496 "#
3497 .unindent();
3498
3499 cx.set_state(
3500 &r#"
3501 ˇLine 0
3502 Line 1
3503 Line 2
3504 Line 3
3505 "#
3506 .unindent(),
3507 );
3508
3509 cx.set_head_text(&diff_base);
3510 executor.run_until_parked();
3511
3512 // Join lines
3513 cx.update_editor(|editor, window, cx| {
3514 editor.join_lines(&JoinLines, window, cx);
3515 });
3516 executor.run_until_parked();
3517
3518 cx.assert_editor_state(
3519 &r#"
3520 Line 0ˇ Line 1
3521 Line 2
3522 Line 3
3523 "#
3524 .unindent(),
3525 );
3526 // Join again
3527 cx.update_editor(|editor, window, cx| {
3528 editor.join_lines(&JoinLines, window, cx);
3529 });
3530 executor.run_until_parked();
3531
3532 cx.assert_editor_state(
3533 &r#"
3534 Line 0 Line 1ˇ Line 2
3535 Line 3
3536 "#
3537 .unindent(),
3538 );
3539}
3540
3541#[gpui::test]
3542async fn test_custom_newlines_cause_no_false_positive_diffs(
3543 executor: BackgroundExecutor,
3544 cx: &mut TestAppContext,
3545) {
3546 init_test(cx, |_| {});
3547 let mut cx = EditorTestContext::new(cx).await;
3548 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3549 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3550 executor.run_until_parked();
3551
3552 cx.update_editor(|editor, window, cx| {
3553 let snapshot = editor.snapshot(window, cx);
3554 assert_eq!(
3555 snapshot
3556 .buffer_snapshot
3557 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3558 .collect::<Vec<_>>(),
3559 Vec::new(),
3560 "Should not have any diffs for files with custom newlines"
3561 );
3562 });
3563}
3564
3565#[gpui::test]
3566async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 // Test sort_lines_case_insensitive()
3572 cx.set_state(indoc! {"
3573 «z
3574 y
3575 x
3576 Z
3577 Y
3578 Xˇ»
3579 "});
3580 cx.update_editor(|e, window, cx| {
3581 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3582 });
3583 cx.assert_editor_state(indoc! {"
3584 «x
3585 X
3586 y
3587 Y
3588 z
3589 Zˇ»
3590 "});
3591
3592 // Test reverse_lines()
3593 cx.set_state(indoc! {"
3594 «5
3595 4
3596 3
3597 2
3598 1ˇ»
3599 "});
3600 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3601 cx.assert_editor_state(indoc! {"
3602 «1
3603 2
3604 3
3605 4
3606 5ˇ»
3607 "});
3608
3609 // Skip testing shuffle_line()
3610
3611 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3612 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3613
3614 // Don't manipulate when cursor is on single line, but expand the selection
3615 cx.set_state(indoc! {"
3616 ddˇdd
3617 ccc
3618 bb
3619 a
3620 "});
3621 cx.update_editor(|e, window, cx| {
3622 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3623 });
3624 cx.assert_editor_state(indoc! {"
3625 «ddddˇ»
3626 ccc
3627 bb
3628 a
3629 "});
3630
3631 // Basic manipulate case
3632 // Start selection moves to column 0
3633 // End of selection shrinks to fit shorter line
3634 cx.set_state(indoc! {"
3635 dd«d
3636 ccc
3637 bb
3638 aaaaaˇ»
3639 "});
3640 cx.update_editor(|e, window, cx| {
3641 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3642 });
3643 cx.assert_editor_state(indoc! {"
3644 «aaaaa
3645 bb
3646 ccc
3647 dddˇ»
3648 "});
3649
3650 // Manipulate case with newlines
3651 cx.set_state(indoc! {"
3652 dd«d
3653 ccc
3654
3655 bb
3656 aaaaa
3657
3658 ˇ»
3659 "});
3660 cx.update_editor(|e, window, cx| {
3661 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3662 });
3663 cx.assert_editor_state(indoc! {"
3664 «
3665
3666 aaaaa
3667 bb
3668 ccc
3669 dddˇ»
3670
3671 "});
3672
3673 // Adding new line
3674 cx.set_state(indoc! {"
3675 aa«a
3676 bbˇ»b
3677 "});
3678 cx.update_editor(|e, window, cx| {
3679 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3680 });
3681 cx.assert_editor_state(indoc! {"
3682 «aaa
3683 bbb
3684 added_lineˇ»
3685 "});
3686
3687 // Removing line
3688 cx.set_state(indoc! {"
3689 aa«a
3690 bbbˇ»
3691 "});
3692 cx.update_editor(|e, window, cx| {
3693 e.manipulate_lines(window, cx, |lines| {
3694 lines.pop();
3695 })
3696 });
3697 cx.assert_editor_state(indoc! {"
3698 «aaaˇ»
3699 "});
3700
3701 // Removing all lines
3702 cx.set_state(indoc! {"
3703 aa«a
3704 bbbˇ»
3705 "});
3706 cx.update_editor(|e, window, cx| {
3707 e.manipulate_lines(window, cx, |lines| {
3708 lines.drain(..);
3709 })
3710 });
3711 cx.assert_editor_state(indoc! {"
3712 ˇ
3713 "});
3714}
3715
3716#[gpui::test]
3717async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3718 init_test(cx, |_| {});
3719
3720 let mut cx = EditorTestContext::new(cx).await;
3721
3722 // Consider continuous selection as single selection
3723 cx.set_state(indoc! {"
3724 Aaa«aa
3725 cˇ»c«c
3726 bb
3727 aaaˇ»aa
3728 "});
3729 cx.update_editor(|e, window, cx| {
3730 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3731 });
3732 cx.assert_editor_state(indoc! {"
3733 «Aaaaa
3734 ccc
3735 bb
3736 aaaaaˇ»
3737 "});
3738
3739 cx.set_state(indoc! {"
3740 Aaa«aa
3741 cˇ»c«c
3742 bb
3743 aaaˇ»aa
3744 "});
3745 cx.update_editor(|e, window, cx| {
3746 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3747 });
3748 cx.assert_editor_state(indoc! {"
3749 «Aaaaa
3750 ccc
3751 bbˇ»
3752 "});
3753
3754 // Consider non continuous selection as distinct dedup operations
3755 cx.set_state(indoc! {"
3756 «aaaaa
3757 bb
3758 aaaaa
3759 aaaaaˇ»
3760
3761 aaa«aaˇ»
3762 "});
3763 cx.update_editor(|e, window, cx| {
3764 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3765 });
3766 cx.assert_editor_state(indoc! {"
3767 «aaaaa
3768 bbˇ»
3769
3770 «aaaaaˇ»
3771 "});
3772}
3773
3774#[gpui::test]
3775async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3776 init_test(cx, |_| {});
3777
3778 let mut cx = EditorTestContext::new(cx).await;
3779
3780 cx.set_state(indoc! {"
3781 «Aaa
3782 aAa
3783 Aaaˇ»
3784 "});
3785 cx.update_editor(|e, window, cx| {
3786 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3787 });
3788 cx.assert_editor_state(indoc! {"
3789 «Aaa
3790 aAaˇ»
3791 "});
3792
3793 cx.set_state(indoc! {"
3794 «Aaa
3795 aAa
3796 aaAˇ»
3797 "});
3798 cx.update_editor(|e, window, cx| {
3799 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3800 });
3801 cx.assert_editor_state(indoc! {"
3802 «Aaaˇ»
3803 "});
3804}
3805
3806#[gpui::test]
3807async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3808 init_test(cx, |_| {});
3809
3810 let mut cx = EditorTestContext::new(cx).await;
3811
3812 // Manipulate with multiple selections on a single line
3813 cx.set_state(indoc! {"
3814 dd«dd
3815 cˇ»c«c
3816 bb
3817 aaaˇ»aa
3818 "});
3819 cx.update_editor(|e, window, cx| {
3820 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3821 });
3822 cx.assert_editor_state(indoc! {"
3823 «aaaaa
3824 bb
3825 ccc
3826 ddddˇ»
3827 "});
3828
3829 // Manipulate with multiple disjoin selections
3830 cx.set_state(indoc! {"
3831 5«
3832 4
3833 3
3834 2
3835 1ˇ»
3836
3837 dd«dd
3838 ccc
3839 bb
3840 aaaˇ»aa
3841 "});
3842 cx.update_editor(|e, window, cx| {
3843 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3844 });
3845 cx.assert_editor_state(indoc! {"
3846 «1
3847 2
3848 3
3849 4
3850 5ˇ»
3851
3852 «aaaaa
3853 bb
3854 ccc
3855 ddddˇ»
3856 "});
3857
3858 // Adding lines on each selection
3859 cx.set_state(indoc! {"
3860 2«
3861 1ˇ»
3862
3863 bb«bb
3864 aaaˇ»aa
3865 "});
3866 cx.update_editor(|e, window, cx| {
3867 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3868 });
3869 cx.assert_editor_state(indoc! {"
3870 «2
3871 1
3872 added lineˇ»
3873
3874 «bbbb
3875 aaaaa
3876 added lineˇ»
3877 "});
3878
3879 // Removing lines on each selection
3880 cx.set_state(indoc! {"
3881 2«
3882 1ˇ»
3883
3884 bb«bb
3885 aaaˇ»aa
3886 "});
3887 cx.update_editor(|e, window, cx| {
3888 e.manipulate_lines(window, cx, |lines| {
3889 lines.pop();
3890 })
3891 });
3892 cx.assert_editor_state(indoc! {"
3893 «2ˇ»
3894
3895 «bbbbˇ»
3896 "});
3897}
3898
3899#[gpui::test]
3900async fn test_manipulate_text(cx: &mut TestAppContext) {
3901 init_test(cx, |_| {});
3902
3903 let mut cx = EditorTestContext::new(cx).await;
3904
3905 // Test convert_to_upper_case()
3906 cx.set_state(indoc! {"
3907 «hello worldˇ»
3908 "});
3909 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3910 cx.assert_editor_state(indoc! {"
3911 «HELLO WORLDˇ»
3912 "});
3913
3914 // Test convert_to_lower_case()
3915 cx.set_state(indoc! {"
3916 «HELLO WORLDˇ»
3917 "});
3918 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3919 cx.assert_editor_state(indoc! {"
3920 «hello worldˇ»
3921 "});
3922
3923 // Test multiple line, single selection case
3924 cx.set_state(indoc! {"
3925 «The quick brown
3926 fox jumps over
3927 the lazy dogˇ»
3928 "});
3929 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3930 cx.assert_editor_state(indoc! {"
3931 «The Quick Brown
3932 Fox Jumps Over
3933 The Lazy Dogˇ»
3934 "});
3935
3936 // Test multiple line, single selection case
3937 cx.set_state(indoc! {"
3938 «The quick brown
3939 fox jumps over
3940 the lazy dogˇ»
3941 "});
3942 cx.update_editor(|e, window, cx| {
3943 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3944 });
3945 cx.assert_editor_state(indoc! {"
3946 «TheQuickBrown
3947 FoxJumpsOver
3948 TheLazyDogˇ»
3949 "});
3950
3951 // From here on out, test more complex cases of manipulate_text()
3952
3953 // Test no selection case - should affect words cursors are in
3954 // Cursor at beginning, middle, and end of word
3955 cx.set_state(indoc! {"
3956 ˇhello big beauˇtiful worldˇ
3957 "});
3958 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3959 cx.assert_editor_state(indoc! {"
3960 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3961 "});
3962
3963 // Test multiple selections on a single line and across multiple lines
3964 cx.set_state(indoc! {"
3965 «Theˇ» quick «brown
3966 foxˇ» jumps «overˇ»
3967 the «lazyˇ» dog
3968 "});
3969 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3970 cx.assert_editor_state(indoc! {"
3971 «THEˇ» quick «BROWN
3972 FOXˇ» jumps «OVERˇ»
3973 the «LAZYˇ» dog
3974 "});
3975
3976 // Test case where text length grows
3977 cx.set_state(indoc! {"
3978 «tschüߡ»
3979 "});
3980 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3981 cx.assert_editor_state(indoc! {"
3982 «TSCHÜSSˇ»
3983 "});
3984
3985 // Test to make sure we don't crash when text shrinks
3986 cx.set_state(indoc! {"
3987 aaa_bbbˇ
3988 "});
3989 cx.update_editor(|e, window, cx| {
3990 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3991 });
3992 cx.assert_editor_state(indoc! {"
3993 «aaaBbbˇ»
3994 "});
3995
3996 // Test to make sure we all aware of the fact that each word can grow and shrink
3997 // Final selections should be aware of this fact
3998 cx.set_state(indoc! {"
3999 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4000 "});
4001 cx.update_editor(|e, window, cx| {
4002 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4003 });
4004 cx.assert_editor_state(indoc! {"
4005 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4006 "});
4007
4008 cx.set_state(indoc! {"
4009 «hElLo, WoRld!ˇ»
4010 "});
4011 cx.update_editor(|e, window, cx| {
4012 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4013 });
4014 cx.assert_editor_state(indoc! {"
4015 «HeLlO, wOrLD!ˇ»
4016 "});
4017}
4018
4019#[gpui::test]
4020fn test_duplicate_line(cx: &mut TestAppContext) {
4021 init_test(cx, |_| {});
4022
4023 let editor = cx.add_window(|window, cx| {
4024 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4025 build_editor(buffer, window, cx)
4026 });
4027 _ = editor.update(cx, |editor, window, cx| {
4028 editor.change_selections(None, window, cx, |s| {
4029 s.select_display_ranges([
4030 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4031 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4032 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4033 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4034 ])
4035 });
4036 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4037 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4038 assert_eq!(
4039 editor.selections.display_ranges(cx),
4040 vec![
4041 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4042 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4043 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4044 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4045 ]
4046 );
4047 });
4048
4049 let editor = cx.add_window(|window, cx| {
4050 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4051 build_editor(buffer, window, cx)
4052 });
4053 _ = editor.update(cx, |editor, window, cx| {
4054 editor.change_selections(None, window, cx, |s| {
4055 s.select_display_ranges([
4056 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4057 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4058 ])
4059 });
4060 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4061 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4062 assert_eq!(
4063 editor.selections.display_ranges(cx),
4064 vec![
4065 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4066 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4067 ]
4068 );
4069 });
4070
4071 // With `move_upwards` the selections stay in place, except for
4072 // the lines inserted above them
4073 let editor = cx.add_window(|window, cx| {
4074 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4075 build_editor(buffer, window, cx)
4076 });
4077 _ = editor.update(cx, |editor, window, cx| {
4078 editor.change_selections(None, window, cx, |s| {
4079 s.select_display_ranges([
4080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4081 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4082 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4083 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4084 ])
4085 });
4086 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4087 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4088 assert_eq!(
4089 editor.selections.display_ranges(cx),
4090 vec![
4091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4092 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4093 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4094 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4095 ]
4096 );
4097 });
4098
4099 let editor = cx.add_window(|window, cx| {
4100 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4101 build_editor(buffer, window, cx)
4102 });
4103 _ = editor.update(cx, |editor, window, cx| {
4104 editor.change_selections(None, window, cx, |s| {
4105 s.select_display_ranges([
4106 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4107 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4108 ])
4109 });
4110 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4111 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4112 assert_eq!(
4113 editor.selections.display_ranges(cx),
4114 vec![
4115 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4116 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4117 ]
4118 );
4119 });
4120
4121 let editor = cx.add_window(|window, cx| {
4122 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4123 build_editor(buffer, window, cx)
4124 });
4125 _ = editor.update(cx, |editor, window, cx| {
4126 editor.change_selections(None, window, cx, |s| {
4127 s.select_display_ranges([
4128 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4129 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4130 ])
4131 });
4132 editor.duplicate_selection(&DuplicateSelection, window, cx);
4133 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4134 assert_eq!(
4135 editor.selections.display_ranges(cx),
4136 vec![
4137 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4138 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4139 ]
4140 );
4141 });
4142}
4143
4144#[gpui::test]
4145fn test_move_line_up_down(cx: &mut TestAppContext) {
4146 init_test(cx, |_| {});
4147
4148 let editor = cx.add_window(|window, cx| {
4149 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4150 build_editor(buffer, window, cx)
4151 });
4152 _ = editor.update(cx, |editor, window, cx| {
4153 editor.fold_creases(
4154 vec![
4155 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4156 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4158 ],
4159 true,
4160 window,
4161 cx,
4162 );
4163 editor.change_selections(None, window, cx, |s| {
4164 s.select_display_ranges([
4165 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4166 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4167 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4168 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4169 ])
4170 });
4171 assert_eq!(
4172 editor.display_text(cx),
4173 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4174 );
4175
4176 editor.move_line_up(&MoveLineUp, window, cx);
4177 assert_eq!(
4178 editor.display_text(cx),
4179 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4180 );
4181 assert_eq!(
4182 editor.selections.display_ranges(cx),
4183 vec![
4184 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4185 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4186 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4187 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4188 ]
4189 );
4190 });
4191
4192 _ = editor.update(cx, |editor, window, cx| {
4193 editor.move_line_down(&MoveLineDown, window, cx);
4194 assert_eq!(
4195 editor.display_text(cx),
4196 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4197 );
4198 assert_eq!(
4199 editor.selections.display_ranges(cx),
4200 vec![
4201 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4202 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4203 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4204 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4205 ]
4206 );
4207 });
4208
4209 _ = editor.update(cx, |editor, window, cx| {
4210 editor.move_line_down(&MoveLineDown, window, cx);
4211 assert_eq!(
4212 editor.display_text(cx),
4213 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4214 );
4215 assert_eq!(
4216 editor.selections.display_ranges(cx),
4217 vec![
4218 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4219 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4220 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4221 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4222 ]
4223 );
4224 });
4225
4226 _ = editor.update(cx, |editor, window, cx| {
4227 editor.move_line_up(&MoveLineUp, window, cx);
4228 assert_eq!(
4229 editor.display_text(cx),
4230 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4231 );
4232 assert_eq!(
4233 editor.selections.display_ranges(cx),
4234 vec![
4235 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4236 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4237 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4238 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4239 ]
4240 );
4241 });
4242}
4243
4244#[gpui::test]
4245fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4246 init_test(cx, |_| {});
4247
4248 let editor = cx.add_window(|window, cx| {
4249 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4250 build_editor(buffer, window, cx)
4251 });
4252 _ = editor.update(cx, |editor, window, cx| {
4253 let snapshot = editor.buffer.read(cx).snapshot(cx);
4254 editor.insert_blocks(
4255 [BlockProperties {
4256 style: BlockStyle::Fixed,
4257 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4258 height: 1,
4259 render: Arc::new(|_| div().into_any()),
4260 priority: 0,
4261 }],
4262 Some(Autoscroll::fit()),
4263 cx,
4264 );
4265 editor.change_selections(None, window, cx, |s| {
4266 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4267 });
4268 editor.move_line_down(&MoveLineDown, window, cx);
4269 });
4270}
4271
4272#[gpui::test]
4273async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4274 init_test(cx, |_| {});
4275
4276 let mut cx = EditorTestContext::new(cx).await;
4277 cx.set_state(
4278 &"
4279 ˇzero
4280 one
4281 two
4282 three
4283 four
4284 five
4285 "
4286 .unindent(),
4287 );
4288
4289 // Create a four-line block that replaces three lines of text.
4290 cx.update_editor(|editor, window, cx| {
4291 let snapshot = editor.snapshot(window, cx);
4292 let snapshot = &snapshot.buffer_snapshot;
4293 let placement = BlockPlacement::Replace(
4294 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4295 );
4296 editor.insert_blocks(
4297 [BlockProperties {
4298 placement,
4299 height: 4,
4300 style: BlockStyle::Sticky,
4301 render: Arc::new(|_| gpui::div().into_any_element()),
4302 priority: 0,
4303 }],
4304 None,
4305 cx,
4306 );
4307 });
4308
4309 // Move down so that the cursor touches the block.
4310 cx.update_editor(|editor, window, cx| {
4311 editor.move_down(&Default::default(), window, cx);
4312 });
4313 cx.assert_editor_state(
4314 &"
4315 zero
4316 «one
4317 two
4318 threeˇ»
4319 four
4320 five
4321 "
4322 .unindent(),
4323 );
4324
4325 // Move down past the block.
4326 cx.update_editor(|editor, window, cx| {
4327 editor.move_down(&Default::default(), window, cx);
4328 });
4329 cx.assert_editor_state(
4330 &"
4331 zero
4332 one
4333 two
4334 three
4335 ˇfour
4336 five
4337 "
4338 .unindent(),
4339 );
4340}
4341
4342#[gpui::test]
4343fn test_transpose(cx: &mut TestAppContext) {
4344 init_test(cx, |_| {});
4345
4346 _ = cx.add_window(|window, cx| {
4347 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4348 editor.set_style(EditorStyle::default(), window, cx);
4349 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4350 editor.transpose(&Default::default(), window, cx);
4351 assert_eq!(editor.text(cx), "bac");
4352 assert_eq!(editor.selections.ranges(cx), [2..2]);
4353
4354 editor.transpose(&Default::default(), window, cx);
4355 assert_eq!(editor.text(cx), "bca");
4356 assert_eq!(editor.selections.ranges(cx), [3..3]);
4357
4358 editor.transpose(&Default::default(), window, cx);
4359 assert_eq!(editor.text(cx), "bac");
4360 assert_eq!(editor.selections.ranges(cx), [3..3]);
4361
4362 editor
4363 });
4364
4365 _ = cx.add_window(|window, cx| {
4366 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4367 editor.set_style(EditorStyle::default(), window, cx);
4368 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4369 editor.transpose(&Default::default(), window, cx);
4370 assert_eq!(editor.text(cx), "acb\nde");
4371 assert_eq!(editor.selections.ranges(cx), [3..3]);
4372
4373 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4374 editor.transpose(&Default::default(), window, cx);
4375 assert_eq!(editor.text(cx), "acbd\ne");
4376 assert_eq!(editor.selections.ranges(cx), [5..5]);
4377
4378 editor.transpose(&Default::default(), window, cx);
4379 assert_eq!(editor.text(cx), "acbde\n");
4380 assert_eq!(editor.selections.ranges(cx), [6..6]);
4381
4382 editor.transpose(&Default::default(), window, cx);
4383 assert_eq!(editor.text(cx), "acbd\ne");
4384 assert_eq!(editor.selections.ranges(cx), [6..6]);
4385
4386 editor
4387 });
4388
4389 _ = cx.add_window(|window, cx| {
4390 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4391 editor.set_style(EditorStyle::default(), window, cx);
4392 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4393 editor.transpose(&Default::default(), window, cx);
4394 assert_eq!(editor.text(cx), "bacd\ne");
4395 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4396
4397 editor.transpose(&Default::default(), window, cx);
4398 assert_eq!(editor.text(cx), "bcade\n");
4399 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4400
4401 editor.transpose(&Default::default(), window, cx);
4402 assert_eq!(editor.text(cx), "bcda\ne");
4403 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4404
4405 editor.transpose(&Default::default(), window, cx);
4406 assert_eq!(editor.text(cx), "bcade\n");
4407 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4408
4409 editor.transpose(&Default::default(), window, cx);
4410 assert_eq!(editor.text(cx), "bcaed\n");
4411 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4412
4413 editor
4414 });
4415
4416 _ = cx.add_window(|window, cx| {
4417 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4418 editor.set_style(EditorStyle::default(), window, cx);
4419 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4420 editor.transpose(&Default::default(), window, cx);
4421 assert_eq!(editor.text(cx), "🏀🍐✋");
4422 assert_eq!(editor.selections.ranges(cx), [8..8]);
4423
4424 editor.transpose(&Default::default(), window, cx);
4425 assert_eq!(editor.text(cx), "🏀✋🍐");
4426 assert_eq!(editor.selections.ranges(cx), [11..11]);
4427
4428 editor.transpose(&Default::default(), window, cx);
4429 assert_eq!(editor.text(cx), "🏀🍐✋");
4430 assert_eq!(editor.selections.ranges(cx), [11..11]);
4431
4432 editor
4433 });
4434}
4435
4436#[gpui::test]
4437async fn test_rewrap(cx: &mut TestAppContext) {
4438 init_test(cx, |settings| {
4439 settings.languages.extend([
4440 (
4441 "Markdown".into(),
4442 LanguageSettingsContent {
4443 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4444 ..Default::default()
4445 },
4446 ),
4447 (
4448 "Plain Text".into(),
4449 LanguageSettingsContent {
4450 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4451 ..Default::default()
4452 },
4453 ),
4454 ])
4455 });
4456
4457 let mut cx = EditorTestContext::new(cx).await;
4458
4459 let language_with_c_comments = Arc::new(Language::new(
4460 LanguageConfig {
4461 line_comments: vec!["// ".into()],
4462 ..LanguageConfig::default()
4463 },
4464 None,
4465 ));
4466 let language_with_pound_comments = Arc::new(Language::new(
4467 LanguageConfig {
4468 line_comments: vec!["# ".into()],
4469 ..LanguageConfig::default()
4470 },
4471 None,
4472 ));
4473 let markdown_language = Arc::new(Language::new(
4474 LanguageConfig {
4475 name: "Markdown".into(),
4476 ..LanguageConfig::default()
4477 },
4478 None,
4479 ));
4480 let language_with_doc_comments = Arc::new(Language::new(
4481 LanguageConfig {
4482 line_comments: vec!["// ".into(), "/// ".into()],
4483 ..LanguageConfig::default()
4484 },
4485 Some(tree_sitter_rust::LANGUAGE.into()),
4486 ));
4487
4488 let plaintext_language = Arc::new(Language::new(
4489 LanguageConfig {
4490 name: "Plain Text".into(),
4491 ..LanguageConfig::default()
4492 },
4493 None,
4494 ));
4495
4496 assert_rewrap(
4497 indoc! {"
4498 // ˇ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.
4499 "},
4500 indoc! {"
4501 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4502 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4503 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4504 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4505 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4506 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4507 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4508 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4509 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4510 // porttitor id. Aliquam id accumsan eros.
4511 "},
4512 language_with_c_comments.clone(),
4513 &mut cx,
4514 );
4515
4516 // Test that rewrapping works inside of a selection
4517 assert_rewrap(
4518 indoc! {"
4519 «// 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.ˇ»
4520 "},
4521 indoc! {"
4522 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4523 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4524 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4525 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4526 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4527 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4528 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4529 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4530 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4531 // porttitor id. Aliquam id accumsan eros.ˇ»
4532 "},
4533 language_with_c_comments.clone(),
4534 &mut cx,
4535 );
4536
4537 // Test that cursors that expand to the same region are collapsed.
4538 assert_rewrap(
4539 indoc! {"
4540 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4541 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4542 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4543 // ˇ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.
4544 "},
4545 indoc! {"
4546 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4547 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4548 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4549 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4550 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4551 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4552 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4553 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4554 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4555 // porttitor id. Aliquam id accumsan eros.
4556 "},
4557 language_with_c_comments.clone(),
4558 &mut cx,
4559 );
4560
4561 // Test that non-contiguous selections are treated separately.
4562 assert_rewrap(
4563 indoc! {"
4564 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4565 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4566 //
4567 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4568 // ˇ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.
4569 "},
4570 indoc! {"
4571 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4572 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4573 // auctor, eu lacinia sapien scelerisque.
4574 //
4575 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4576 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4577 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4578 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4579 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4580 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4581 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4582 "},
4583 language_with_c_comments.clone(),
4584 &mut cx,
4585 );
4586
4587 // Test that different comment prefixes are supported.
4588 assert_rewrap(
4589 indoc! {"
4590 # ˇ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.
4591 "},
4592 indoc! {"
4593 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4594 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4595 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4596 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4597 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4598 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4599 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4600 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4601 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4602 # accumsan eros.
4603 "},
4604 language_with_pound_comments.clone(),
4605 &mut cx,
4606 );
4607
4608 // Test that rewrapping is ignored outside of comments in most languages.
4609 assert_rewrap(
4610 indoc! {"
4611 /// Adds two numbers.
4612 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4613 fn add(a: u32, b: u32) -> u32 {
4614 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ˇ
4615 }
4616 "},
4617 indoc! {"
4618 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4619 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4620 fn add(a: u32, b: u32) -> u32 {
4621 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ˇ
4622 }
4623 "},
4624 language_with_doc_comments.clone(),
4625 &mut cx,
4626 );
4627
4628 // Test that rewrapping works in Markdown and Plain Text languages.
4629 assert_rewrap(
4630 indoc! {"
4631 # Hello
4632
4633 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.
4634 "},
4635 indoc! {"
4636 # Hello
4637
4638 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4639 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4640 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4641 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4642 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4643 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4644 Integer sit amet scelerisque nisi.
4645 "},
4646 markdown_language,
4647 &mut cx,
4648 );
4649
4650 assert_rewrap(
4651 indoc! {"
4652 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.
4653 "},
4654 indoc! {"
4655 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4656 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4657 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4658 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4659 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4660 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4661 Integer sit amet scelerisque nisi.
4662 "},
4663 plaintext_language,
4664 &mut cx,
4665 );
4666
4667 // Test rewrapping unaligned comments in a selection.
4668 assert_rewrap(
4669 indoc! {"
4670 fn foo() {
4671 if true {
4672 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4673 // Praesent semper egestas tellus id dignissim.ˇ»
4674 do_something();
4675 } else {
4676 //
4677 }
4678 }
4679 "},
4680 indoc! {"
4681 fn foo() {
4682 if true {
4683 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4684 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4685 // egestas tellus id dignissim.ˇ»
4686 do_something();
4687 } else {
4688 //
4689 }
4690 }
4691 "},
4692 language_with_doc_comments.clone(),
4693 &mut cx,
4694 );
4695
4696 assert_rewrap(
4697 indoc! {"
4698 fn foo() {
4699 if true {
4700 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4701 // Praesent semper egestas tellus id dignissim.»
4702 do_something();
4703 } else {
4704 //
4705 }
4706
4707 }
4708 "},
4709 indoc! {"
4710 fn foo() {
4711 if true {
4712 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4713 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4714 // egestas tellus id dignissim.»
4715 do_something();
4716 } else {
4717 //
4718 }
4719
4720 }
4721 "},
4722 language_with_doc_comments.clone(),
4723 &mut cx,
4724 );
4725
4726 #[track_caller]
4727 fn assert_rewrap(
4728 unwrapped_text: &str,
4729 wrapped_text: &str,
4730 language: Arc<Language>,
4731 cx: &mut EditorTestContext,
4732 ) {
4733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4734 cx.set_state(unwrapped_text);
4735 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4736 cx.assert_editor_state(wrapped_text);
4737 }
4738}
4739
4740#[gpui::test]
4741async fn test_hard_wrap(cx: &mut TestAppContext) {
4742 init_test(cx, |_| {});
4743 let mut cx = EditorTestContext::new(cx).await;
4744
4745 cx.update_editor(|editor, _, cx| {
4746 editor.set_hard_wrap(Some(14), cx);
4747 });
4748
4749 cx.set_state(indoc!(
4750 "
4751 one two three ˇ
4752 "
4753 ));
4754 cx.simulate_input("four");
4755 cx.run_until_parked();
4756
4757 cx.assert_editor_state(indoc!(
4758 "
4759 one two three
4760 fourˇ
4761 "
4762 ));
4763}
4764
4765#[gpui::test]
4766async fn test_clipboard(cx: &mut TestAppContext) {
4767 init_test(cx, |_| {});
4768
4769 let mut cx = EditorTestContext::new(cx).await;
4770
4771 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4772 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4773 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4774
4775 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4776 cx.set_state("two ˇfour ˇsix ˇ");
4777 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4778 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4779
4780 // Paste again but with only two cursors. Since the number of cursors doesn't
4781 // match the number of slices in the clipboard, the entire clipboard text
4782 // is pasted at each cursor.
4783 cx.set_state("ˇtwo one✅ four three six five ˇ");
4784 cx.update_editor(|e, window, cx| {
4785 e.handle_input("( ", window, cx);
4786 e.paste(&Paste, window, cx);
4787 e.handle_input(") ", window, cx);
4788 });
4789 cx.assert_editor_state(
4790 &([
4791 "( one✅ ",
4792 "three ",
4793 "five ) ˇtwo one✅ four three six five ( one✅ ",
4794 "three ",
4795 "five ) ˇ",
4796 ]
4797 .join("\n")),
4798 );
4799
4800 // Cut with three selections, one of which is full-line.
4801 cx.set_state(indoc! {"
4802 1«2ˇ»3
4803 4ˇ567
4804 «8ˇ»9"});
4805 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4806 cx.assert_editor_state(indoc! {"
4807 1ˇ3
4808 ˇ9"});
4809
4810 // Paste with three selections, noticing how the copied selection that was full-line
4811 // gets inserted before the second cursor.
4812 cx.set_state(indoc! {"
4813 1ˇ3
4814 9ˇ
4815 «oˇ»ne"});
4816 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4817 cx.assert_editor_state(indoc! {"
4818 12ˇ3
4819 4567
4820 9ˇ
4821 8ˇne"});
4822
4823 // Copy with a single cursor only, which writes the whole line into the clipboard.
4824 cx.set_state(indoc! {"
4825 The quick brown
4826 fox juˇmps over
4827 the lazy dog"});
4828 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4829 assert_eq!(
4830 cx.read_from_clipboard()
4831 .and_then(|item| item.text().as_deref().map(str::to_string)),
4832 Some("fox jumps over\n".to_string())
4833 );
4834
4835 // Paste with three selections, noticing how the copied full-line selection is inserted
4836 // before the empty selections but replaces the selection that is non-empty.
4837 cx.set_state(indoc! {"
4838 Tˇhe quick brown
4839 «foˇ»x jumps over
4840 tˇhe lazy dog"});
4841 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4842 cx.assert_editor_state(indoc! {"
4843 fox jumps over
4844 Tˇhe quick brown
4845 fox jumps over
4846 ˇx jumps over
4847 fox jumps over
4848 tˇhe lazy dog"});
4849}
4850
4851#[gpui::test]
4852async fn test_paste_multiline(cx: &mut TestAppContext) {
4853 init_test(cx, |_| {});
4854
4855 let mut cx = EditorTestContext::new(cx).await;
4856 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4857
4858 // Cut an indented block, without the leading whitespace.
4859 cx.set_state(indoc! {"
4860 const a: B = (
4861 c(),
4862 «d(
4863 e,
4864 f
4865 )ˇ»
4866 );
4867 "});
4868 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4869 cx.assert_editor_state(indoc! {"
4870 const a: B = (
4871 c(),
4872 ˇ
4873 );
4874 "});
4875
4876 // Paste it at the same position.
4877 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4878 cx.assert_editor_state(indoc! {"
4879 const a: B = (
4880 c(),
4881 d(
4882 e,
4883 f
4884 )ˇ
4885 );
4886 "});
4887
4888 // Paste it at a line with a lower indent level.
4889 cx.set_state(indoc! {"
4890 ˇ
4891 const a: B = (
4892 c(),
4893 );
4894 "});
4895 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4896 cx.assert_editor_state(indoc! {"
4897 d(
4898 e,
4899 f
4900 )ˇ
4901 const a: B = (
4902 c(),
4903 );
4904 "});
4905
4906 // Cut an indented block, with the leading whitespace.
4907 cx.set_state(indoc! {"
4908 const a: B = (
4909 c(),
4910 « d(
4911 e,
4912 f
4913 )
4914 ˇ»);
4915 "});
4916 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4917 cx.assert_editor_state(indoc! {"
4918 const a: B = (
4919 c(),
4920 ˇ);
4921 "});
4922
4923 // Paste it at the same position.
4924 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4925 cx.assert_editor_state(indoc! {"
4926 const a: B = (
4927 c(),
4928 d(
4929 e,
4930 f
4931 )
4932 ˇ);
4933 "});
4934
4935 // Paste it at a line with a higher indent level.
4936 cx.set_state(indoc! {"
4937 const a: B = (
4938 c(),
4939 d(
4940 e,
4941 fˇ
4942 )
4943 );
4944 "});
4945 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4946 cx.assert_editor_state(indoc! {"
4947 const a: B = (
4948 c(),
4949 d(
4950 e,
4951 f d(
4952 e,
4953 f
4954 )
4955 ˇ
4956 )
4957 );
4958 "});
4959
4960 // Copy an indented block, starting mid-line
4961 cx.set_state(indoc! {"
4962 const a: B = (
4963 c(),
4964 somethin«g(
4965 e,
4966 f
4967 )ˇ»
4968 );
4969 "});
4970 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4971
4972 // Paste it on a line with a lower indent level
4973 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4974 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4975 cx.assert_editor_state(indoc! {"
4976 const a: B = (
4977 c(),
4978 something(
4979 e,
4980 f
4981 )
4982 );
4983 g(
4984 e,
4985 f
4986 )ˇ"});
4987}
4988
4989#[gpui::test]
4990async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4991 init_test(cx, |_| {});
4992
4993 cx.write_to_clipboard(ClipboardItem::new_string(
4994 " d(\n e\n );\n".into(),
4995 ));
4996
4997 let mut cx = EditorTestContext::new(cx).await;
4998 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4999
5000 cx.set_state(indoc! {"
5001 fn a() {
5002 b();
5003 if c() {
5004 ˇ
5005 }
5006 }
5007 "});
5008
5009 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5010 cx.assert_editor_state(indoc! {"
5011 fn a() {
5012 b();
5013 if c() {
5014 d(
5015 e
5016 );
5017 ˇ
5018 }
5019 }
5020 "});
5021
5022 cx.set_state(indoc! {"
5023 fn a() {
5024 b();
5025 ˇ
5026 }
5027 "});
5028
5029 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5030 cx.assert_editor_state(indoc! {"
5031 fn a() {
5032 b();
5033 d(
5034 e
5035 );
5036 ˇ
5037 }
5038 "});
5039}
5040
5041#[gpui::test]
5042fn test_select_all(cx: &mut TestAppContext) {
5043 init_test(cx, |_| {});
5044
5045 let editor = cx.add_window(|window, cx| {
5046 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5047 build_editor(buffer, window, cx)
5048 });
5049 _ = editor.update(cx, |editor, window, cx| {
5050 editor.select_all(&SelectAll, window, cx);
5051 assert_eq!(
5052 editor.selections.display_ranges(cx),
5053 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5054 );
5055 });
5056}
5057
5058#[gpui::test]
5059fn test_select_line(cx: &mut TestAppContext) {
5060 init_test(cx, |_| {});
5061
5062 let editor = cx.add_window(|window, cx| {
5063 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5064 build_editor(buffer, window, cx)
5065 });
5066 _ = editor.update(cx, |editor, window, cx| {
5067 editor.change_selections(None, window, cx, |s| {
5068 s.select_display_ranges([
5069 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5070 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5071 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5072 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5073 ])
5074 });
5075 editor.select_line(&SelectLine, window, cx);
5076 assert_eq!(
5077 editor.selections.display_ranges(cx),
5078 vec![
5079 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5080 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5081 ]
5082 );
5083 });
5084
5085 _ = editor.update(cx, |editor, window, cx| {
5086 editor.select_line(&SelectLine, window, cx);
5087 assert_eq!(
5088 editor.selections.display_ranges(cx),
5089 vec![
5090 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5091 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5092 ]
5093 );
5094 });
5095
5096 _ = editor.update(cx, |editor, window, cx| {
5097 editor.select_line(&SelectLine, window, cx);
5098 assert_eq!(
5099 editor.selections.display_ranges(cx),
5100 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5101 );
5102 });
5103}
5104
5105#[gpui::test]
5106async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5107 init_test(cx, |_| {});
5108 let mut cx = EditorTestContext::new(cx).await;
5109
5110 #[track_caller]
5111 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5112 cx.set_state(initial_state);
5113 cx.update_editor(|e, window, cx| {
5114 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5115 });
5116 cx.assert_editor_state(expected_state);
5117 }
5118
5119 // Selection starts and ends at the middle of lines, left-to-right
5120 test(
5121 &mut cx,
5122 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5123 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5124 );
5125 // Same thing, right-to-left
5126 test(
5127 &mut cx,
5128 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5129 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5130 );
5131
5132 // Whole buffer, left-to-right, last line *doesn't* end with newline
5133 test(
5134 &mut cx,
5135 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5136 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5137 );
5138 // Same thing, right-to-left
5139 test(
5140 &mut cx,
5141 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5142 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5143 );
5144
5145 // Whole buffer, left-to-right, last line ends with newline
5146 test(
5147 &mut cx,
5148 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5149 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5150 );
5151 // Same thing, right-to-left
5152 test(
5153 &mut cx,
5154 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5155 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5156 );
5157
5158 // Starts at the end of a line, ends at the start of another
5159 test(
5160 &mut cx,
5161 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5162 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5163 );
5164}
5165
5166#[gpui::test]
5167async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5168 init_test(cx, |_| {});
5169
5170 let editor = cx.add_window(|window, cx| {
5171 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5172 build_editor(buffer, window, cx)
5173 });
5174
5175 // setup
5176 _ = editor.update(cx, |editor, window, cx| {
5177 editor.fold_creases(
5178 vec![
5179 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5180 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5181 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5182 ],
5183 true,
5184 window,
5185 cx,
5186 );
5187 assert_eq!(
5188 editor.display_text(cx),
5189 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5190 );
5191 });
5192
5193 _ = editor.update(cx, |editor, window, cx| {
5194 editor.change_selections(None, window, cx, |s| {
5195 s.select_display_ranges([
5196 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5197 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5198 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5199 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5200 ])
5201 });
5202 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5203 assert_eq!(
5204 editor.display_text(cx),
5205 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5206 );
5207 });
5208 EditorTestContext::for_editor(editor, cx)
5209 .await
5210 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5211
5212 _ = editor.update(cx, |editor, window, cx| {
5213 editor.change_selections(None, window, cx, |s| {
5214 s.select_display_ranges([
5215 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5216 ])
5217 });
5218 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5219 assert_eq!(
5220 editor.display_text(cx),
5221 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5222 );
5223 assert_eq!(
5224 editor.selections.display_ranges(cx),
5225 [
5226 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5227 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5228 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5229 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5230 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5231 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5232 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5233 ]
5234 );
5235 });
5236 EditorTestContext::for_editor(editor, cx)
5237 .await
5238 .assert_editor_state(
5239 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248
5249 cx.set_state(indoc!(
5250 r#"abc
5251 defˇghi
5252
5253 jk
5254 nlmo
5255 "#
5256 ));
5257
5258 cx.update_editor(|editor, window, cx| {
5259 editor.add_selection_above(&Default::default(), window, cx);
5260 });
5261
5262 cx.assert_editor_state(indoc!(
5263 r#"abcˇ
5264 defˇghi
5265
5266 jk
5267 nlmo
5268 "#
5269 ));
5270
5271 cx.update_editor(|editor, window, cx| {
5272 editor.add_selection_above(&Default::default(), window, cx);
5273 });
5274
5275 cx.assert_editor_state(indoc!(
5276 r#"abcˇ
5277 defˇghi
5278
5279 jk
5280 nlmo
5281 "#
5282 ));
5283
5284 cx.update_editor(|editor, window, cx| {
5285 editor.add_selection_below(&Default::default(), window, cx);
5286 });
5287
5288 cx.assert_editor_state(indoc!(
5289 r#"abc
5290 defˇghi
5291
5292 jk
5293 nlmo
5294 "#
5295 ));
5296
5297 cx.update_editor(|editor, window, cx| {
5298 editor.undo_selection(&Default::default(), window, cx);
5299 });
5300
5301 cx.assert_editor_state(indoc!(
5302 r#"abcˇ
5303 defˇghi
5304
5305 jk
5306 nlmo
5307 "#
5308 ));
5309
5310 cx.update_editor(|editor, window, cx| {
5311 editor.redo_selection(&Default::default(), window, cx);
5312 });
5313
5314 cx.assert_editor_state(indoc!(
5315 r#"abc
5316 defˇghi
5317
5318 jk
5319 nlmo
5320 "#
5321 ));
5322
5323 cx.update_editor(|editor, window, cx| {
5324 editor.add_selection_below(&Default::default(), window, cx);
5325 });
5326
5327 cx.assert_editor_state(indoc!(
5328 r#"abc
5329 defˇghi
5330
5331 jk
5332 nlmˇo
5333 "#
5334 ));
5335
5336 cx.update_editor(|editor, window, cx| {
5337 editor.add_selection_below(&Default::default(), window, cx);
5338 });
5339
5340 cx.assert_editor_state(indoc!(
5341 r#"abc
5342 defˇghi
5343
5344 jk
5345 nlmˇo
5346 "#
5347 ));
5348
5349 // change selections
5350 cx.set_state(indoc!(
5351 r#"abc
5352 def«ˇg»hi
5353
5354 jk
5355 nlmo
5356 "#
5357 ));
5358
5359 cx.update_editor(|editor, window, cx| {
5360 editor.add_selection_below(&Default::default(), window, cx);
5361 });
5362
5363 cx.assert_editor_state(indoc!(
5364 r#"abc
5365 def«ˇg»hi
5366
5367 jk
5368 nlm«ˇo»
5369 "#
5370 ));
5371
5372 cx.update_editor(|editor, window, cx| {
5373 editor.add_selection_below(&Default::default(), window, cx);
5374 });
5375
5376 cx.assert_editor_state(indoc!(
5377 r#"abc
5378 def«ˇg»hi
5379
5380 jk
5381 nlm«ˇo»
5382 "#
5383 ));
5384
5385 cx.update_editor(|editor, window, cx| {
5386 editor.add_selection_above(&Default::default(), window, cx);
5387 });
5388
5389 cx.assert_editor_state(indoc!(
5390 r#"abc
5391 def«ˇg»hi
5392
5393 jk
5394 nlmo
5395 "#
5396 ));
5397
5398 cx.update_editor(|editor, window, cx| {
5399 editor.add_selection_above(&Default::default(), window, cx);
5400 });
5401
5402 cx.assert_editor_state(indoc!(
5403 r#"abc
5404 def«ˇg»hi
5405
5406 jk
5407 nlmo
5408 "#
5409 ));
5410
5411 // Change selections again
5412 cx.set_state(indoc!(
5413 r#"a«bc
5414 defgˇ»hi
5415
5416 jk
5417 nlmo
5418 "#
5419 ));
5420
5421 cx.update_editor(|editor, window, cx| {
5422 editor.add_selection_below(&Default::default(), window, cx);
5423 });
5424
5425 cx.assert_editor_state(indoc!(
5426 r#"a«bcˇ»
5427 d«efgˇ»hi
5428
5429 j«kˇ»
5430 nlmo
5431 "#
5432 ));
5433
5434 cx.update_editor(|editor, window, cx| {
5435 editor.add_selection_below(&Default::default(), window, cx);
5436 });
5437 cx.assert_editor_state(indoc!(
5438 r#"a«bcˇ»
5439 d«efgˇ»hi
5440
5441 j«kˇ»
5442 n«lmoˇ»
5443 "#
5444 ));
5445 cx.update_editor(|editor, window, cx| {
5446 editor.add_selection_above(&Default::default(), window, cx);
5447 });
5448
5449 cx.assert_editor_state(indoc!(
5450 r#"a«bcˇ»
5451 d«efgˇ»hi
5452
5453 j«kˇ»
5454 nlmo
5455 "#
5456 ));
5457
5458 // Change selections again
5459 cx.set_state(indoc!(
5460 r#"abc
5461 d«ˇefghi
5462
5463 jk
5464 nlm»o
5465 "#
5466 ));
5467
5468 cx.update_editor(|editor, window, cx| {
5469 editor.add_selection_above(&Default::default(), window, cx);
5470 });
5471
5472 cx.assert_editor_state(indoc!(
5473 r#"a«ˇbc»
5474 d«ˇef»ghi
5475
5476 j«ˇk»
5477 n«ˇlm»o
5478 "#
5479 ));
5480
5481 cx.update_editor(|editor, window, cx| {
5482 editor.add_selection_below(&Default::default(), window, cx);
5483 });
5484
5485 cx.assert_editor_state(indoc!(
5486 r#"abc
5487 d«ˇef»ghi
5488
5489 j«ˇk»
5490 n«ˇlm»o
5491 "#
5492 ));
5493}
5494
5495#[gpui::test]
5496async fn test_select_next(cx: &mut TestAppContext) {
5497 init_test(cx, |_| {});
5498
5499 let mut cx = EditorTestContext::new(cx).await;
5500 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5501
5502 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5503 .unwrap();
5504 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5505
5506 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5507 .unwrap();
5508 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5509
5510 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5511 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5512
5513 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5514 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5515
5516 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5517 .unwrap();
5518 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5519
5520 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5521 .unwrap();
5522 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5523}
5524
5525#[gpui::test]
5526async fn test_select_all_matches(cx: &mut TestAppContext) {
5527 init_test(cx, |_| {});
5528
5529 let mut cx = EditorTestContext::new(cx).await;
5530
5531 // Test caret-only selections
5532 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5533 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5534 .unwrap();
5535 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5536
5537 // Test left-to-right selections
5538 cx.set_state("abc\n«abcˇ»\nabc");
5539 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5540 .unwrap();
5541 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5542
5543 // Test right-to-left selections
5544 cx.set_state("abc\n«ˇabc»\nabc");
5545 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5546 .unwrap();
5547 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5548
5549 // Test selecting whitespace with caret selection
5550 cx.set_state("abc\nˇ abc\nabc");
5551 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5552 .unwrap();
5553 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5554
5555 // Test selecting whitespace with left-to-right selection
5556 cx.set_state("abc\n«ˇ »abc\nabc");
5557 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5558 .unwrap();
5559 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5560
5561 // Test no matches with right-to-left selection
5562 cx.set_state("abc\n« ˇ»abc\nabc");
5563 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5564 .unwrap();
5565 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5566}
5567
5568#[gpui::test]
5569async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5570 init_test(cx, |_| {});
5571
5572 let mut cx = EditorTestContext::new(cx).await;
5573 cx.set_state(
5574 r#"let foo = 2;
5575lˇet foo = 2;
5576let fooˇ = 2;
5577let foo = 2;
5578let foo = ˇ2;"#,
5579 );
5580
5581 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5582 .unwrap();
5583 cx.assert_editor_state(
5584 r#"let foo = 2;
5585«letˇ» foo = 2;
5586let «fooˇ» = 2;
5587let foo = 2;
5588let foo = «2ˇ»;"#,
5589 );
5590
5591 // noop for multiple selections with different contents
5592 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5593 .unwrap();
5594 cx.assert_editor_state(
5595 r#"let foo = 2;
5596«letˇ» foo = 2;
5597let «fooˇ» = 2;
5598let foo = 2;
5599let foo = «2ˇ»;"#,
5600 );
5601}
5602
5603#[gpui::test]
5604async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5605 init_test(cx, |_| {});
5606
5607 let mut cx =
5608 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5609
5610 cx.assert_editor_state(indoc! {"
5611 ˇbbb
5612 ccc
5613
5614 bbb
5615 ccc
5616 "});
5617 cx.dispatch_action(SelectPrevious::default());
5618 cx.assert_editor_state(indoc! {"
5619 «bbbˇ»
5620 ccc
5621
5622 bbb
5623 ccc
5624 "});
5625 cx.dispatch_action(SelectPrevious::default());
5626 cx.assert_editor_state(indoc! {"
5627 «bbbˇ»
5628 ccc
5629
5630 «bbbˇ»
5631 ccc
5632 "});
5633}
5634
5635#[gpui::test]
5636async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5637 init_test(cx, |_| {});
5638
5639 let mut cx = EditorTestContext::new(cx).await;
5640 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5641
5642 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5643 .unwrap();
5644 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5645
5646 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5647 .unwrap();
5648 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5649
5650 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5651 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5652
5653 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5654 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5655
5656 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5657 .unwrap();
5658 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5659
5660 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5661 .unwrap();
5662 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5663
5664 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5665 .unwrap();
5666 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5667}
5668
5669#[gpui::test]
5670async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5671 init_test(cx, |_| {});
5672
5673 let mut cx = EditorTestContext::new(cx).await;
5674 cx.set_state("aˇ");
5675
5676 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5677 .unwrap();
5678 cx.assert_editor_state("«aˇ»");
5679 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5680 .unwrap();
5681 cx.assert_editor_state("«aˇ»");
5682}
5683
5684#[gpui::test]
5685async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5686 init_test(cx, |_| {});
5687
5688 let mut cx = EditorTestContext::new(cx).await;
5689 cx.set_state(
5690 r#"let foo = 2;
5691lˇet foo = 2;
5692let fooˇ = 2;
5693let foo = 2;
5694let foo = ˇ2;"#,
5695 );
5696
5697 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5698 .unwrap();
5699 cx.assert_editor_state(
5700 r#"let foo = 2;
5701«letˇ» foo = 2;
5702let «fooˇ» = 2;
5703let foo = 2;
5704let foo = «2ˇ»;"#,
5705 );
5706
5707 // noop for multiple selections with different contents
5708 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5709 .unwrap();
5710 cx.assert_editor_state(
5711 r#"let foo = 2;
5712«letˇ» foo = 2;
5713let «fooˇ» = 2;
5714let foo = 2;
5715let foo = «2ˇ»;"#,
5716 );
5717}
5718
5719#[gpui::test]
5720async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5721 init_test(cx, |_| {});
5722
5723 let mut cx = EditorTestContext::new(cx).await;
5724 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5725
5726 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5727 .unwrap();
5728 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5729
5730 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5731 .unwrap();
5732 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5733
5734 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5735 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5736
5737 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5738 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5739
5740 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5741 .unwrap();
5742 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5743
5744 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5745 .unwrap();
5746 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5747}
5748
5749#[gpui::test]
5750async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5751 init_test(cx, |_| {});
5752
5753 let language = Arc::new(Language::new(
5754 LanguageConfig::default(),
5755 Some(tree_sitter_rust::LANGUAGE.into()),
5756 ));
5757
5758 let text = r#"
5759 use mod1::mod2::{mod3, mod4};
5760
5761 fn fn_1(param1: bool, param2: &str) {
5762 let var1 = "text";
5763 }
5764 "#
5765 .unindent();
5766
5767 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5768 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5769 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5770
5771 editor
5772 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5773 .await;
5774
5775 editor.update_in(cx, |editor, window, cx| {
5776 editor.change_selections(None, window, cx, |s| {
5777 s.select_display_ranges([
5778 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5779 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5780 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5781 ]);
5782 });
5783 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5784 });
5785 editor.update(cx, |editor, cx| {
5786 assert_text_with_selections(
5787 editor,
5788 indoc! {r#"
5789 use mod1::mod2::{mod3, «mod4ˇ»};
5790
5791 fn fn_1«ˇ(param1: bool, param2: &str)» {
5792 let var1 = "«textˇ»";
5793 }
5794 "#},
5795 cx,
5796 );
5797 });
5798
5799 editor.update_in(cx, |editor, window, cx| {
5800 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5801 });
5802 editor.update(cx, |editor, cx| {
5803 assert_text_with_selections(
5804 editor,
5805 indoc! {r#"
5806 use mod1::mod2::«{mod3, mod4}ˇ»;
5807
5808 «ˇfn fn_1(param1: bool, param2: &str) {
5809 let var1 = "text";
5810 }»
5811 "#},
5812 cx,
5813 );
5814 });
5815
5816 editor.update_in(cx, |editor, window, cx| {
5817 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5818 });
5819 assert_eq!(
5820 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5821 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5822 );
5823
5824 // Trying to expand the selected syntax node one more time has no effect.
5825 editor.update_in(cx, |editor, window, cx| {
5826 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5827 });
5828 assert_eq!(
5829 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5830 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5831 );
5832
5833 editor.update_in(cx, |editor, window, cx| {
5834 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5835 });
5836 editor.update(cx, |editor, cx| {
5837 assert_text_with_selections(
5838 editor,
5839 indoc! {r#"
5840 use mod1::mod2::«{mod3, mod4}ˇ»;
5841
5842 «ˇfn fn_1(param1: bool, param2: &str) {
5843 let var1 = "text";
5844 }»
5845 "#},
5846 cx,
5847 );
5848 });
5849
5850 editor.update_in(cx, |editor, window, cx| {
5851 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5852 });
5853 editor.update(cx, |editor, cx| {
5854 assert_text_with_selections(
5855 editor,
5856 indoc! {r#"
5857 use mod1::mod2::{mod3, «mod4ˇ»};
5858
5859 fn fn_1«ˇ(param1: bool, param2: &str)» {
5860 let var1 = "«textˇ»";
5861 }
5862 "#},
5863 cx,
5864 );
5865 });
5866
5867 editor.update_in(cx, |editor, window, cx| {
5868 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5869 });
5870 editor.update(cx, |editor, cx| {
5871 assert_text_with_selections(
5872 editor,
5873 indoc! {r#"
5874 use mod1::mod2::{mod3, mo«ˇ»d4};
5875
5876 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5877 let var1 = "te«ˇ»xt";
5878 }
5879 "#},
5880 cx,
5881 );
5882 });
5883
5884 // Trying to shrink the selected syntax node one more time has no effect.
5885 editor.update_in(cx, |editor, window, cx| {
5886 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5887 });
5888 editor.update_in(cx, |editor, _, cx| {
5889 assert_text_with_selections(
5890 editor,
5891 indoc! {r#"
5892 use mod1::mod2::{mod3, mo«ˇ»d4};
5893
5894 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5895 let var1 = "te«ˇ»xt";
5896 }
5897 "#},
5898 cx,
5899 );
5900 });
5901
5902 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5903 // a fold.
5904 editor.update_in(cx, |editor, window, cx| {
5905 editor.fold_creases(
5906 vec![
5907 Crease::simple(
5908 Point::new(0, 21)..Point::new(0, 24),
5909 FoldPlaceholder::test(),
5910 ),
5911 Crease::simple(
5912 Point::new(3, 20)..Point::new(3, 22),
5913 FoldPlaceholder::test(),
5914 ),
5915 ],
5916 true,
5917 window,
5918 cx,
5919 );
5920 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5921 });
5922 editor.update(cx, |editor, cx| {
5923 assert_text_with_selections(
5924 editor,
5925 indoc! {r#"
5926 use mod1::mod2::«{mod3, mod4}ˇ»;
5927
5928 fn fn_1«ˇ(param1: bool, param2: &str)» {
5929 «let var1 = "text";ˇ»
5930 }
5931 "#},
5932 cx,
5933 );
5934 });
5935}
5936
5937#[gpui::test]
5938async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5939 init_test(cx, |_| {});
5940
5941 let base_text = r#"
5942 impl A {
5943 // this is an uncommitted comment
5944
5945 fn b() {
5946 c();
5947 }
5948
5949 // this is another uncommitted comment
5950
5951 fn d() {
5952 // e
5953 // f
5954 }
5955 }
5956
5957 fn g() {
5958 // h
5959 }
5960 "#
5961 .unindent();
5962
5963 let text = r#"
5964 ˇimpl A {
5965
5966 fn b() {
5967 c();
5968 }
5969
5970 fn d() {
5971 // e
5972 // f
5973 }
5974 }
5975
5976 fn g() {
5977 // h
5978 }
5979 "#
5980 .unindent();
5981
5982 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5983 cx.set_state(&text);
5984 cx.set_head_text(&base_text);
5985 cx.update_editor(|editor, window, cx| {
5986 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5987 });
5988
5989 cx.assert_state_with_diff(
5990 "
5991 ˇimpl A {
5992 - // this is an uncommitted comment
5993
5994 fn b() {
5995 c();
5996 }
5997
5998 - // this is another uncommitted comment
5999 -
6000 fn d() {
6001 // e
6002 // f
6003 }
6004 }
6005
6006 fn g() {
6007 // h
6008 }
6009 "
6010 .unindent(),
6011 );
6012
6013 let expected_display_text = "
6014 impl A {
6015 // this is an uncommitted comment
6016
6017 fn b() {
6018 ⋯
6019 }
6020
6021 // this is another uncommitted comment
6022
6023 fn d() {
6024 ⋯
6025 }
6026 }
6027
6028 fn g() {
6029 ⋯
6030 }
6031 "
6032 .unindent();
6033
6034 cx.update_editor(|editor, window, cx| {
6035 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6036 assert_eq!(editor.display_text(cx), expected_display_text);
6037 });
6038}
6039
6040#[gpui::test]
6041async fn test_autoindent(cx: &mut TestAppContext) {
6042 init_test(cx, |_| {});
6043
6044 let language = Arc::new(
6045 Language::new(
6046 LanguageConfig {
6047 brackets: BracketPairConfig {
6048 pairs: vec![
6049 BracketPair {
6050 start: "{".to_string(),
6051 end: "}".to_string(),
6052 close: false,
6053 surround: false,
6054 newline: true,
6055 },
6056 BracketPair {
6057 start: "(".to_string(),
6058 end: ")".to_string(),
6059 close: false,
6060 surround: false,
6061 newline: true,
6062 },
6063 ],
6064 ..Default::default()
6065 },
6066 ..Default::default()
6067 },
6068 Some(tree_sitter_rust::LANGUAGE.into()),
6069 )
6070 .with_indents_query(
6071 r#"
6072 (_ "(" ")" @end) @indent
6073 (_ "{" "}" @end) @indent
6074 "#,
6075 )
6076 .unwrap(),
6077 );
6078
6079 let text = "fn a() {}";
6080
6081 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6082 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6083 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6084 editor
6085 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6086 .await;
6087
6088 editor.update_in(cx, |editor, window, cx| {
6089 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6090 editor.newline(&Newline, window, cx);
6091 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6092 assert_eq!(
6093 editor.selections.ranges(cx),
6094 &[
6095 Point::new(1, 4)..Point::new(1, 4),
6096 Point::new(3, 4)..Point::new(3, 4),
6097 Point::new(5, 0)..Point::new(5, 0)
6098 ]
6099 );
6100 });
6101}
6102
6103#[gpui::test]
6104async fn test_autoindent_selections(cx: &mut TestAppContext) {
6105 init_test(cx, |_| {});
6106
6107 {
6108 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6109 cx.set_state(indoc! {"
6110 impl A {
6111
6112 fn b() {}
6113
6114 «fn c() {
6115
6116 }ˇ»
6117 }
6118 "});
6119
6120 cx.update_editor(|editor, window, cx| {
6121 editor.autoindent(&Default::default(), window, cx);
6122 });
6123
6124 cx.assert_editor_state(indoc! {"
6125 impl A {
6126
6127 fn b() {}
6128
6129 «fn c() {
6130
6131 }ˇ»
6132 }
6133 "});
6134 }
6135
6136 {
6137 let mut cx = EditorTestContext::new_multibuffer(
6138 cx,
6139 [indoc! { "
6140 impl A {
6141 «
6142 // a
6143 fn b(){}
6144 »
6145 «
6146 }
6147 fn c(){}
6148 »
6149 "}],
6150 );
6151
6152 let buffer = cx.update_editor(|editor, _, cx| {
6153 let buffer = editor.buffer().update(cx, |buffer, _| {
6154 buffer.all_buffers().iter().next().unwrap().clone()
6155 });
6156 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6157 buffer
6158 });
6159
6160 cx.run_until_parked();
6161 cx.update_editor(|editor, window, cx| {
6162 editor.select_all(&Default::default(), window, cx);
6163 editor.autoindent(&Default::default(), window, cx)
6164 });
6165 cx.run_until_parked();
6166
6167 cx.update(|_, cx| {
6168 pretty_assertions::assert_eq!(
6169 buffer.read(cx).text(),
6170 indoc! { "
6171 impl A {
6172
6173 // a
6174 fn b(){}
6175
6176
6177 }
6178 fn c(){}
6179
6180 " }
6181 )
6182 });
6183 }
6184}
6185
6186#[gpui::test]
6187async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6188 init_test(cx, |_| {});
6189
6190 let mut cx = EditorTestContext::new(cx).await;
6191
6192 let language = Arc::new(Language::new(
6193 LanguageConfig {
6194 brackets: BracketPairConfig {
6195 pairs: vec![
6196 BracketPair {
6197 start: "{".to_string(),
6198 end: "}".to_string(),
6199 close: true,
6200 surround: true,
6201 newline: true,
6202 },
6203 BracketPair {
6204 start: "(".to_string(),
6205 end: ")".to_string(),
6206 close: true,
6207 surround: true,
6208 newline: true,
6209 },
6210 BracketPair {
6211 start: "/*".to_string(),
6212 end: " */".to_string(),
6213 close: true,
6214 surround: true,
6215 newline: true,
6216 },
6217 BracketPair {
6218 start: "[".to_string(),
6219 end: "]".to_string(),
6220 close: false,
6221 surround: false,
6222 newline: true,
6223 },
6224 BracketPair {
6225 start: "\"".to_string(),
6226 end: "\"".to_string(),
6227 close: true,
6228 surround: true,
6229 newline: false,
6230 },
6231 BracketPair {
6232 start: "<".to_string(),
6233 end: ">".to_string(),
6234 close: false,
6235 surround: true,
6236 newline: true,
6237 },
6238 ],
6239 ..Default::default()
6240 },
6241 autoclose_before: "})]".to_string(),
6242 ..Default::default()
6243 },
6244 Some(tree_sitter_rust::LANGUAGE.into()),
6245 ));
6246
6247 cx.language_registry().add(language.clone());
6248 cx.update_buffer(|buffer, cx| {
6249 buffer.set_language(Some(language), cx);
6250 });
6251
6252 cx.set_state(
6253 &r#"
6254 🏀ˇ
6255 εˇ
6256 ❤️ˇ
6257 "#
6258 .unindent(),
6259 );
6260
6261 // autoclose multiple nested brackets at multiple cursors
6262 cx.update_editor(|editor, window, cx| {
6263 editor.handle_input("{", window, cx);
6264 editor.handle_input("{", window, cx);
6265 editor.handle_input("{", window, cx);
6266 });
6267 cx.assert_editor_state(
6268 &"
6269 🏀{{{ˇ}}}
6270 ε{{{ˇ}}}
6271 ❤️{{{ˇ}}}
6272 "
6273 .unindent(),
6274 );
6275
6276 // insert a different closing bracket
6277 cx.update_editor(|editor, window, cx| {
6278 editor.handle_input(")", window, cx);
6279 });
6280 cx.assert_editor_state(
6281 &"
6282 🏀{{{)ˇ}}}
6283 ε{{{)ˇ}}}
6284 ❤️{{{)ˇ}}}
6285 "
6286 .unindent(),
6287 );
6288
6289 // skip over the auto-closed brackets when typing a closing bracket
6290 cx.update_editor(|editor, window, cx| {
6291 editor.move_right(&MoveRight, window, cx);
6292 editor.handle_input("}", window, cx);
6293 editor.handle_input("}", window, cx);
6294 editor.handle_input("}", window, cx);
6295 });
6296 cx.assert_editor_state(
6297 &"
6298 🏀{{{)}}}}ˇ
6299 ε{{{)}}}}ˇ
6300 ❤️{{{)}}}}ˇ
6301 "
6302 .unindent(),
6303 );
6304
6305 // autoclose multi-character pairs
6306 cx.set_state(
6307 &"
6308 ˇ
6309 ˇ
6310 "
6311 .unindent(),
6312 );
6313 cx.update_editor(|editor, window, cx| {
6314 editor.handle_input("/", window, cx);
6315 editor.handle_input("*", window, cx);
6316 });
6317 cx.assert_editor_state(
6318 &"
6319 /*ˇ */
6320 /*ˇ */
6321 "
6322 .unindent(),
6323 );
6324
6325 // one cursor autocloses a multi-character pair, one cursor
6326 // does not autoclose.
6327 cx.set_state(
6328 &"
6329 /ˇ
6330 ˇ
6331 "
6332 .unindent(),
6333 );
6334 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6335 cx.assert_editor_state(
6336 &"
6337 /*ˇ */
6338 *ˇ
6339 "
6340 .unindent(),
6341 );
6342
6343 // Don't autoclose if the next character isn't whitespace and isn't
6344 // listed in the language's "autoclose_before" section.
6345 cx.set_state("ˇa b");
6346 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6347 cx.assert_editor_state("{ˇa b");
6348
6349 // Don't autoclose if `close` is false for the bracket pair
6350 cx.set_state("ˇ");
6351 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6352 cx.assert_editor_state("[ˇ");
6353
6354 // Surround with brackets if text is selected
6355 cx.set_state("«aˇ» b");
6356 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6357 cx.assert_editor_state("{«aˇ»} b");
6358
6359 // Autclose pair where the start and end characters are the same
6360 cx.set_state("aˇ");
6361 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6362 cx.assert_editor_state("a\"ˇ\"");
6363 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6364 cx.assert_editor_state("a\"\"ˇ");
6365
6366 // Don't autoclose pair if autoclose is disabled
6367 cx.set_state("ˇ");
6368 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6369 cx.assert_editor_state("<ˇ");
6370
6371 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6372 cx.set_state("«aˇ» b");
6373 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6374 cx.assert_editor_state("<«aˇ»> b");
6375}
6376
6377#[gpui::test]
6378async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6379 init_test(cx, |settings| {
6380 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6381 });
6382
6383 let mut cx = EditorTestContext::new(cx).await;
6384
6385 let language = Arc::new(Language::new(
6386 LanguageConfig {
6387 brackets: BracketPairConfig {
6388 pairs: vec![
6389 BracketPair {
6390 start: "{".to_string(),
6391 end: "}".to_string(),
6392 close: true,
6393 surround: true,
6394 newline: true,
6395 },
6396 BracketPair {
6397 start: "(".to_string(),
6398 end: ")".to_string(),
6399 close: true,
6400 surround: true,
6401 newline: true,
6402 },
6403 BracketPair {
6404 start: "[".to_string(),
6405 end: "]".to_string(),
6406 close: false,
6407 surround: false,
6408 newline: true,
6409 },
6410 ],
6411 ..Default::default()
6412 },
6413 autoclose_before: "})]".to_string(),
6414 ..Default::default()
6415 },
6416 Some(tree_sitter_rust::LANGUAGE.into()),
6417 ));
6418
6419 cx.language_registry().add(language.clone());
6420 cx.update_buffer(|buffer, cx| {
6421 buffer.set_language(Some(language), cx);
6422 });
6423
6424 cx.set_state(
6425 &"
6426 ˇ
6427 ˇ
6428 ˇ
6429 "
6430 .unindent(),
6431 );
6432
6433 // ensure only matching closing brackets are skipped over
6434 cx.update_editor(|editor, window, cx| {
6435 editor.handle_input("}", window, cx);
6436 editor.move_left(&MoveLeft, window, cx);
6437 editor.handle_input(")", window, cx);
6438 editor.move_left(&MoveLeft, window, cx);
6439 });
6440 cx.assert_editor_state(
6441 &"
6442 ˇ)}
6443 ˇ)}
6444 ˇ)}
6445 "
6446 .unindent(),
6447 );
6448
6449 // skip-over closing brackets at multiple cursors
6450 cx.update_editor(|editor, window, cx| {
6451 editor.handle_input(")", window, cx);
6452 editor.handle_input("}", window, cx);
6453 });
6454 cx.assert_editor_state(
6455 &"
6456 )}ˇ
6457 )}ˇ
6458 )}ˇ
6459 "
6460 .unindent(),
6461 );
6462
6463 // ignore non-close brackets
6464 cx.update_editor(|editor, window, cx| {
6465 editor.handle_input("]", window, cx);
6466 editor.move_left(&MoveLeft, window, cx);
6467 editor.handle_input("]", window, cx);
6468 });
6469 cx.assert_editor_state(
6470 &"
6471 )}]ˇ]
6472 )}]ˇ]
6473 )}]ˇ]
6474 "
6475 .unindent(),
6476 );
6477}
6478
6479#[gpui::test]
6480async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6481 init_test(cx, |_| {});
6482
6483 let mut cx = EditorTestContext::new(cx).await;
6484
6485 let html_language = Arc::new(
6486 Language::new(
6487 LanguageConfig {
6488 name: "HTML".into(),
6489 brackets: BracketPairConfig {
6490 pairs: vec![
6491 BracketPair {
6492 start: "<".into(),
6493 end: ">".into(),
6494 close: true,
6495 ..Default::default()
6496 },
6497 BracketPair {
6498 start: "{".into(),
6499 end: "}".into(),
6500 close: true,
6501 ..Default::default()
6502 },
6503 BracketPair {
6504 start: "(".into(),
6505 end: ")".into(),
6506 close: true,
6507 ..Default::default()
6508 },
6509 ],
6510 ..Default::default()
6511 },
6512 autoclose_before: "})]>".into(),
6513 ..Default::default()
6514 },
6515 Some(tree_sitter_html::LANGUAGE.into()),
6516 )
6517 .with_injection_query(
6518 r#"
6519 (script_element
6520 (raw_text) @injection.content
6521 (#set! injection.language "javascript"))
6522 "#,
6523 )
6524 .unwrap(),
6525 );
6526
6527 let javascript_language = Arc::new(Language::new(
6528 LanguageConfig {
6529 name: "JavaScript".into(),
6530 brackets: BracketPairConfig {
6531 pairs: vec![
6532 BracketPair {
6533 start: "/*".into(),
6534 end: " */".into(),
6535 close: true,
6536 ..Default::default()
6537 },
6538 BracketPair {
6539 start: "{".into(),
6540 end: "}".into(),
6541 close: true,
6542 ..Default::default()
6543 },
6544 BracketPair {
6545 start: "(".into(),
6546 end: ")".into(),
6547 close: true,
6548 ..Default::default()
6549 },
6550 ],
6551 ..Default::default()
6552 },
6553 autoclose_before: "})]>".into(),
6554 ..Default::default()
6555 },
6556 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6557 ));
6558
6559 cx.language_registry().add(html_language.clone());
6560 cx.language_registry().add(javascript_language.clone());
6561
6562 cx.update_buffer(|buffer, cx| {
6563 buffer.set_language(Some(html_language), cx);
6564 });
6565
6566 cx.set_state(
6567 &r#"
6568 <body>ˇ
6569 <script>
6570 var x = 1;ˇ
6571 </script>
6572 </body>ˇ
6573 "#
6574 .unindent(),
6575 );
6576
6577 // Precondition: different languages are active at different locations.
6578 cx.update_editor(|editor, window, cx| {
6579 let snapshot = editor.snapshot(window, cx);
6580 let cursors = editor.selections.ranges::<usize>(cx);
6581 let languages = cursors
6582 .iter()
6583 .map(|c| snapshot.language_at(c.start).unwrap().name())
6584 .collect::<Vec<_>>();
6585 assert_eq!(
6586 languages,
6587 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6588 );
6589 });
6590
6591 // Angle brackets autoclose in HTML, but not JavaScript.
6592 cx.update_editor(|editor, window, cx| {
6593 editor.handle_input("<", window, cx);
6594 editor.handle_input("a", window, cx);
6595 });
6596 cx.assert_editor_state(
6597 &r#"
6598 <body><aˇ>
6599 <script>
6600 var x = 1;<aˇ
6601 </script>
6602 </body><aˇ>
6603 "#
6604 .unindent(),
6605 );
6606
6607 // Curly braces and parens autoclose in both HTML and JavaScript.
6608 cx.update_editor(|editor, window, cx| {
6609 editor.handle_input(" b=", window, cx);
6610 editor.handle_input("{", window, cx);
6611 editor.handle_input("c", window, cx);
6612 editor.handle_input("(", window, cx);
6613 });
6614 cx.assert_editor_state(
6615 &r#"
6616 <body><a b={c(ˇ)}>
6617 <script>
6618 var x = 1;<a b={c(ˇ)}
6619 </script>
6620 </body><a b={c(ˇ)}>
6621 "#
6622 .unindent(),
6623 );
6624
6625 // Brackets that were already autoclosed are skipped.
6626 cx.update_editor(|editor, window, cx| {
6627 editor.handle_input(")", window, cx);
6628 editor.handle_input("d", window, cx);
6629 editor.handle_input("}", window, cx);
6630 });
6631 cx.assert_editor_state(
6632 &r#"
6633 <body><a b={c()d}ˇ>
6634 <script>
6635 var x = 1;<a b={c()d}ˇ
6636 </script>
6637 </body><a b={c()d}ˇ>
6638 "#
6639 .unindent(),
6640 );
6641 cx.update_editor(|editor, window, cx| {
6642 editor.handle_input(">", window, cx);
6643 });
6644 cx.assert_editor_state(
6645 &r#"
6646 <body><a b={c()d}>ˇ
6647 <script>
6648 var x = 1;<a b={c()d}>ˇ
6649 </script>
6650 </body><a b={c()d}>ˇ
6651 "#
6652 .unindent(),
6653 );
6654
6655 // Reset
6656 cx.set_state(
6657 &r#"
6658 <body>ˇ
6659 <script>
6660 var x = 1;ˇ
6661 </script>
6662 </body>ˇ
6663 "#
6664 .unindent(),
6665 );
6666
6667 cx.update_editor(|editor, window, cx| {
6668 editor.handle_input("<", window, cx);
6669 });
6670 cx.assert_editor_state(
6671 &r#"
6672 <body><ˇ>
6673 <script>
6674 var x = 1;<ˇ
6675 </script>
6676 </body><ˇ>
6677 "#
6678 .unindent(),
6679 );
6680
6681 // When backspacing, the closing angle brackets are removed.
6682 cx.update_editor(|editor, window, cx| {
6683 editor.backspace(&Backspace, window, cx);
6684 });
6685 cx.assert_editor_state(
6686 &r#"
6687 <body>ˇ
6688 <script>
6689 var x = 1;ˇ
6690 </script>
6691 </body>ˇ
6692 "#
6693 .unindent(),
6694 );
6695
6696 // Block comments autoclose in JavaScript, but not HTML.
6697 cx.update_editor(|editor, window, cx| {
6698 editor.handle_input("/", window, cx);
6699 editor.handle_input("*", window, cx);
6700 });
6701 cx.assert_editor_state(
6702 &r#"
6703 <body>/*ˇ
6704 <script>
6705 var x = 1;/*ˇ */
6706 </script>
6707 </body>/*ˇ
6708 "#
6709 .unindent(),
6710 );
6711}
6712
6713#[gpui::test]
6714async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6715 init_test(cx, |_| {});
6716
6717 let mut cx = EditorTestContext::new(cx).await;
6718
6719 let rust_language = Arc::new(
6720 Language::new(
6721 LanguageConfig {
6722 name: "Rust".into(),
6723 brackets: serde_json::from_value(json!([
6724 { "start": "{", "end": "}", "close": true, "newline": true },
6725 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6726 ]))
6727 .unwrap(),
6728 autoclose_before: "})]>".into(),
6729 ..Default::default()
6730 },
6731 Some(tree_sitter_rust::LANGUAGE.into()),
6732 )
6733 .with_override_query("(string_literal) @string")
6734 .unwrap(),
6735 );
6736
6737 cx.language_registry().add(rust_language.clone());
6738 cx.update_buffer(|buffer, cx| {
6739 buffer.set_language(Some(rust_language), cx);
6740 });
6741
6742 cx.set_state(
6743 &r#"
6744 let x = ˇ
6745 "#
6746 .unindent(),
6747 );
6748
6749 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6750 cx.update_editor(|editor, window, cx| {
6751 editor.handle_input("\"", window, cx);
6752 });
6753 cx.assert_editor_state(
6754 &r#"
6755 let x = "ˇ"
6756 "#
6757 .unindent(),
6758 );
6759
6760 // Inserting another quotation mark. The cursor moves across the existing
6761 // automatically-inserted quotation mark.
6762 cx.update_editor(|editor, window, cx| {
6763 editor.handle_input("\"", window, cx);
6764 });
6765 cx.assert_editor_state(
6766 &r#"
6767 let x = ""ˇ
6768 "#
6769 .unindent(),
6770 );
6771
6772 // Reset
6773 cx.set_state(
6774 &r#"
6775 let x = ˇ
6776 "#
6777 .unindent(),
6778 );
6779
6780 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6781 cx.update_editor(|editor, window, cx| {
6782 editor.handle_input("\"", window, cx);
6783 editor.handle_input(" ", window, cx);
6784 editor.move_left(&Default::default(), window, cx);
6785 editor.handle_input("\\", window, cx);
6786 editor.handle_input("\"", window, cx);
6787 });
6788 cx.assert_editor_state(
6789 &r#"
6790 let x = "\"ˇ "
6791 "#
6792 .unindent(),
6793 );
6794
6795 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6796 // mark. Nothing is inserted.
6797 cx.update_editor(|editor, window, cx| {
6798 editor.move_right(&Default::default(), window, cx);
6799 editor.handle_input("\"", window, cx);
6800 });
6801 cx.assert_editor_state(
6802 &r#"
6803 let x = "\" "ˇ
6804 "#
6805 .unindent(),
6806 );
6807}
6808
6809#[gpui::test]
6810async fn test_surround_with_pair(cx: &mut TestAppContext) {
6811 init_test(cx, |_| {});
6812
6813 let language = Arc::new(Language::new(
6814 LanguageConfig {
6815 brackets: BracketPairConfig {
6816 pairs: vec![
6817 BracketPair {
6818 start: "{".to_string(),
6819 end: "}".to_string(),
6820 close: true,
6821 surround: true,
6822 newline: true,
6823 },
6824 BracketPair {
6825 start: "/* ".to_string(),
6826 end: "*/".to_string(),
6827 close: true,
6828 surround: true,
6829 ..Default::default()
6830 },
6831 ],
6832 ..Default::default()
6833 },
6834 ..Default::default()
6835 },
6836 Some(tree_sitter_rust::LANGUAGE.into()),
6837 ));
6838
6839 let text = r#"
6840 a
6841 b
6842 c
6843 "#
6844 .unindent();
6845
6846 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6847 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6848 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6849 editor
6850 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6851 .await;
6852
6853 editor.update_in(cx, |editor, window, cx| {
6854 editor.change_selections(None, window, cx, |s| {
6855 s.select_display_ranges([
6856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6857 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6858 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6859 ])
6860 });
6861
6862 editor.handle_input("{", window, cx);
6863 editor.handle_input("{", window, cx);
6864 editor.handle_input("{", window, cx);
6865 assert_eq!(
6866 editor.text(cx),
6867 "
6868 {{{a}}}
6869 {{{b}}}
6870 {{{c}}}
6871 "
6872 .unindent()
6873 );
6874 assert_eq!(
6875 editor.selections.display_ranges(cx),
6876 [
6877 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6878 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6879 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6880 ]
6881 );
6882
6883 editor.undo(&Undo, window, cx);
6884 editor.undo(&Undo, window, cx);
6885 editor.undo(&Undo, window, cx);
6886 assert_eq!(
6887 editor.text(cx),
6888 "
6889 a
6890 b
6891 c
6892 "
6893 .unindent()
6894 );
6895 assert_eq!(
6896 editor.selections.display_ranges(cx),
6897 [
6898 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6899 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6900 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6901 ]
6902 );
6903
6904 // Ensure inserting the first character of a multi-byte bracket pair
6905 // doesn't surround the selections with the bracket.
6906 editor.handle_input("/", window, cx);
6907 assert_eq!(
6908 editor.text(cx),
6909 "
6910 /
6911 /
6912 /
6913 "
6914 .unindent()
6915 );
6916 assert_eq!(
6917 editor.selections.display_ranges(cx),
6918 [
6919 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6920 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6921 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6922 ]
6923 );
6924
6925 editor.undo(&Undo, window, cx);
6926 assert_eq!(
6927 editor.text(cx),
6928 "
6929 a
6930 b
6931 c
6932 "
6933 .unindent()
6934 );
6935 assert_eq!(
6936 editor.selections.display_ranges(cx),
6937 [
6938 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6939 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6940 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6941 ]
6942 );
6943
6944 // Ensure inserting the last character of a multi-byte bracket pair
6945 // doesn't surround the selections with the bracket.
6946 editor.handle_input("*", window, cx);
6947 assert_eq!(
6948 editor.text(cx),
6949 "
6950 *
6951 *
6952 *
6953 "
6954 .unindent()
6955 );
6956 assert_eq!(
6957 editor.selections.display_ranges(cx),
6958 [
6959 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6960 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6961 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6962 ]
6963 );
6964 });
6965}
6966
6967#[gpui::test]
6968async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6969 init_test(cx, |_| {});
6970
6971 let language = Arc::new(Language::new(
6972 LanguageConfig {
6973 brackets: BracketPairConfig {
6974 pairs: vec![BracketPair {
6975 start: "{".to_string(),
6976 end: "}".to_string(),
6977 close: true,
6978 surround: true,
6979 newline: true,
6980 }],
6981 ..Default::default()
6982 },
6983 autoclose_before: "}".to_string(),
6984 ..Default::default()
6985 },
6986 Some(tree_sitter_rust::LANGUAGE.into()),
6987 ));
6988
6989 let text = r#"
6990 a
6991 b
6992 c
6993 "#
6994 .unindent();
6995
6996 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6997 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6998 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6999 editor
7000 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7001 .await;
7002
7003 editor.update_in(cx, |editor, window, cx| {
7004 editor.change_selections(None, window, cx, |s| {
7005 s.select_ranges([
7006 Point::new(0, 1)..Point::new(0, 1),
7007 Point::new(1, 1)..Point::new(1, 1),
7008 Point::new(2, 1)..Point::new(2, 1),
7009 ])
7010 });
7011
7012 editor.handle_input("{", window, cx);
7013 editor.handle_input("{", window, cx);
7014 editor.handle_input("_", window, cx);
7015 assert_eq!(
7016 editor.text(cx),
7017 "
7018 a{{_}}
7019 b{{_}}
7020 c{{_}}
7021 "
7022 .unindent()
7023 );
7024 assert_eq!(
7025 editor.selections.ranges::<Point>(cx),
7026 [
7027 Point::new(0, 4)..Point::new(0, 4),
7028 Point::new(1, 4)..Point::new(1, 4),
7029 Point::new(2, 4)..Point::new(2, 4)
7030 ]
7031 );
7032
7033 editor.backspace(&Default::default(), window, cx);
7034 editor.backspace(&Default::default(), window, cx);
7035 assert_eq!(
7036 editor.text(cx),
7037 "
7038 a{}
7039 b{}
7040 c{}
7041 "
7042 .unindent()
7043 );
7044 assert_eq!(
7045 editor.selections.ranges::<Point>(cx),
7046 [
7047 Point::new(0, 2)..Point::new(0, 2),
7048 Point::new(1, 2)..Point::new(1, 2),
7049 Point::new(2, 2)..Point::new(2, 2)
7050 ]
7051 );
7052
7053 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7054 assert_eq!(
7055 editor.text(cx),
7056 "
7057 a
7058 b
7059 c
7060 "
7061 .unindent()
7062 );
7063 assert_eq!(
7064 editor.selections.ranges::<Point>(cx),
7065 [
7066 Point::new(0, 1)..Point::new(0, 1),
7067 Point::new(1, 1)..Point::new(1, 1),
7068 Point::new(2, 1)..Point::new(2, 1)
7069 ]
7070 );
7071 });
7072}
7073
7074#[gpui::test]
7075async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7076 init_test(cx, |settings| {
7077 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7078 });
7079
7080 let mut cx = EditorTestContext::new(cx).await;
7081
7082 let language = Arc::new(Language::new(
7083 LanguageConfig {
7084 brackets: BracketPairConfig {
7085 pairs: vec![
7086 BracketPair {
7087 start: "{".to_string(),
7088 end: "}".to_string(),
7089 close: true,
7090 surround: true,
7091 newline: true,
7092 },
7093 BracketPair {
7094 start: "(".to_string(),
7095 end: ")".to_string(),
7096 close: true,
7097 surround: true,
7098 newline: true,
7099 },
7100 BracketPair {
7101 start: "[".to_string(),
7102 end: "]".to_string(),
7103 close: false,
7104 surround: true,
7105 newline: true,
7106 },
7107 ],
7108 ..Default::default()
7109 },
7110 autoclose_before: "})]".to_string(),
7111 ..Default::default()
7112 },
7113 Some(tree_sitter_rust::LANGUAGE.into()),
7114 ));
7115
7116 cx.language_registry().add(language.clone());
7117 cx.update_buffer(|buffer, cx| {
7118 buffer.set_language(Some(language), cx);
7119 });
7120
7121 cx.set_state(
7122 &"
7123 {(ˇ)}
7124 [[ˇ]]
7125 {(ˇ)}
7126 "
7127 .unindent(),
7128 );
7129
7130 cx.update_editor(|editor, window, cx| {
7131 editor.backspace(&Default::default(), window, cx);
7132 editor.backspace(&Default::default(), window, cx);
7133 });
7134
7135 cx.assert_editor_state(
7136 &"
7137 ˇ
7138 ˇ]]
7139 ˇ
7140 "
7141 .unindent(),
7142 );
7143
7144 cx.update_editor(|editor, window, cx| {
7145 editor.handle_input("{", window, cx);
7146 editor.handle_input("{", window, cx);
7147 editor.move_right(&MoveRight, window, cx);
7148 editor.move_right(&MoveRight, window, cx);
7149 editor.move_left(&MoveLeft, window, cx);
7150 editor.move_left(&MoveLeft, window, cx);
7151 editor.backspace(&Default::default(), window, cx);
7152 });
7153
7154 cx.assert_editor_state(
7155 &"
7156 {ˇ}
7157 {ˇ}]]
7158 {ˇ}
7159 "
7160 .unindent(),
7161 );
7162
7163 cx.update_editor(|editor, window, cx| {
7164 editor.backspace(&Default::default(), window, cx);
7165 });
7166
7167 cx.assert_editor_state(
7168 &"
7169 ˇ
7170 ˇ]]
7171 ˇ
7172 "
7173 .unindent(),
7174 );
7175}
7176
7177#[gpui::test]
7178async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7179 init_test(cx, |_| {});
7180
7181 let language = Arc::new(Language::new(
7182 LanguageConfig::default(),
7183 Some(tree_sitter_rust::LANGUAGE.into()),
7184 ));
7185
7186 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7187 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7188 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7189 editor
7190 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7191 .await;
7192
7193 editor.update_in(cx, |editor, window, cx| {
7194 editor.set_auto_replace_emoji_shortcode(true);
7195
7196 editor.handle_input("Hello ", window, cx);
7197 editor.handle_input(":wave", window, cx);
7198 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7199
7200 editor.handle_input(":", window, cx);
7201 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7202
7203 editor.handle_input(" :smile", window, cx);
7204 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7205
7206 editor.handle_input(":", window, cx);
7207 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7208
7209 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7210 editor.handle_input(":wave", window, cx);
7211 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7212
7213 editor.handle_input(":", window, cx);
7214 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7215
7216 editor.handle_input(":1", window, cx);
7217 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7218
7219 editor.handle_input(":", window, cx);
7220 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7221
7222 // Ensure shortcode does not get replaced when it is part of a word
7223 editor.handle_input(" Test:wave", window, cx);
7224 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7225
7226 editor.handle_input(":", window, cx);
7227 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7228
7229 editor.set_auto_replace_emoji_shortcode(false);
7230
7231 // Ensure shortcode does not get replaced when auto replace is off
7232 editor.handle_input(" :wave", window, cx);
7233 assert_eq!(
7234 editor.text(cx),
7235 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7236 );
7237
7238 editor.handle_input(":", window, cx);
7239 assert_eq!(
7240 editor.text(cx),
7241 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7242 );
7243 });
7244}
7245
7246#[gpui::test]
7247async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7248 init_test(cx, |_| {});
7249
7250 let (text, insertion_ranges) = marked_text_ranges(
7251 indoc! {"
7252 ˇ
7253 "},
7254 false,
7255 );
7256
7257 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7258 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7259
7260 _ = editor.update_in(cx, |editor, window, cx| {
7261 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7262
7263 editor
7264 .insert_snippet(&insertion_ranges, snippet, window, cx)
7265 .unwrap();
7266
7267 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7268 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7269 assert_eq!(editor.text(cx), expected_text);
7270 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7271 }
7272
7273 assert(
7274 editor,
7275 cx,
7276 indoc! {"
7277 type «» =•
7278 "},
7279 );
7280
7281 assert!(editor.context_menu_visible(), "There should be a matches");
7282 });
7283}
7284
7285#[gpui::test]
7286async fn test_snippets(cx: &mut TestAppContext) {
7287 init_test(cx, |_| {});
7288
7289 let (text, insertion_ranges) = marked_text_ranges(
7290 indoc! {"
7291 a.ˇ b
7292 a.ˇ b
7293 a.ˇ b
7294 "},
7295 false,
7296 );
7297
7298 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7299 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7300
7301 editor.update_in(cx, |editor, window, cx| {
7302 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7303
7304 editor
7305 .insert_snippet(&insertion_ranges, snippet, window, cx)
7306 .unwrap();
7307
7308 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7309 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7310 assert_eq!(editor.text(cx), expected_text);
7311 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7312 }
7313
7314 assert(
7315 editor,
7316 cx,
7317 indoc! {"
7318 a.f(«one», two, «three») b
7319 a.f(«one», two, «three») b
7320 a.f(«one», two, «three») b
7321 "},
7322 );
7323
7324 // Can't move earlier than the first tab stop
7325 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7326 assert(
7327 editor,
7328 cx,
7329 indoc! {"
7330 a.f(«one», two, «three») b
7331 a.f(«one», two, «three») b
7332 a.f(«one», two, «three») b
7333 "},
7334 );
7335
7336 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7337 assert(
7338 editor,
7339 cx,
7340 indoc! {"
7341 a.f(one, «two», three) b
7342 a.f(one, «two», three) b
7343 a.f(one, «two», three) b
7344 "},
7345 );
7346
7347 editor.move_to_prev_snippet_tabstop(window, cx);
7348 assert(
7349 editor,
7350 cx,
7351 indoc! {"
7352 a.f(«one», two, «three») b
7353 a.f(«one», two, «three») b
7354 a.f(«one», two, «three») b
7355 "},
7356 );
7357
7358 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7359 assert(
7360 editor,
7361 cx,
7362 indoc! {"
7363 a.f(one, «two», three) b
7364 a.f(one, «two», three) b
7365 a.f(one, «two», three) b
7366 "},
7367 );
7368 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7369 assert(
7370 editor,
7371 cx,
7372 indoc! {"
7373 a.f(one, two, three)ˇ b
7374 a.f(one, two, three)ˇ b
7375 a.f(one, two, three)ˇ b
7376 "},
7377 );
7378
7379 // As soon as the last tab stop is reached, snippet state is gone
7380 editor.move_to_prev_snippet_tabstop(window, cx);
7381 assert(
7382 editor,
7383 cx,
7384 indoc! {"
7385 a.f(one, two, three)ˇ b
7386 a.f(one, two, three)ˇ b
7387 a.f(one, two, three)ˇ b
7388 "},
7389 );
7390 });
7391}
7392
7393#[gpui::test]
7394async fn test_document_format_during_save(cx: &mut TestAppContext) {
7395 init_test(cx, |_| {});
7396
7397 let fs = FakeFs::new(cx.executor());
7398 fs.insert_file(path!("/file.rs"), Default::default()).await;
7399
7400 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7401
7402 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7403 language_registry.add(rust_lang());
7404 let mut fake_servers = language_registry.register_fake_lsp(
7405 "Rust",
7406 FakeLspAdapter {
7407 capabilities: lsp::ServerCapabilities {
7408 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7409 ..Default::default()
7410 },
7411 ..Default::default()
7412 },
7413 );
7414
7415 let buffer = project
7416 .update(cx, |project, cx| {
7417 project.open_local_buffer(path!("/file.rs"), cx)
7418 })
7419 .await
7420 .unwrap();
7421
7422 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7423 let (editor, cx) = cx.add_window_view(|window, cx| {
7424 build_editor_with_project(project.clone(), buffer, window, cx)
7425 });
7426 editor.update_in(cx, |editor, window, cx| {
7427 editor.set_text("one\ntwo\nthree\n", window, cx)
7428 });
7429 assert!(cx.read(|cx| editor.is_dirty(cx)));
7430
7431 cx.executor().start_waiting();
7432 let fake_server = fake_servers.next().await.unwrap();
7433
7434 let save = editor
7435 .update_in(cx, |editor, window, cx| {
7436 editor.save(true, project.clone(), window, cx)
7437 })
7438 .unwrap();
7439 fake_server
7440 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7441 assert_eq!(
7442 params.text_document.uri,
7443 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7444 );
7445 assert_eq!(params.options.tab_size, 4);
7446 Ok(Some(vec![lsp::TextEdit::new(
7447 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7448 ", ".to_string(),
7449 )]))
7450 })
7451 .next()
7452 .await;
7453 cx.executor().start_waiting();
7454 save.await;
7455
7456 assert_eq!(
7457 editor.update(cx, |editor, cx| editor.text(cx)),
7458 "one, two\nthree\n"
7459 );
7460 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7461
7462 editor.update_in(cx, |editor, window, cx| {
7463 editor.set_text("one\ntwo\nthree\n", window, cx)
7464 });
7465 assert!(cx.read(|cx| editor.is_dirty(cx)));
7466
7467 // Ensure we can still save even if formatting hangs.
7468 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7469 assert_eq!(
7470 params.text_document.uri,
7471 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7472 );
7473 futures::future::pending::<()>().await;
7474 unreachable!()
7475 });
7476 let save = editor
7477 .update_in(cx, |editor, window, cx| {
7478 editor.save(true, project.clone(), window, cx)
7479 })
7480 .unwrap();
7481 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7482 cx.executor().start_waiting();
7483 save.await;
7484 assert_eq!(
7485 editor.update(cx, |editor, cx| editor.text(cx)),
7486 "one\ntwo\nthree\n"
7487 );
7488 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7489
7490 // For non-dirty buffer, no formatting request should be sent
7491 let save = editor
7492 .update_in(cx, |editor, window, cx| {
7493 editor.save(true, project.clone(), window, cx)
7494 })
7495 .unwrap();
7496 let _pending_format_request = fake_server
7497 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7498 panic!("Should not be invoked on non-dirty buffer");
7499 })
7500 .next();
7501 cx.executor().start_waiting();
7502 save.await;
7503
7504 // Set rust language override and assert overridden tabsize is sent to language server
7505 update_test_language_settings(cx, |settings| {
7506 settings.languages.insert(
7507 "Rust".into(),
7508 LanguageSettingsContent {
7509 tab_size: NonZeroU32::new(8),
7510 ..Default::default()
7511 },
7512 );
7513 });
7514
7515 editor.update_in(cx, |editor, window, cx| {
7516 editor.set_text("somehting_new\n", window, cx)
7517 });
7518 assert!(cx.read(|cx| editor.is_dirty(cx)));
7519 let save = editor
7520 .update_in(cx, |editor, window, cx| {
7521 editor.save(true, project.clone(), window, cx)
7522 })
7523 .unwrap();
7524 fake_server
7525 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7526 assert_eq!(
7527 params.text_document.uri,
7528 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7529 );
7530 assert_eq!(params.options.tab_size, 8);
7531 Ok(Some(vec![]))
7532 })
7533 .next()
7534 .await;
7535 cx.executor().start_waiting();
7536 save.await;
7537}
7538
7539#[gpui::test]
7540async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7541 init_test(cx, |_| {});
7542
7543 let cols = 4;
7544 let rows = 10;
7545 let sample_text_1 = sample_text(rows, cols, 'a');
7546 assert_eq!(
7547 sample_text_1,
7548 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7549 );
7550 let sample_text_2 = sample_text(rows, cols, 'l');
7551 assert_eq!(
7552 sample_text_2,
7553 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7554 );
7555 let sample_text_3 = sample_text(rows, cols, 'v');
7556 assert_eq!(
7557 sample_text_3,
7558 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7559 );
7560
7561 let fs = FakeFs::new(cx.executor());
7562 fs.insert_tree(
7563 path!("/a"),
7564 json!({
7565 "main.rs": sample_text_1,
7566 "other.rs": sample_text_2,
7567 "lib.rs": sample_text_3,
7568 }),
7569 )
7570 .await;
7571
7572 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7573 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7574 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7575
7576 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7577 language_registry.add(rust_lang());
7578 let mut fake_servers = language_registry.register_fake_lsp(
7579 "Rust",
7580 FakeLspAdapter {
7581 capabilities: lsp::ServerCapabilities {
7582 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7583 ..Default::default()
7584 },
7585 ..Default::default()
7586 },
7587 );
7588
7589 let worktree = project.update(cx, |project, cx| {
7590 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7591 assert_eq!(worktrees.len(), 1);
7592 worktrees.pop().unwrap()
7593 });
7594 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7595
7596 let buffer_1 = project
7597 .update(cx, |project, cx| {
7598 project.open_buffer((worktree_id, "main.rs"), cx)
7599 })
7600 .await
7601 .unwrap();
7602 let buffer_2 = project
7603 .update(cx, |project, cx| {
7604 project.open_buffer((worktree_id, "other.rs"), cx)
7605 })
7606 .await
7607 .unwrap();
7608 let buffer_3 = project
7609 .update(cx, |project, cx| {
7610 project.open_buffer((worktree_id, "lib.rs"), cx)
7611 })
7612 .await
7613 .unwrap();
7614
7615 let multi_buffer = cx.new(|cx| {
7616 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7617 multi_buffer.push_excerpts(
7618 buffer_1.clone(),
7619 [
7620 ExcerptRange {
7621 context: Point::new(0, 0)..Point::new(3, 0),
7622 primary: None,
7623 },
7624 ExcerptRange {
7625 context: Point::new(5, 0)..Point::new(7, 0),
7626 primary: None,
7627 },
7628 ExcerptRange {
7629 context: Point::new(9, 0)..Point::new(10, 4),
7630 primary: None,
7631 },
7632 ],
7633 cx,
7634 );
7635 multi_buffer.push_excerpts(
7636 buffer_2.clone(),
7637 [
7638 ExcerptRange {
7639 context: Point::new(0, 0)..Point::new(3, 0),
7640 primary: None,
7641 },
7642 ExcerptRange {
7643 context: Point::new(5, 0)..Point::new(7, 0),
7644 primary: None,
7645 },
7646 ExcerptRange {
7647 context: Point::new(9, 0)..Point::new(10, 4),
7648 primary: None,
7649 },
7650 ],
7651 cx,
7652 );
7653 multi_buffer.push_excerpts(
7654 buffer_3.clone(),
7655 [
7656 ExcerptRange {
7657 context: Point::new(0, 0)..Point::new(3, 0),
7658 primary: None,
7659 },
7660 ExcerptRange {
7661 context: Point::new(5, 0)..Point::new(7, 0),
7662 primary: None,
7663 },
7664 ExcerptRange {
7665 context: Point::new(9, 0)..Point::new(10, 4),
7666 primary: None,
7667 },
7668 ],
7669 cx,
7670 );
7671 multi_buffer
7672 });
7673 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7674 Editor::new(
7675 EditorMode::Full,
7676 multi_buffer,
7677 Some(project.clone()),
7678 true,
7679 window,
7680 cx,
7681 )
7682 });
7683
7684 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7685 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7686 s.select_ranges(Some(1..2))
7687 });
7688 editor.insert("|one|two|three|", window, cx);
7689 });
7690 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7691 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7692 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7693 s.select_ranges(Some(60..70))
7694 });
7695 editor.insert("|four|five|six|", window, cx);
7696 });
7697 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7698
7699 // First two buffers should be edited, but not the third one.
7700 assert_eq!(
7701 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7702 "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}",
7703 );
7704 buffer_1.update(cx, |buffer, _| {
7705 assert!(buffer.is_dirty());
7706 assert_eq!(
7707 buffer.text(),
7708 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7709 )
7710 });
7711 buffer_2.update(cx, |buffer, _| {
7712 assert!(buffer.is_dirty());
7713 assert_eq!(
7714 buffer.text(),
7715 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7716 )
7717 });
7718 buffer_3.update(cx, |buffer, _| {
7719 assert!(!buffer.is_dirty());
7720 assert_eq!(buffer.text(), sample_text_3,)
7721 });
7722 cx.executor().run_until_parked();
7723
7724 cx.executor().start_waiting();
7725 let save = multi_buffer_editor
7726 .update_in(cx, |editor, window, cx| {
7727 editor.save(true, project.clone(), window, cx)
7728 })
7729 .unwrap();
7730
7731 let fake_server = fake_servers.next().await.unwrap();
7732 fake_server
7733 .server
7734 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7735 Ok(Some(vec![lsp::TextEdit::new(
7736 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7737 format!("[{} formatted]", params.text_document.uri),
7738 )]))
7739 })
7740 .detach();
7741 save.await;
7742
7743 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7744 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7745 assert_eq!(
7746 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7747 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"),
7748 );
7749 buffer_1.update(cx, |buffer, _| {
7750 assert!(!buffer.is_dirty());
7751 assert_eq!(
7752 buffer.text(),
7753 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7754 )
7755 });
7756 buffer_2.update(cx, |buffer, _| {
7757 assert!(!buffer.is_dirty());
7758 assert_eq!(
7759 buffer.text(),
7760 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7761 )
7762 });
7763 buffer_3.update(cx, |buffer, _| {
7764 assert!(!buffer.is_dirty());
7765 assert_eq!(buffer.text(), sample_text_3,)
7766 });
7767}
7768
7769#[gpui::test]
7770async fn test_range_format_during_save(cx: &mut TestAppContext) {
7771 init_test(cx, |_| {});
7772
7773 let fs = FakeFs::new(cx.executor());
7774 fs.insert_file(path!("/file.rs"), Default::default()).await;
7775
7776 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7777
7778 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7779 language_registry.add(rust_lang());
7780 let mut fake_servers = language_registry.register_fake_lsp(
7781 "Rust",
7782 FakeLspAdapter {
7783 capabilities: lsp::ServerCapabilities {
7784 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7785 ..Default::default()
7786 },
7787 ..Default::default()
7788 },
7789 );
7790
7791 let buffer = project
7792 .update(cx, |project, cx| {
7793 project.open_local_buffer(path!("/file.rs"), cx)
7794 })
7795 .await
7796 .unwrap();
7797
7798 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7799 let (editor, cx) = cx.add_window_view(|window, cx| {
7800 build_editor_with_project(project.clone(), buffer, window, cx)
7801 });
7802 editor.update_in(cx, |editor, window, cx| {
7803 editor.set_text("one\ntwo\nthree\n", window, cx)
7804 });
7805 assert!(cx.read(|cx| editor.is_dirty(cx)));
7806
7807 cx.executor().start_waiting();
7808 let fake_server = fake_servers.next().await.unwrap();
7809
7810 let save = editor
7811 .update_in(cx, |editor, window, cx| {
7812 editor.save(true, project.clone(), window, cx)
7813 })
7814 .unwrap();
7815 fake_server
7816 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7817 assert_eq!(
7818 params.text_document.uri,
7819 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7820 );
7821 assert_eq!(params.options.tab_size, 4);
7822 Ok(Some(vec![lsp::TextEdit::new(
7823 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7824 ", ".to_string(),
7825 )]))
7826 })
7827 .next()
7828 .await;
7829 cx.executor().start_waiting();
7830 save.await;
7831 assert_eq!(
7832 editor.update(cx, |editor, cx| editor.text(cx)),
7833 "one, two\nthree\n"
7834 );
7835 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7836
7837 editor.update_in(cx, |editor, window, cx| {
7838 editor.set_text("one\ntwo\nthree\n", window, cx)
7839 });
7840 assert!(cx.read(|cx| editor.is_dirty(cx)));
7841
7842 // Ensure we can still save even if formatting hangs.
7843 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7844 move |params, _| async move {
7845 assert_eq!(
7846 params.text_document.uri,
7847 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7848 );
7849 futures::future::pending::<()>().await;
7850 unreachable!()
7851 },
7852 );
7853 let save = editor
7854 .update_in(cx, |editor, window, cx| {
7855 editor.save(true, project.clone(), window, cx)
7856 })
7857 .unwrap();
7858 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7859 cx.executor().start_waiting();
7860 save.await;
7861 assert_eq!(
7862 editor.update(cx, |editor, cx| editor.text(cx)),
7863 "one\ntwo\nthree\n"
7864 );
7865 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7866
7867 // For non-dirty buffer, no formatting request should be sent
7868 let save = editor
7869 .update_in(cx, |editor, window, cx| {
7870 editor.save(true, project.clone(), window, cx)
7871 })
7872 .unwrap();
7873 let _pending_format_request = fake_server
7874 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7875 panic!("Should not be invoked on non-dirty buffer");
7876 })
7877 .next();
7878 cx.executor().start_waiting();
7879 save.await;
7880
7881 // Set Rust language override and assert overridden tabsize is sent to language server
7882 update_test_language_settings(cx, |settings| {
7883 settings.languages.insert(
7884 "Rust".into(),
7885 LanguageSettingsContent {
7886 tab_size: NonZeroU32::new(8),
7887 ..Default::default()
7888 },
7889 );
7890 });
7891
7892 editor.update_in(cx, |editor, window, cx| {
7893 editor.set_text("somehting_new\n", window, cx)
7894 });
7895 assert!(cx.read(|cx| editor.is_dirty(cx)));
7896 let save = editor
7897 .update_in(cx, |editor, window, cx| {
7898 editor.save(true, project.clone(), window, cx)
7899 })
7900 .unwrap();
7901 fake_server
7902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7903 assert_eq!(
7904 params.text_document.uri,
7905 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7906 );
7907 assert_eq!(params.options.tab_size, 8);
7908 Ok(Some(vec![]))
7909 })
7910 .next()
7911 .await;
7912 cx.executor().start_waiting();
7913 save.await;
7914}
7915
7916#[gpui::test]
7917async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7918 init_test(cx, |settings| {
7919 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7920 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7921 ))
7922 });
7923
7924 let fs = FakeFs::new(cx.executor());
7925 fs.insert_file(path!("/file.rs"), Default::default()).await;
7926
7927 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7928
7929 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7930 language_registry.add(Arc::new(Language::new(
7931 LanguageConfig {
7932 name: "Rust".into(),
7933 matcher: LanguageMatcher {
7934 path_suffixes: vec!["rs".to_string()],
7935 ..Default::default()
7936 },
7937 ..LanguageConfig::default()
7938 },
7939 Some(tree_sitter_rust::LANGUAGE.into()),
7940 )));
7941 update_test_language_settings(cx, |settings| {
7942 // Enable Prettier formatting for the same buffer, and ensure
7943 // LSP is called instead of Prettier.
7944 settings.defaults.prettier = Some(PrettierSettings {
7945 allowed: true,
7946 ..PrettierSettings::default()
7947 });
7948 });
7949 let mut fake_servers = language_registry.register_fake_lsp(
7950 "Rust",
7951 FakeLspAdapter {
7952 capabilities: lsp::ServerCapabilities {
7953 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7954 ..Default::default()
7955 },
7956 ..Default::default()
7957 },
7958 );
7959
7960 let buffer = project
7961 .update(cx, |project, cx| {
7962 project.open_local_buffer(path!("/file.rs"), cx)
7963 })
7964 .await
7965 .unwrap();
7966
7967 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7968 let (editor, cx) = cx.add_window_view(|window, cx| {
7969 build_editor_with_project(project.clone(), buffer, window, cx)
7970 });
7971 editor.update_in(cx, |editor, window, cx| {
7972 editor.set_text("one\ntwo\nthree\n", window, cx)
7973 });
7974
7975 cx.executor().start_waiting();
7976 let fake_server = fake_servers.next().await.unwrap();
7977
7978 let format = editor
7979 .update_in(cx, |editor, window, cx| {
7980 editor.perform_format(
7981 project.clone(),
7982 FormatTrigger::Manual,
7983 FormatTarget::Buffers,
7984 window,
7985 cx,
7986 )
7987 })
7988 .unwrap();
7989 fake_server
7990 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7991 assert_eq!(
7992 params.text_document.uri,
7993 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7994 );
7995 assert_eq!(params.options.tab_size, 4);
7996 Ok(Some(vec![lsp::TextEdit::new(
7997 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7998 ", ".to_string(),
7999 )]))
8000 })
8001 .next()
8002 .await;
8003 cx.executor().start_waiting();
8004 format.await;
8005 assert_eq!(
8006 editor.update(cx, |editor, cx| editor.text(cx)),
8007 "one, two\nthree\n"
8008 );
8009
8010 editor.update_in(cx, |editor, window, cx| {
8011 editor.set_text("one\ntwo\nthree\n", window, cx)
8012 });
8013 // Ensure we don't lock if formatting hangs.
8014 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8015 assert_eq!(
8016 params.text_document.uri,
8017 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8018 );
8019 futures::future::pending::<()>().await;
8020 unreachable!()
8021 });
8022 let format = editor
8023 .update_in(cx, |editor, window, cx| {
8024 editor.perform_format(
8025 project,
8026 FormatTrigger::Manual,
8027 FormatTarget::Buffers,
8028 window,
8029 cx,
8030 )
8031 })
8032 .unwrap();
8033 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8034 cx.executor().start_waiting();
8035 format.await;
8036 assert_eq!(
8037 editor.update(cx, |editor, cx| editor.text(cx)),
8038 "one\ntwo\nthree\n"
8039 );
8040}
8041
8042#[gpui::test]
8043async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8044 init_test(cx, |settings| {
8045 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8046 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8047 ))
8048 });
8049
8050 let fs = FakeFs::new(cx.executor());
8051 fs.insert_file(path!("/file.ts"), Default::default()).await;
8052
8053 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8054
8055 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8056 language_registry.add(Arc::new(Language::new(
8057 LanguageConfig {
8058 name: "TypeScript".into(),
8059 matcher: LanguageMatcher {
8060 path_suffixes: vec!["ts".to_string()],
8061 ..Default::default()
8062 },
8063 ..LanguageConfig::default()
8064 },
8065 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8066 )));
8067 update_test_language_settings(cx, |settings| {
8068 settings.defaults.prettier = Some(PrettierSettings {
8069 allowed: true,
8070 ..PrettierSettings::default()
8071 });
8072 });
8073 let mut fake_servers = language_registry.register_fake_lsp(
8074 "TypeScript",
8075 FakeLspAdapter {
8076 capabilities: lsp::ServerCapabilities {
8077 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8078 ..Default::default()
8079 },
8080 ..Default::default()
8081 },
8082 );
8083
8084 let buffer = project
8085 .update(cx, |project, cx| {
8086 project.open_local_buffer(path!("/file.ts"), cx)
8087 })
8088 .await
8089 .unwrap();
8090
8091 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8092 let (editor, cx) = cx.add_window_view(|window, cx| {
8093 build_editor_with_project(project.clone(), buffer, window, cx)
8094 });
8095 editor.update_in(cx, |editor, window, cx| {
8096 editor.set_text(
8097 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8098 window,
8099 cx,
8100 )
8101 });
8102
8103 cx.executor().start_waiting();
8104 let fake_server = fake_servers.next().await.unwrap();
8105
8106 let format = editor
8107 .update_in(cx, |editor, window, cx| {
8108 editor.perform_code_action_kind(
8109 project.clone(),
8110 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8111 window,
8112 cx,
8113 )
8114 })
8115 .unwrap();
8116 fake_server
8117 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8118 assert_eq!(
8119 params.text_document.uri,
8120 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8121 );
8122 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8123 lsp::CodeAction {
8124 title: "Organize Imports".to_string(),
8125 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8126 edit: Some(lsp::WorkspaceEdit {
8127 changes: Some(
8128 [(
8129 params.text_document.uri.clone(),
8130 vec![lsp::TextEdit::new(
8131 lsp::Range::new(
8132 lsp::Position::new(1, 0),
8133 lsp::Position::new(2, 0),
8134 ),
8135 "".to_string(),
8136 )],
8137 )]
8138 .into_iter()
8139 .collect(),
8140 ),
8141 ..Default::default()
8142 }),
8143 ..Default::default()
8144 },
8145 )]))
8146 })
8147 .next()
8148 .await;
8149 cx.executor().start_waiting();
8150 format.await;
8151 assert_eq!(
8152 editor.update(cx, |editor, cx| editor.text(cx)),
8153 "import { a } from 'module';\n\nconst x = a;\n"
8154 );
8155
8156 editor.update_in(cx, |editor, window, cx| {
8157 editor.set_text(
8158 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8159 window,
8160 cx,
8161 )
8162 });
8163 // Ensure we don't lock if code action hangs.
8164 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8165 move |params, _| async move {
8166 assert_eq!(
8167 params.text_document.uri,
8168 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8169 );
8170 futures::future::pending::<()>().await;
8171 unreachable!()
8172 },
8173 );
8174 let format = editor
8175 .update_in(cx, |editor, window, cx| {
8176 editor.perform_code_action_kind(
8177 project,
8178 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8179 window,
8180 cx,
8181 )
8182 })
8183 .unwrap();
8184 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8185 cx.executor().start_waiting();
8186 format.await;
8187 assert_eq!(
8188 editor.update(cx, |editor, cx| editor.text(cx)),
8189 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8190 );
8191}
8192
8193#[gpui::test]
8194async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8195 init_test(cx, |_| {});
8196
8197 let mut cx = EditorLspTestContext::new_rust(
8198 lsp::ServerCapabilities {
8199 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8200 ..Default::default()
8201 },
8202 cx,
8203 )
8204 .await;
8205
8206 cx.set_state(indoc! {"
8207 one.twoˇ
8208 "});
8209
8210 // The format request takes a long time. When it completes, it inserts
8211 // a newline and an indent before the `.`
8212 cx.lsp
8213 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8214 let executor = cx.background_executor().clone();
8215 async move {
8216 executor.timer(Duration::from_millis(100)).await;
8217 Ok(Some(vec![lsp::TextEdit {
8218 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8219 new_text: "\n ".into(),
8220 }]))
8221 }
8222 });
8223
8224 // Submit a format request.
8225 let format_1 = cx
8226 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8227 .unwrap();
8228 cx.executor().run_until_parked();
8229
8230 // Submit a second format request.
8231 let format_2 = cx
8232 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8233 .unwrap();
8234 cx.executor().run_until_parked();
8235
8236 // Wait for both format requests to complete
8237 cx.executor().advance_clock(Duration::from_millis(200));
8238 cx.executor().start_waiting();
8239 format_1.await.unwrap();
8240 cx.executor().start_waiting();
8241 format_2.await.unwrap();
8242
8243 // The formatting edits only happens once.
8244 cx.assert_editor_state(indoc! {"
8245 one
8246 .twoˇ
8247 "});
8248}
8249
8250#[gpui::test]
8251async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8252 init_test(cx, |settings| {
8253 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8254 });
8255
8256 let mut cx = EditorLspTestContext::new_rust(
8257 lsp::ServerCapabilities {
8258 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8259 ..Default::default()
8260 },
8261 cx,
8262 )
8263 .await;
8264
8265 // Set up a buffer white some trailing whitespace and no trailing newline.
8266 cx.set_state(
8267 &[
8268 "one ", //
8269 "twoˇ", //
8270 "three ", //
8271 "four", //
8272 ]
8273 .join("\n"),
8274 );
8275
8276 // Submit a format request.
8277 let format = cx
8278 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8279 .unwrap();
8280
8281 // Record which buffer changes have been sent to the language server
8282 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8283 cx.lsp
8284 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8285 let buffer_changes = buffer_changes.clone();
8286 move |params, _| {
8287 buffer_changes.lock().extend(
8288 params
8289 .content_changes
8290 .into_iter()
8291 .map(|e| (e.range.unwrap(), e.text)),
8292 );
8293 }
8294 });
8295
8296 // Handle formatting requests to the language server.
8297 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8298 let buffer_changes = buffer_changes.clone();
8299 move |_, _| {
8300 // When formatting is requested, trailing whitespace has already been stripped,
8301 // and the trailing newline has already been added.
8302 assert_eq!(
8303 &buffer_changes.lock()[1..],
8304 &[
8305 (
8306 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8307 "".into()
8308 ),
8309 (
8310 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8311 "".into()
8312 ),
8313 (
8314 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8315 "\n".into()
8316 ),
8317 ]
8318 );
8319
8320 // Insert blank lines between each line of the buffer.
8321 async move {
8322 Ok(Some(vec![
8323 lsp::TextEdit {
8324 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8325 new_text: "\n".into(),
8326 },
8327 lsp::TextEdit {
8328 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8329 new_text: "\n".into(),
8330 },
8331 ]))
8332 }
8333 }
8334 });
8335
8336 // After formatting the buffer, the trailing whitespace is stripped,
8337 // a newline is appended, and the edits provided by the language server
8338 // have been applied.
8339 format.await.unwrap();
8340 cx.assert_editor_state(
8341 &[
8342 "one", //
8343 "", //
8344 "twoˇ", //
8345 "", //
8346 "three", //
8347 "four", //
8348 "", //
8349 ]
8350 .join("\n"),
8351 );
8352
8353 // Undoing the formatting undoes the trailing whitespace removal, the
8354 // trailing newline, and the LSP edits.
8355 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8356 cx.assert_editor_state(
8357 &[
8358 "one ", //
8359 "twoˇ", //
8360 "three ", //
8361 "four", //
8362 ]
8363 .join("\n"),
8364 );
8365}
8366
8367#[gpui::test]
8368async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8369 cx: &mut TestAppContext,
8370) {
8371 init_test(cx, |_| {});
8372
8373 cx.update(|cx| {
8374 cx.update_global::<SettingsStore, _>(|settings, cx| {
8375 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8376 settings.auto_signature_help = Some(true);
8377 });
8378 });
8379 });
8380
8381 let mut cx = EditorLspTestContext::new_rust(
8382 lsp::ServerCapabilities {
8383 signature_help_provider: Some(lsp::SignatureHelpOptions {
8384 ..Default::default()
8385 }),
8386 ..Default::default()
8387 },
8388 cx,
8389 )
8390 .await;
8391
8392 let language = Language::new(
8393 LanguageConfig {
8394 name: "Rust".into(),
8395 brackets: BracketPairConfig {
8396 pairs: vec![
8397 BracketPair {
8398 start: "{".to_string(),
8399 end: "}".to_string(),
8400 close: true,
8401 surround: true,
8402 newline: true,
8403 },
8404 BracketPair {
8405 start: "(".to_string(),
8406 end: ")".to_string(),
8407 close: true,
8408 surround: true,
8409 newline: true,
8410 },
8411 BracketPair {
8412 start: "/*".to_string(),
8413 end: " */".to_string(),
8414 close: true,
8415 surround: true,
8416 newline: true,
8417 },
8418 BracketPair {
8419 start: "[".to_string(),
8420 end: "]".to_string(),
8421 close: false,
8422 surround: false,
8423 newline: true,
8424 },
8425 BracketPair {
8426 start: "\"".to_string(),
8427 end: "\"".to_string(),
8428 close: true,
8429 surround: true,
8430 newline: false,
8431 },
8432 BracketPair {
8433 start: "<".to_string(),
8434 end: ">".to_string(),
8435 close: false,
8436 surround: true,
8437 newline: true,
8438 },
8439 ],
8440 ..Default::default()
8441 },
8442 autoclose_before: "})]".to_string(),
8443 ..Default::default()
8444 },
8445 Some(tree_sitter_rust::LANGUAGE.into()),
8446 );
8447 let language = Arc::new(language);
8448
8449 cx.language_registry().add(language.clone());
8450 cx.update_buffer(|buffer, cx| {
8451 buffer.set_language(Some(language), cx);
8452 });
8453
8454 cx.set_state(
8455 &r#"
8456 fn main() {
8457 sampleˇ
8458 }
8459 "#
8460 .unindent(),
8461 );
8462
8463 cx.update_editor(|editor, window, cx| {
8464 editor.handle_input("(", window, cx);
8465 });
8466 cx.assert_editor_state(
8467 &"
8468 fn main() {
8469 sample(ˇ)
8470 }
8471 "
8472 .unindent(),
8473 );
8474
8475 let mocked_response = lsp::SignatureHelp {
8476 signatures: vec![lsp::SignatureInformation {
8477 label: "fn sample(param1: u8, param2: u8)".to_string(),
8478 documentation: None,
8479 parameters: Some(vec![
8480 lsp::ParameterInformation {
8481 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8482 documentation: None,
8483 },
8484 lsp::ParameterInformation {
8485 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8486 documentation: None,
8487 },
8488 ]),
8489 active_parameter: None,
8490 }],
8491 active_signature: Some(0),
8492 active_parameter: Some(0),
8493 };
8494 handle_signature_help_request(&mut cx, mocked_response).await;
8495
8496 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8497 .await;
8498
8499 cx.editor(|editor, _, _| {
8500 let signature_help_state = editor.signature_help_state.popover().cloned();
8501 assert_eq!(
8502 signature_help_state.unwrap().label,
8503 "param1: u8, param2: u8"
8504 );
8505 });
8506}
8507
8508#[gpui::test]
8509async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8510 init_test(cx, |_| {});
8511
8512 cx.update(|cx| {
8513 cx.update_global::<SettingsStore, _>(|settings, cx| {
8514 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8515 settings.auto_signature_help = Some(false);
8516 settings.show_signature_help_after_edits = Some(false);
8517 });
8518 });
8519 });
8520
8521 let mut cx = EditorLspTestContext::new_rust(
8522 lsp::ServerCapabilities {
8523 signature_help_provider: Some(lsp::SignatureHelpOptions {
8524 ..Default::default()
8525 }),
8526 ..Default::default()
8527 },
8528 cx,
8529 )
8530 .await;
8531
8532 let language = Language::new(
8533 LanguageConfig {
8534 name: "Rust".into(),
8535 brackets: BracketPairConfig {
8536 pairs: vec![
8537 BracketPair {
8538 start: "{".to_string(),
8539 end: "}".to_string(),
8540 close: true,
8541 surround: true,
8542 newline: true,
8543 },
8544 BracketPair {
8545 start: "(".to_string(),
8546 end: ")".to_string(),
8547 close: true,
8548 surround: true,
8549 newline: true,
8550 },
8551 BracketPair {
8552 start: "/*".to_string(),
8553 end: " */".to_string(),
8554 close: true,
8555 surround: true,
8556 newline: true,
8557 },
8558 BracketPair {
8559 start: "[".to_string(),
8560 end: "]".to_string(),
8561 close: false,
8562 surround: false,
8563 newline: true,
8564 },
8565 BracketPair {
8566 start: "\"".to_string(),
8567 end: "\"".to_string(),
8568 close: true,
8569 surround: true,
8570 newline: false,
8571 },
8572 BracketPair {
8573 start: "<".to_string(),
8574 end: ">".to_string(),
8575 close: false,
8576 surround: true,
8577 newline: true,
8578 },
8579 ],
8580 ..Default::default()
8581 },
8582 autoclose_before: "})]".to_string(),
8583 ..Default::default()
8584 },
8585 Some(tree_sitter_rust::LANGUAGE.into()),
8586 );
8587 let language = Arc::new(language);
8588
8589 cx.language_registry().add(language.clone());
8590 cx.update_buffer(|buffer, cx| {
8591 buffer.set_language(Some(language), cx);
8592 });
8593
8594 // Ensure that signature_help is not called when no signature help is enabled.
8595 cx.set_state(
8596 &r#"
8597 fn main() {
8598 sampleˇ
8599 }
8600 "#
8601 .unindent(),
8602 );
8603 cx.update_editor(|editor, window, cx| {
8604 editor.handle_input("(", window, cx);
8605 });
8606 cx.assert_editor_state(
8607 &"
8608 fn main() {
8609 sample(ˇ)
8610 }
8611 "
8612 .unindent(),
8613 );
8614 cx.editor(|editor, _, _| {
8615 assert!(editor.signature_help_state.task().is_none());
8616 });
8617
8618 let mocked_response = lsp::SignatureHelp {
8619 signatures: vec![lsp::SignatureInformation {
8620 label: "fn sample(param1: u8, param2: u8)".to_string(),
8621 documentation: None,
8622 parameters: Some(vec![
8623 lsp::ParameterInformation {
8624 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8625 documentation: None,
8626 },
8627 lsp::ParameterInformation {
8628 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8629 documentation: None,
8630 },
8631 ]),
8632 active_parameter: None,
8633 }],
8634 active_signature: Some(0),
8635 active_parameter: Some(0),
8636 };
8637
8638 // Ensure that signature_help is called when enabled afte edits
8639 cx.update(|_, cx| {
8640 cx.update_global::<SettingsStore, _>(|settings, cx| {
8641 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8642 settings.auto_signature_help = Some(false);
8643 settings.show_signature_help_after_edits = Some(true);
8644 });
8645 });
8646 });
8647 cx.set_state(
8648 &r#"
8649 fn main() {
8650 sampleˇ
8651 }
8652 "#
8653 .unindent(),
8654 );
8655 cx.update_editor(|editor, window, cx| {
8656 editor.handle_input("(", window, cx);
8657 });
8658 cx.assert_editor_state(
8659 &"
8660 fn main() {
8661 sample(ˇ)
8662 }
8663 "
8664 .unindent(),
8665 );
8666 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8667 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8668 .await;
8669 cx.update_editor(|editor, _, _| {
8670 let signature_help_state = editor.signature_help_state.popover().cloned();
8671 assert!(signature_help_state.is_some());
8672 assert_eq!(
8673 signature_help_state.unwrap().label,
8674 "param1: u8, param2: u8"
8675 );
8676 editor.signature_help_state = SignatureHelpState::default();
8677 });
8678
8679 // Ensure that signature_help is called when auto signature help override is enabled
8680 cx.update(|_, cx| {
8681 cx.update_global::<SettingsStore, _>(|settings, cx| {
8682 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8683 settings.auto_signature_help = Some(true);
8684 settings.show_signature_help_after_edits = Some(false);
8685 });
8686 });
8687 });
8688 cx.set_state(
8689 &r#"
8690 fn main() {
8691 sampleˇ
8692 }
8693 "#
8694 .unindent(),
8695 );
8696 cx.update_editor(|editor, window, cx| {
8697 editor.handle_input("(", window, cx);
8698 });
8699 cx.assert_editor_state(
8700 &"
8701 fn main() {
8702 sample(ˇ)
8703 }
8704 "
8705 .unindent(),
8706 );
8707 handle_signature_help_request(&mut cx, mocked_response).await;
8708 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8709 .await;
8710 cx.editor(|editor, _, _| {
8711 let signature_help_state = editor.signature_help_state.popover().cloned();
8712 assert!(signature_help_state.is_some());
8713 assert_eq!(
8714 signature_help_state.unwrap().label,
8715 "param1: u8, param2: u8"
8716 );
8717 });
8718}
8719
8720#[gpui::test]
8721async fn test_signature_help(cx: &mut TestAppContext) {
8722 init_test(cx, |_| {});
8723 cx.update(|cx| {
8724 cx.update_global::<SettingsStore, _>(|settings, cx| {
8725 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8726 settings.auto_signature_help = Some(true);
8727 });
8728 });
8729 });
8730
8731 let mut cx = EditorLspTestContext::new_rust(
8732 lsp::ServerCapabilities {
8733 signature_help_provider: Some(lsp::SignatureHelpOptions {
8734 ..Default::default()
8735 }),
8736 ..Default::default()
8737 },
8738 cx,
8739 )
8740 .await;
8741
8742 // A test that directly calls `show_signature_help`
8743 cx.update_editor(|editor, window, cx| {
8744 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8745 });
8746
8747 let mocked_response = lsp::SignatureHelp {
8748 signatures: vec![lsp::SignatureInformation {
8749 label: "fn sample(param1: u8, param2: u8)".to_string(),
8750 documentation: None,
8751 parameters: Some(vec![
8752 lsp::ParameterInformation {
8753 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8754 documentation: None,
8755 },
8756 lsp::ParameterInformation {
8757 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8758 documentation: None,
8759 },
8760 ]),
8761 active_parameter: None,
8762 }],
8763 active_signature: Some(0),
8764 active_parameter: Some(0),
8765 };
8766 handle_signature_help_request(&mut cx, mocked_response).await;
8767
8768 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8769 .await;
8770
8771 cx.editor(|editor, _, _| {
8772 let signature_help_state = editor.signature_help_state.popover().cloned();
8773 assert!(signature_help_state.is_some());
8774 assert_eq!(
8775 signature_help_state.unwrap().label,
8776 "param1: u8, param2: u8"
8777 );
8778 });
8779
8780 // When exiting outside from inside the brackets, `signature_help` is closed.
8781 cx.set_state(indoc! {"
8782 fn main() {
8783 sample(ˇ);
8784 }
8785
8786 fn sample(param1: u8, param2: u8) {}
8787 "});
8788
8789 cx.update_editor(|editor, window, cx| {
8790 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8791 });
8792
8793 let mocked_response = lsp::SignatureHelp {
8794 signatures: Vec::new(),
8795 active_signature: None,
8796 active_parameter: None,
8797 };
8798 handle_signature_help_request(&mut cx, mocked_response).await;
8799
8800 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8801 .await;
8802
8803 cx.editor(|editor, _, _| {
8804 assert!(!editor.signature_help_state.is_shown());
8805 });
8806
8807 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8808 cx.set_state(indoc! {"
8809 fn main() {
8810 sample(ˇ);
8811 }
8812
8813 fn sample(param1: u8, param2: u8) {}
8814 "});
8815
8816 let mocked_response = lsp::SignatureHelp {
8817 signatures: vec![lsp::SignatureInformation {
8818 label: "fn sample(param1: u8, param2: u8)".to_string(),
8819 documentation: None,
8820 parameters: Some(vec![
8821 lsp::ParameterInformation {
8822 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8823 documentation: None,
8824 },
8825 lsp::ParameterInformation {
8826 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8827 documentation: None,
8828 },
8829 ]),
8830 active_parameter: None,
8831 }],
8832 active_signature: Some(0),
8833 active_parameter: Some(0),
8834 };
8835 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8836 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8837 .await;
8838 cx.editor(|editor, _, _| {
8839 assert!(editor.signature_help_state.is_shown());
8840 });
8841
8842 // Restore the popover with more parameter input
8843 cx.set_state(indoc! {"
8844 fn main() {
8845 sample(param1, param2ˇ);
8846 }
8847
8848 fn sample(param1: u8, param2: u8) {}
8849 "});
8850
8851 let mocked_response = lsp::SignatureHelp {
8852 signatures: vec![lsp::SignatureInformation {
8853 label: "fn sample(param1: u8, param2: u8)".to_string(),
8854 documentation: None,
8855 parameters: Some(vec![
8856 lsp::ParameterInformation {
8857 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8858 documentation: None,
8859 },
8860 lsp::ParameterInformation {
8861 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8862 documentation: None,
8863 },
8864 ]),
8865 active_parameter: None,
8866 }],
8867 active_signature: Some(0),
8868 active_parameter: Some(1),
8869 };
8870 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8871 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8872 .await;
8873
8874 // When selecting a range, the popover is gone.
8875 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8876 cx.update_editor(|editor, window, cx| {
8877 editor.change_selections(None, window, cx, |s| {
8878 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8879 })
8880 });
8881 cx.assert_editor_state(indoc! {"
8882 fn main() {
8883 sample(param1, «ˇparam2»);
8884 }
8885
8886 fn sample(param1: u8, param2: u8) {}
8887 "});
8888 cx.editor(|editor, _, _| {
8889 assert!(!editor.signature_help_state.is_shown());
8890 });
8891
8892 // When unselecting again, the popover is back if within the brackets.
8893 cx.update_editor(|editor, window, cx| {
8894 editor.change_selections(None, window, cx, |s| {
8895 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8896 })
8897 });
8898 cx.assert_editor_state(indoc! {"
8899 fn main() {
8900 sample(param1, ˇparam2);
8901 }
8902
8903 fn sample(param1: u8, param2: u8) {}
8904 "});
8905 handle_signature_help_request(&mut cx, mocked_response).await;
8906 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8907 .await;
8908 cx.editor(|editor, _, _| {
8909 assert!(editor.signature_help_state.is_shown());
8910 });
8911
8912 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8913 cx.update_editor(|editor, window, cx| {
8914 editor.change_selections(None, window, cx, |s| {
8915 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8916 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8917 })
8918 });
8919 cx.assert_editor_state(indoc! {"
8920 fn main() {
8921 sample(param1, ˇparam2);
8922 }
8923
8924 fn sample(param1: u8, param2: u8) {}
8925 "});
8926
8927 let mocked_response = lsp::SignatureHelp {
8928 signatures: vec![lsp::SignatureInformation {
8929 label: "fn sample(param1: u8, param2: u8)".to_string(),
8930 documentation: None,
8931 parameters: Some(vec![
8932 lsp::ParameterInformation {
8933 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8934 documentation: None,
8935 },
8936 lsp::ParameterInformation {
8937 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8938 documentation: None,
8939 },
8940 ]),
8941 active_parameter: None,
8942 }],
8943 active_signature: Some(0),
8944 active_parameter: Some(1),
8945 };
8946 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8947 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8948 .await;
8949 cx.update_editor(|editor, _, cx| {
8950 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8951 });
8952 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8953 .await;
8954 cx.update_editor(|editor, window, cx| {
8955 editor.change_selections(None, window, cx, |s| {
8956 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8957 })
8958 });
8959 cx.assert_editor_state(indoc! {"
8960 fn main() {
8961 sample(param1, «ˇparam2»);
8962 }
8963
8964 fn sample(param1: u8, param2: u8) {}
8965 "});
8966 cx.update_editor(|editor, window, cx| {
8967 editor.change_selections(None, window, cx, |s| {
8968 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8969 })
8970 });
8971 cx.assert_editor_state(indoc! {"
8972 fn main() {
8973 sample(param1, ˇparam2);
8974 }
8975
8976 fn sample(param1: u8, param2: u8) {}
8977 "});
8978 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8979 .await;
8980}
8981
8982#[gpui::test]
8983async fn test_completion(cx: &mut TestAppContext) {
8984 init_test(cx, |_| {});
8985
8986 let mut cx = EditorLspTestContext::new_rust(
8987 lsp::ServerCapabilities {
8988 completion_provider: Some(lsp::CompletionOptions {
8989 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8990 resolve_provider: Some(true),
8991 ..Default::default()
8992 }),
8993 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8994 ..Default::default()
8995 },
8996 cx,
8997 )
8998 .await;
8999 let counter = Arc::new(AtomicUsize::new(0));
9000
9001 cx.set_state(indoc! {"
9002 oneˇ
9003 two
9004 three
9005 "});
9006 cx.simulate_keystroke(".");
9007 handle_completion_request(
9008 &mut cx,
9009 indoc! {"
9010 one.|<>
9011 two
9012 three
9013 "},
9014 vec!["first_completion", "second_completion"],
9015 counter.clone(),
9016 )
9017 .await;
9018 cx.condition(|editor, _| editor.context_menu_visible())
9019 .await;
9020 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9021
9022 let _handler = handle_signature_help_request(
9023 &mut cx,
9024 lsp::SignatureHelp {
9025 signatures: vec![lsp::SignatureInformation {
9026 label: "test signature".to_string(),
9027 documentation: None,
9028 parameters: Some(vec![lsp::ParameterInformation {
9029 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9030 documentation: None,
9031 }]),
9032 active_parameter: None,
9033 }],
9034 active_signature: None,
9035 active_parameter: None,
9036 },
9037 );
9038 cx.update_editor(|editor, window, cx| {
9039 assert!(
9040 !editor.signature_help_state.is_shown(),
9041 "No signature help was called for"
9042 );
9043 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9044 });
9045 cx.run_until_parked();
9046 cx.update_editor(|editor, _, _| {
9047 assert!(
9048 !editor.signature_help_state.is_shown(),
9049 "No signature help should be shown when completions menu is open"
9050 );
9051 });
9052
9053 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9054 editor.context_menu_next(&Default::default(), window, cx);
9055 editor
9056 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9057 .unwrap()
9058 });
9059 cx.assert_editor_state(indoc! {"
9060 one.second_completionˇ
9061 two
9062 three
9063 "});
9064
9065 handle_resolve_completion_request(
9066 &mut cx,
9067 Some(vec![
9068 (
9069 //This overlaps with the primary completion edit which is
9070 //misbehavior from the LSP spec, test that we filter it out
9071 indoc! {"
9072 one.second_ˇcompletion
9073 two
9074 threeˇ
9075 "},
9076 "overlapping additional edit",
9077 ),
9078 (
9079 indoc! {"
9080 one.second_completion
9081 two
9082 threeˇ
9083 "},
9084 "\nadditional edit",
9085 ),
9086 ]),
9087 )
9088 .await;
9089 apply_additional_edits.await.unwrap();
9090 cx.assert_editor_state(indoc! {"
9091 one.second_completionˇ
9092 two
9093 three
9094 additional edit
9095 "});
9096
9097 cx.set_state(indoc! {"
9098 one.second_completion
9099 twoˇ
9100 threeˇ
9101 additional edit
9102 "});
9103 cx.simulate_keystroke(" ");
9104 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9105 cx.simulate_keystroke("s");
9106 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9107
9108 cx.assert_editor_state(indoc! {"
9109 one.second_completion
9110 two sˇ
9111 three sˇ
9112 additional edit
9113 "});
9114 handle_completion_request(
9115 &mut cx,
9116 indoc! {"
9117 one.second_completion
9118 two s
9119 three <s|>
9120 additional edit
9121 "},
9122 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9123 counter.clone(),
9124 )
9125 .await;
9126 cx.condition(|editor, _| editor.context_menu_visible())
9127 .await;
9128 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9129
9130 cx.simulate_keystroke("i");
9131
9132 handle_completion_request(
9133 &mut cx,
9134 indoc! {"
9135 one.second_completion
9136 two si
9137 three <si|>
9138 additional edit
9139 "},
9140 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9141 counter.clone(),
9142 )
9143 .await;
9144 cx.condition(|editor, _| editor.context_menu_visible())
9145 .await;
9146 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9147
9148 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9149 editor
9150 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9151 .unwrap()
9152 });
9153 cx.assert_editor_state(indoc! {"
9154 one.second_completion
9155 two sixth_completionˇ
9156 three sixth_completionˇ
9157 additional edit
9158 "});
9159
9160 apply_additional_edits.await.unwrap();
9161
9162 update_test_language_settings(&mut cx, |settings| {
9163 settings.defaults.show_completions_on_input = Some(false);
9164 });
9165 cx.set_state("editorˇ");
9166 cx.simulate_keystroke(".");
9167 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9168 cx.simulate_keystroke("c");
9169 cx.simulate_keystroke("l");
9170 cx.simulate_keystroke("o");
9171 cx.assert_editor_state("editor.cloˇ");
9172 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9173 cx.update_editor(|editor, window, cx| {
9174 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9175 });
9176 handle_completion_request(
9177 &mut cx,
9178 "editor.<clo|>",
9179 vec!["close", "clobber"],
9180 counter.clone(),
9181 )
9182 .await;
9183 cx.condition(|editor, _| editor.context_menu_visible())
9184 .await;
9185 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9186
9187 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9188 editor
9189 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9190 .unwrap()
9191 });
9192 cx.assert_editor_state("editor.closeˇ");
9193 handle_resolve_completion_request(&mut cx, None).await;
9194 apply_additional_edits.await.unwrap();
9195}
9196
9197#[gpui::test]
9198async fn test_multiline_completion(cx: &mut TestAppContext) {
9199 init_test(cx, |_| {});
9200
9201 let fs = FakeFs::new(cx.executor());
9202 fs.insert_tree(
9203 path!("/a"),
9204 json!({
9205 "main.ts": "a",
9206 }),
9207 )
9208 .await;
9209
9210 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9211 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9212 let typescript_language = Arc::new(Language::new(
9213 LanguageConfig {
9214 name: "TypeScript".into(),
9215 matcher: LanguageMatcher {
9216 path_suffixes: vec!["ts".to_string()],
9217 ..LanguageMatcher::default()
9218 },
9219 line_comments: vec!["// ".into()],
9220 ..LanguageConfig::default()
9221 },
9222 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9223 ));
9224 language_registry.add(typescript_language.clone());
9225 let mut fake_servers = language_registry.register_fake_lsp(
9226 "TypeScript",
9227 FakeLspAdapter {
9228 capabilities: lsp::ServerCapabilities {
9229 completion_provider: Some(lsp::CompletionOptions {
9230 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9231 ..lsp::CompletionOptions::default()
9232 }),
9233 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9234 ..lsp::ServerCapabilities::default()
9235 },
9236 // Emulate vtsls label generation
9237 label_for_completion: Some(Box::new(|item, _| {
9238 let text = if let Some(description) = item
9239 .label_details
9240 .as_ref()
9241 .and_then(|label_details| label_details.description.as_ref())
9242 {
9243 format!("{} {}", item.label, description)
9244 } else if let Some(detail) = &item.detail {
9245 format!("{} {}", item.label, detail)
9246 } else {
9247 item.label.clone()
9248 };
9249 let len = text.len();
9250 Some(language::CodeLabel {
9251 text,
9252 runs: Vec::new(),
9253 filter_range: 0..len,
9254 })
9255 })),
9256 ..FakeLspAdapter::default()
9257 },
9258 );
9259 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9260 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9261 let worktree_id = workspace
9262 .update(cx, |workspace, _window, cx| {
9263 workspace.project().update(cx, |project, cx| {
9264 project.worktrees(cx).next().unwrap().read(cx).id()
9265 })
9266 })
9267 .unwrap();
9268 let _buffer = project
9269 .update(cx, |project, cx| {
9270 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9271 })
9272 .await
9273 .unwrap();
9274 let editor = workspace
9275 .update(cx, |workspace, window, cx| {
9276 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9277 })
9278 .unwrap()
9279 .await
9280 .unwrap()
9281 .downcast::<Editor>()
9282 .unwrap();
9283 let fake_server = fake_servers.next().await.unwrap();
9284
9285 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9286 let multiline_label_2 = "a\nb\nc\n";
9287 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9288 let multiline_description = "d\ne\nf\n";
9289 let multiline_detail_2 = "g\nh\ni\n";
9290
9291 let mut completion_handle =
9292 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9293 Ok(Some(lsp::CompletionResponse::Array(vec![
9294 lsp::CompletionItem {
9295 label: multiline_label.to_string(),
9296 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9297 range: lsp::Range {
9298 start: lsp::Position {
9299 line: params.text_document_position.position.line,
9300 character: params.text_document_position.position.character,
9301 },
9302 end: lsp::Position {
9303 line: params.text_document_position.position.line,
9304 character: params.text_document_position.position.character,
9305 },
9306 },
9307 new_text: "new_text_1".to_string(),
9308 })),
9309 ..lsp::CompletionItem::default()
9310 },
9311 lsp::CompletionItem {
9312 label: "single line label 1".to_string(),
9313 detail: Some(multiline_detail.to_string()),
9314 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9315 range: lsp::Range {
9316 start: lsp::Position {
9317 line: params.text_document_position.position.line,
9318 character: params.text_document_position.position.character,
9319 },
9320 end: lsp::Position {
9321 line: params.text_document_position.position.line,
9322 character: params.text_document_position.position.character,
9323 },
9324 },
9325 new_text: "new_text_2".to_string(),
9326 })),
9327 ..lsp::CompletionItem::default()
9328 },
9329 lsp::CompletionItem {
9330 label: "single line label 2".to_string(),
9331 label_details: Some(lsp::CompletionItemLabelDetails {
9332 description: Some(multiline_description.to_string()),
9333 detail: None,
9334 }),
9335 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9336 range: lsp::Range {
9337 start: lsp::Position {
9338 line: params.text_document_position.position.line,
9339 character: params.text_document_position.position.character,
9340 },
9341 end: lsp::Position {
9342 line: params.text_document_position.position.line,
9343 character: params.text_document_position.position.character,
9344 },
9345 },
9346 new_text: "new_text_2".to_string(),
9347 })),
9348 ..lsp::CompletionItem::default()
9349 },
9350 lsp::CompletionItem {
9351 label: multiline_label_2.to_string(),
9352 detail: Some(multiline_detail_2.to_string()),
9353 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9354 range: lsp::Range {
9355 start: lsp::Position {
9356 line: params.text_document_position.position.line,
9357 character: params.text_document_position.position.character,
9358 },
9359 end: lsp::Position {
9360 line: params.text_document_position.position.line,
9361 character: params.text_document_position.position.character,
9362 },
9363 },
9364 new_text: "new_text_3".to_string(),
9365 })),
9366 ..lsp::CompletionItem::default()
9367 },
9368 lsp::CompletionItem {
9369 label: "Label with many spaces and \t but without newlines".to_string(),
9370 detail: Some(
9371 "Details with many spaces and \t but without newlines".to_string(),
9372 ),
9373 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9374 range: lsp::Range {
9375 start: lsp::Position {
9376 line: params.text_document_position.position.line,
9377 character: params.text_document_position.position.character,
9378 },
9379 end: lsp::Position {
9380 line: params.text_document_position.position.line,
9381 character: params.text_document_position.position.character,
9382 },
9383 },
9384 new_text: "new_text_4".to_string(),
9385 })),
9386 ..lsp::CompletionItem::default()
9387 },
9388 ])))
9389 });
9390
9391 editor.update_in(cx, |editor, window, cx| {
9392 cx.focus_self(window);
9393 editor.move_to_end(&MoveToEnd, window, cx);
9394 editor.handle_input(".", window, cx);
9395 });
9396 cx.run_until_parked();
9397 completion_handle.next().await.unwrap();
9398
9399 editor.update(cx, |editor, _| {
9400 assert!(editor.context_menu_visible());
9401 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9402 {
9403 let completion_labels = menu
9404 .completions
9405 .borrow()
9406 .iter()
9407 .map(|c| c.label.text.clone())
9408 .collect::<Vec<_>>();
9409 assert_eq!(
9410 completion_labels,
9411 &[
9412 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9413 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9414 "single line label 2 d e f ",
9415 "a b c g h i ",
9416 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9417 ],
9418 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9419 );
9420
9421 for completion in menu
9422 .completions
9423 .borrow()
9424 .iter() {
9425 assert_eq!(
9426 completion.label.filter_range,
9427 0..completion.label.text.len(),
9428 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9429 );
9430 }
9431
9432 } else {
9433 panic!("expected completion menu to be open");
9434 }
9435 });
9436}
9437
9438#[gpui::test]
9439async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9440 init_test(cx, |_| {});
9441 let mut cx = EditorLspTestContext::new_rust(
9442 lsp::ServerCapabilities {
9443 completion_provider: Some(lsp::CompletionOptions {
9444 trigger_characters: Some(vec![".".to_string()]),
9445 ..Default::default()
9446 }),
9447 ..Default::default()
9448 },
9449 cx,
9450 )
9451 .await;
9452 cx.lsp
9453 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9454 Ok(Some(lsp::CompletionResponse::Array(vec![
9455 lsp::CompletionItem {
9456 label: "first".into(),
9457 ..Default::default()
9458 },
9459 lsp::CompletionItem {
9460 label: "last".into(),
9461 ..Default::default()
9462 },
9463 ])))
9464 });
9465 cx.set_state("variableˇ");
9466 cx.simulate_keystroke(".");
9467 cx.executor().run_until_parked();
9468
9469 cx.update_editor(|editor, _, _| {
9470 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9471 {
9472 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9473 } else {
9474 panic!("expected completion menu to be open");
9475 }
9476 });
9477
9478 cx.update_editor(|editor, window, cx| {
9479 editor.move_page_down(&MovePageDown::default(), window, cx);
9480 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9481 {
9482 assert!(
9483 menu.selected_item == 1,
9484 "expected PageDown to select the last item from the context menu"
9485 );
9486 } else {
9487 panic!("expected completion menu to stay open after PageDown");
9488 }
9489 });
9490
9491 cx.update_editor(|editor, window, cx| {
9492 editor.move_page_up(&MovePageUp::default(), window, cx);
9493 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9494 {
9495 assert!(
9496 menu.selected_item == 0,
9497 "expected PageUp to select the first item from the context menu"
9498 );
9499 } else {
9500 panic!("expected completion menu to stay open after PageUp");
9501 }
9502 });
9503}
9504
9505#[gpui::test]
9506async fn test_completion_sort(cx: &mut TestAppContext) {
9507 init_test(cx, |_| {});
9508 let mut cx = EditorLspTestContext::new_rust(
9509 lsp::ServerCapabilities {
9510 completion_provider: Some(lsp::CompletionOptions {
9511 trigger_characters: Some(vec![".".to_string()]),
9512 ..Default::default()
9513 }),
9514 ..Default::default()
9515 },
9516 cx,
9517 )
9518 .await;
9519 cx.lsp
9520 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9521 Ok(Some(lsp::CompletionResponse::Array(vec![
9522 lsp::CompletionItem {
9523 label: "Range".into(),
9524 sort_text: Some("a".into()),
9525 ..Default::default()
9526 },
9527 lsp::CompletionItem {
9528 label: "r".into(),
9529 sort_text: Some("b".into()),
9530 ..Default::default()
9531 },
9532 lsp::CompletionItem {
9533 label: "ret".into(),
9534 sort_text: Some("c".into()),
9535 ..Default::default()
9536 },
9537 lsp::CompletionItem {
9538 label: "return".into(),
9539 sort_text: Some("d".into()),
9540 ..Default::default()
9541 },
9542 lsp::CompletionItem {
9543 label: "slice".into(),
9544 sort_text: Some("d".into()),
9545 ..Default::default()
9546 },
9547 ])))
9548 });
9549 cx.set_state("rˇ");
9550 cx.executor().run_until_parked();
9551 cx.update_editor(|editor, window, cx| {
9552 editor.show_completions(
9553 &ShowCompletions {
9554 trigger: Some("r".into()),
9555 },
9556 window,
9557 cx,
9558 );
9559 });
9560 cx.executor().run_until_parked();
9561
9562 cx.update_editor(|editor, _, _| {
9563 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9564 {
9565 assert_eq!(
9566 completion_menu_entries(&menu),
9567 &["r", "ret", "Range", "return"]
9568 );
9569 } else {
9570 panic!("expected completion menu to be open");
9571 }
9572 });
9573}
9574
9575#[gpui::test]
9576async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9577 init_test(cx, |_| {});
9578
9579 let mut cx = EditorLspTestContext::new_rust(
9580 lsp::ServerCapabilities {
9581 completion_provider: Some(lsp::CompletionOptions {
9582 trigger_characters: Some(vec![".".to_string()]),
9583 resolve_provider: Some(true),
9584 ..Default::default()
9585 }),
9586 ..Default::default()
9587 },
9588 cx,
9589 )
9590 .await;
9591
9592 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9593 cx.simulate_keystroke(".");
9594 let completion_item = lsp::CompletionItem {
9595 label: "Some".into(),
9596 kind: Some(lsp::CompletionItemKind::SNIPPET),
9597 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9598 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9599 kind: lsp::MarkupKind::Markdown,
9600 value: "```rust\nSome(2)\n```".to_string(),
9601 })),
9602 deprecated: Some(false),
9603 sort_text: Some("Some".to_string()),
9604 filter_text: Some("Some".to_string()),
9605 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9606 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9607 range: lsp::Range {
9608 start: lsp::Position {
9609 line: 0,
9610 character: 22,
9611 },
9612 end: lsp::Position {
9613 line: 0,
9614 character: 22,
9615 },
9616 },
9617 new_text: "Some(2)".to_string(),
9618 })),
9619 additional_text_edits: Some(vec![lsp::TextEdit {
9620 range: lsp::Range {
9621 start: lsp::Position {
9622 line: 0,
9623 character: 20,
9624 },
9625 end: lsp::Position {
9626 line: 0,
9627 character: 22,
9628 },
9629 },
9630 new_text: "".to_string(),
9631 }]),
9632 ..Default::default()
9633 };
9634
9635 let closure_completion_item = completion_item.clone();
9636 let counter = Arc::new(AtomicUsize::new(0));
9637 let counter_clone = counter.clone();
9638 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9639 let task_completion_item = closure_completion_item.clone();
9640 counter_clone.fetch_add(1, atomic::Ordering::Release);
9641 async move {
9642 Ok(Some(lsp::CompletionResponse::Array(vec![
9643 task_completion_item,
9644 ])))
9645 }
9646 });
9647
9648 cx.condition(|editor, _| editor.context_menu_visible())
9649 .await;
9650 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9651 assert!(request.next().await.is_some());
9652 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9653
9654 cx.simulate_keystroke("S");
9655 cx.simulate_keystroke("o");
9656 cx.simulate_keystroke("m");
9657 cx.condition(|editor, _| editor.context_menu_visible())
9658 .await;
9659 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9660 assert!(request.next().await.is_some());
9661 assert!(request.next().await.is_some());
9662 assert!(request.next().await.is_some());
9663 request.close();
9664 assert!(request.next().await.is_none());
9665 assert_eq!(
9666 counter.load(atomic::Ordering::Acquire),
9667 4,
9668 "With the completions menu open, only one LSP request should happen per input"
9669 );
9670}
9671
9672#[gpui::test]
9673async fn test_toggle_comment(cx: &mut TestAppContext) {
9674 init_test(cx, |_| {});
9675 let mut cx = EditorTestContext::new(cx).await;
9676 let language = Arc::new(Language::new(
9677 LanguageConfig {
9678 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9679 ..Default::default()
9680 },
9681 Some(tree_sitter_rust::LANGUAGE.into()),
9682 ));
9683 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9684
9685 // If multiple selections intersect a line, the line is only toggled once.
9686 cx.set_state(indoc! {"
9687 fn a() {
9688 «//b();
9689 ˇ»// «c();
9690 //ˇ» d();
9691 }
9692 "});
9693
9694 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9695
9696 cx.assert_editor_state(indoc! {"
9697 fn a() {
9698 «b();
9699 c();
9700 ˇ» d();
9701 }
9702 "});
9703
9704 // The comment prefix is inserted at the same column for every line in a
9705 // selection.
9706 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9707
9708 cx.assert_editor_state(indoc! {"
9709 fn a() {
9710 // «b();
9711 // c();
9712 ˇ»// d();
9713 }
9714 "});
9715
9716 // If a selection ends at the beginning of a line, that line is not toggled.
9717 cx.set_selections_state(indoc! {"
9718 fn a() {
9719 // b();
9720 «// c();
9721 ˇ» // d();
9722 }
9723 "});
9724
9725 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9726
9727 cx.assert_editor_state(indoc! {"
9728 fn a() {
9729 // b();
9730 «c();
9731 ˇ» // d();
9732 }
9733 "});
9734
9735 // If a selection span a single line and is empty, the line is toggled.
9736 cx.set_state(indoc! {"
9737 fn a() {
9738 a();
9739 b();
9740 ˇ
9741 }
9742 "});
9743
9744 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9745
9746 cx.assert_editor_state(indoc! {"
9747 fn a() {
9748 a();
9749 b();
9750 //•ˇ
9751 }
9752 "});
9753
9754 // If a selection span multiple lines, empty lines are not toggled.
9755 cx.set_state(indoc! {"
9756 fn a() {
9757 «a();
9758
9759 c();ˇ»
9760 }
9761 "});
9762
9763 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9764
9765 cx.assert_editor_state(indoc! {"
9766 fn a() {
9767 // «a();
9768
9769 // c();ˇ»
9770 }
9771 "});
9772
9773 // If a selection includes multiple comment prefixes, all lines are uncommented.
9774 cx.set_state(indoc! {"
9775 fn a() {
9776 «// a();
9777 /// b();
9778 //! c();ˇ»
9779 }
9780 "});
9781
9782 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9783
9784 cx.assert_editor_state(indoc! {"
9785 fn a() {
9786 «a();
9787 b();
9788 c();ˇ»
9789 }
9790 "});
9791}
9792
9793#[gpui::test]
9794async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9795 init_test(cx, |_| {});
9796 let mut cx = EditorTestContext::new(cx).await;
9797 let language = Arc::new(Language::new(
9798 LanguageConfig {
9799 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9800 ..Default::default()
9801 },
9802 Some(tree_sitter_rust::LANGUAGE.into()),
9803 ));
9804 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9805
9806 let toggle_comments = &ToggleComments {
9807 advance_downwards: false,
9808 ignore_indent: true,
9809 };
9810
9811 // If multiple selections intersect a line, the line is only toggled once.
9812 cx.set_state(indoc! {"
9813 fn a() {
9814 // «b();
9815 // c();
9816 // ˇ» d();
9817 }
9818 "});
9819
9820 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9821
9822 cx.assert_editor_state(indoc! {"
9823 fn a() {
9824 «b();
9825 c();
9826 ˇ» d();
9827 }
9828 "});
9829
9830 // The comment prefix is inserted at the beginning of each line
9831 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9832
9833 cx.assert_editor_state(indoc! {"
9834 fn a() {
9835 // «b();
9836 // c();
9837 // ˇ» d();
9838 }
9839 "});
9840
9841 // If a selection ends at the beginning of a line, that line is not toggled.
9842 cx.set_selections_state(indoc! {"
9843 fn a() {
9844 // b();
9845 // «c();
9846 ˇ»// d();
9847 }
9848 "});
9849
9850 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9851
9852 cx.assert_editor_state(indoc! {"
9853 fn a() {
9854 // b();
9855 «c();
9856 ˇ»// d();
9857 }
9858 "});
9859
9860 // If a selection span a single line and is empty, the line is toggled.
9861 cx.set_state(indoc! {"
9862 fn a() {
9863 a();
9864 b();
9865 ˇ
9866 }
9867 "});
9868
9869 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9870
9871 cx.assert_editor_state(indoc! {"
9872 fn a() {
9873 a();
9874 b();
9875 //ˇ
9876 }
9877 "});
9878
9879 // If a selection span multiple lines, empty lines are not toggled.
9880 cx.set_state(indoc! {"
9881 fn a() {
9882 «a();
9883
9884 c();ˇ»
9885 }
9886 "});
9887
9888 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9889
9890 cx.assert_editor_state(indoc! {"
9891 fn a() {
9892 // «a();
9893
9894 // c();ˇ»
9895 }
9896 "});
9897
9898 // If a selection includes multiple comment prefixes, all lines are uncommented.
9899 cx.set_state(indoc! {"
9900 fn a() {
9901 // «a();
9902 /// b();
9903 //! c();ˇ»
9904 }
9905 "});
9906
9907 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9908
9909 cx.assert_editor_state(indoc! {"
9910 fn a() {
9911 «a();
9912 b();
9913 c();ˇ»
9914 }
9915 "});
9916}
9917
9918#[gpui::test]
9919async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9920 init_test(cx, |_| {});
9921
9922 let language = Arc::new(Language::new(
9923 LanguageConfig {
9924 line_comments: vec!["// ".into()],
9925 ..Default::default()
9926 },
9927 Some(tree_sitter_rust::LANGUAGE.into()),
9928 ));
9929
9930 let mut cx = EditorTestContext::new(cx).await;
9931
9932 cx.language_registry().add(language.clone());
9933 cx.update_buffer(|buffer, cx| {
9934 buffer.set_language(Some(language), cx);
9935 });
9936
9937 let toggle_comments = &ToggleComments {
9938 advance_downwards: true,
9939 ignore_indent: false,
9940 };
9941
9942 // Single cursor on one line -> advance
9943 // Cursor moves horizontally 3 characters as well on non-blank line
9944 cx.set_state(indoc!(
9945 "fn a() {
9946 ˇdog();
9947 cat();
9948 }"
9949 ));
9950 cx.update_editor(|editor, window, cx| {
9951 editor.toggle_comments(toggle_comments, window, cx);
9952 });
9953 cx.assert_editor_state(indoc!(
9954 "fn a() {
9955 // dog();
9956 catˇ();
9957 }"
9958 ));
9959
9960 // Single selection on one line -> don't advance
9961 cx.set_state(indoc!(
9962 "fn a() {
9963 «dog()ˇ»;
9964 cat();
9965 }"
9966 ));
9967 cx.update_editor(|editor, window, cx| {
9968 editor.toggle_comments(toggle_comments, window, cx);
9969 });
9970 cx.assert_editor_state(indoc!(
9971 "fn a() {
9972 // «dog()ˇ»;
9973 cat();
9974 }"
9975 ));
9976
9977 // Multiple cursors on one line -> advance
9978 cx.set_state(indoc!(
9979 "fn a() {
9980 ˇdˇog();
9981 cat();
9982 }"
9983 ));
9984 cx.update_editor(|editor, window, cx| {
9985 editor.toggle_comments(toggle_comments, window, cx);
9986 });
9987 cx.assert_editor_state(indoc!(
9988 "fn a() {
9989 // dog();
9990 catˇ(ˇ);
9991 }"
9992 ));
9993
9994 // Multiple cursors on one line, with selection -> don't advance
9995 cx.set_state(indoc!(
9996 "fn a() {
9997 ˇdˇog«()ˇ»;
9998 cat();
9999 }"
10000 ));
10001 cx.update_editor(|editor, window, cx| {
10002 editor.toggle_comments(toggle_comments, window, cx);
10003 });
10004 cx.assert_editor_state(indoc!(
10005 "fn a() {
10006 // ˇdˇog«()ˇ»;
10007 cat();
10008 }"
10009 ));
10010
10011 // Single cursor on one line -> advance
10012 // Cursor moves to column 0 on blank line
10013 cx.set_state(indoc!(
10014 "fn a() {
10015 ˇdog();
10016
10017 cat();
10018 }"
10019 ));
10020 cx.update_editor(|editor, window, cx| {
10021 editor.toggle_comments(toggle_comments, window, cx);
10022 });
10023 cx.assert_editor_state(indoc!(
10024 "fn a() {
10025 // dog();
10026 ˇ
10027 cat();
10028 }"
10029 ));
10030
10031 // Single cursor on one line -> advance
10032 // Cursor starts and ends at column 0
10033 cx.set_state(indoc!(
10034 "fn a() {
10035 ˇ dog();
10036 cat();
10037 }"
10038 ));
10039 cx.update_editor(|editor, window, cx| {
10040 editor.toggle_comments(toggle_comments, window, cx);
10041 });
10042 cx.assert_editor_state(indoc!(
10043 "fn a() {
10044 // dog();
10045 ˇ cat();
10046 }"
10047 ));
10048}
10049
10050#[gpui::test]
10051async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10052 init_test(cx, |_| {});
10053
10054 let mut cx = EditorTestContext::new(cx).await;
10055
10056 let html_language = Arc::new(
10057 Language::new(
10058 LanguageConfig {
10059 name: "HTML".into(),
10060 block_comment: Some(("<!-- ".into(), " -->".into())),
10061 ..Default::default()
10062 },
10063 Some(tree_sitter_html::LANGUAGE.into()),
10064 )
10065 .with_injection_query(
10066 r#"
10067 (script_element
10068 (raw_text) @injection.content
10069 (#set! injection.language "javascript"))
10070 "#,
10071 )
10072 .unwrap(),
10073 );
10074
10075 let javascript_language = Arc::new(Language::new(
10076 LanguageConfig {
10077 name: "JavaScript".into(),
10078 line_comments: vec!["// ".into()],
10079 ..Default::default()
10080 },
10081 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10082 ));
10083
10084 cx.language_registry().add(html_language.clone());
10085 cx.language_registry().add(javascript_language.clone());
10086 cx.update_buffer(|buffer, cx| {
10087 buffer.set_language(Some(html_language), cx);
10088 });
10089
10090 // Toggle comments for empty selections
10091 cx.set_state(
10092 &r#"
10093 <p>A</p>ˇ
10094 <p>B</p>ˇ
10095 <p>C</p>ˇ
10096 "#
10097 .unindent(),
10098 );
10099 cx.update_editor(|editor, window, cx| {
10100 editor.toggle_comments(&ToggleComments::default(), window, cx)
10101 });
10102 cx.assert_editor_state(
10103 &r#"
10104 <!-- <p>A</p>ˇ -->
10105 <!-- <p>B</p>ˇ -->
10106 <!-- <p>C</p>ˇ -->
10107 "#
10108 .unindent(),
10109 );
10110 cx.update_editor(|editor, window, cx| {
10111 editor.toggle_comments(&ToggleComments::default(), window, cx)
10112 });
10113 cx.assert_editor_state(
10114 &r#"
10115 <p>A</p>ˇ
10116 <p>B</p>ˇ
10117 <p>C</p>ˇ
10118 "#
10119 .unindent(),
10120 );
10121
10122 // Toggle comments for mixture of empty and non-empty selections, where
10123 // multiple selections occupy a given line.
10124 cx.set_state(
10125 &r#"
10126 <p>A«</p>
10127 <p>ˇ»B</p>ˇ
10128 <p>C«</p>
10129 <p>ˇ»D</p>ˇ
10130 "#
10131 .unindent(),
10132 );
10133
10134 cx.update_editor(|editor, window, cx| {
10135 editor.toggle_comments(&ToggleComments::default(), window, cx)
10136 });
10137 cx.assert_editor_state(
10138 &r#"
10139 <!-- <p>A«</p>
10140 <p>ˇ»B</p>ˇ -->
10141 <!-- <p>C«</p>
10142 <p>ˇ»D</p>ˇ -->
10143 "#
10144 .unindent(),
10145 );
10146 cx.update_editor(|editor, window, cx| {
10147 editor.toggle_comments(&ToggleComments::default(), window, cx)
10148 });
10149 cx.assert_editor_state(
10150 &r#"
10151 <p>A«</p>
10152 <p>ˇ»B</p>ˇ
10153 <p>C«</p>
10154 <p>ˇ»D</p>ˇ
10155 "#
10156 .unindent(),
10157 );
10158
10159 // Toggle comments when different languages are active for different
10160 // selections.
10161 cx.set_state(
10162 &r#"
10163 ˇ<script>
10164 ˇvar x = new Y();
10165 ˇ</script>
10166 "#
10167 .unindent(),
10168 );
10169 cx.executor().run_until_parked();
10170 cx.update_editor(|editor, window, cx| {
10171 editor.toggle_comments(&ToggleComments::default(), window, cx)
10172 });
10173 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10174 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10175 cx.assert_editor_state(
10176 &r#"
10177 <!-- ˇ<script> -->
10178 // ˇvar x = new Y();
10179 <!-- ˇ</script> -->
10180 "#
10181 .unindent(),
10182 );
10183}
10184
10185#[gpui::test]
10186fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10187 init_test(cx, |_| {});
10188
10189 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10190 let multibuffer = cx.new(|cx| {
10191 let mut multibuffer = MultiBuffer::new(ReadWrite);
10192 multibuffer.push_excerpts(
10193 buffer.clone(),
10194 [
10195 ExcerptRange {
10196 context: Point::new(0, 0)..Point::new(0, 4),
10197 primary: None,
10198 },
10199 ExcerptRange {
10200 context: Point::new(1, 0)..Point::new(1, 4),
10201 primary: None,
10202 },
10203 ],
10204 cx,
10205 );
10206 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10207 multibuffer
10208 });
10209
10210 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10211 editor.update_in(cx, |editor, window, cx| {
10212 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10213 editor.change_selections(None, window, cx, |s| {
10214 s.select_ranges([
10215 Point::new(0, 0)..Point::new(0, 0),
10216 Point::new(1, 0)..Point::new(1, 0),
10217 ])
10218 });
10219
10220 editor.handle_input("X", window, cx);
10221 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10222 assert_eq!(
10223 editor.selections.ranges(cx),
10224 [
10225 Point::new(0, 1)..Point::new(0, 1),
10226 Point::new(1, 1)..Point::new(1, 1),
10227 ]
10228 );
10229
10230 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10231 editor.change_selections(None, window, cx, |s| {
10232 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10233 });
10234 editor.backspace(&Default::default(), window, cx);
10235 assert_eq!(editor.text(cx), "Xa\nbbb");
10236 assert_eq!(
10237 editor.selections.ranges(cx),
10238 [Point::new(1, 0)..Point::new(1, 0)]
10239 );
10240
10241 editor.change_selections(None, window, cx, |s| {
10242 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10243 });
10244 editor.backspace(&Default::default(), window, cx);
10245 assert_eq!(editor.text(cx), "X\nbb");
10246 assert_eq!(
10247 editor.selections.ranges(cx),
10248 [Point::new(0, 1)..Point::new(0, 1)]
10249 );
10250 });
10251}
10252
10253#[gpui::test]
10254fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10255 init_test(cx, |_| {});
10256
10257 let markers = vec![('[', ']').into(), ('(', ')').into()];
10258 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10259 indoc! {"
10260 [aaaa
10261 (bbbb]
10262 cccc)",
10263 },
10264 markers.clone(),
10265 );
10266 let excerpt_ranges = markers.into_iter().map(|marker| {
10267 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10268 ExcerptRange {
10269 context,
10270 primary: None,
10271 }
10272 });
10273 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10274 let multibuffer = cx.new(|cx| {
10275 let mut multibuffer = MultiBuffer::new(ReadWrite);
10276 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10277 multibuffer
10278 });
10279
10280 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10281 editor.update_in(cx, |editor, window, cx| {
10282 let (expected_text, selection_ranges) = marked_text_ranges(
10283 indoc! {"
10284 aaaa
10285 bˇbbb
10286 bˇbbˇb
10287 cccc"
10288 },
10289 true,
10290 );
10291 assert_eq!(editor.text(cx), expected_text);
10292 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10293
10294 editor.handle_input("X", window, cx);
10295
10296 let (expected_text, expected_selections) = marked_text_ranges(
10297 indoc! {"
10298 aaaa
10299 bXˇbbXb
10300 bXˇbbXˇb
10301 cccc"
10302 },
10303 false,
10304 );
10305 assert_eq!(editor.text(cx), expected_text);
10306 assert_eq!(editor.selections.ranges(cx), expected_selections);
10307
10308 editor.newline(&Newline, window, cx);
10309 let (expected_text, expected_selections) = marked_text_ranges(
10310 indoc! {"
10311 aaaa
10312 bX
10313 ˇbbX
10314 b
10315 bX
10316 ˇbbX
10317 ˇb
10318 cccc"
10319 },
10320 false,
10321 );
10322 assert_eq!(editor.text(cx), expected_text);
10323 assert_eq!(editor.selections.ranges(cx), expected_selections);
10324 });
10325}
10326
10327#[gpui::test]
10328fn test_refresh_selections(cx: &mut TestAppContext) {
10329 init_test(cx, |_| {});
10330
10331 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10332 let mut excerpt1_id = None;
10333 let multibuffer = cx.new(|cx| {
10334 let mut multibuffer = MultiBuffer::new(ReadWrite);
10335 excerpt1_id = multibuffer
10336 .push_excerpts(
10337 buffer.clone(),
10338 [
10339 ExcerptRange {
10340 context: Point::new(0, 0)..Point::new(1, 4),
10341 primary: None,
10342 },
10343 ExcerptRange {
10344 context: Point::new(1, 0)..Point::new(2, 4),
10345 primary: None,
10346 },
10347 ],
10348 cx,
10349 )
10350 .into_iter()
10351 .next();
10352 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10353 multibuffer
10354 });
10355
10356 let editor = cx.add_window(|window, cx| {
10357 let mut editor = build_editor(multibuffer.clone(), window, cx);
10358 let snapshot = editor.snapshot(window, cx);
10359 editor.change_selections(None, window, cx, |s| {
10360 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10361 });
10362 editor.begin_selection(
10363 Point::new(2, 1).to_display_point(&snapshot),
10364 true,
10365 1,
10366 window,
10367 cx,
10368 );
10369 assert_eq!(
10370 editor.selections.ranges(cx),
10371 [
10372 Point::new(1, 3)..Point::new(1, 3),
10373 Point::new(2, 1)..Point::new(2, 1),
10374 ]
10375 );
10376 editor
10377 });
10378
10379 // Refreshing selections is a no-op when excerpts haven't changed.
10380 _ = editor.update(cx, |editor, window, cx| {
10381 editor.change_selections(None, window, cx, |s| s.refresh());
10382 assert_eq!(
10383 editor.selections.ranges(cx),
10384 [
10385 Point::new(1, 3)..Point::new(1, 3),
10386 Point::new(2, 1)..Point::new(2, 1),
10387 ]
10388 );
10389 });
10390
10391 multibuffer.update(cx, |multibuffer, cx| {
10392 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10393 });
10394 _ = editor.update(cx, |editor, window, cx| {
10395 // Removing an excerpt causes the first selection to become degenerate.
10396 assert_eq!(
10397 editor.selections.ranges(cx),
10398 [
10399 Point::new(0, 0)..Point::new(0, 0),
10400 Point::new(0, 1)..Point::new(0, 1)
10401 ]
10402 );
10403
10404 // Refreshing selections will relocate the first selection to the original buffer
10405 // location.
10406 editor.change_selections(None, window, cx, |s| s.refresh());
10407 assert_eq!(
10408 editor.selections.ranges(cx),
10409 [
10410 Point::new(0, 1)..Point::new(0, 1),
10411 Point::new(0, 3)..Point::new(0, 3)
10412 ]
10413 );
10414 assert!(editor.selections.pending_anchor().is_some());
10415 });
10416}
10417
10418#[gpui::test]
10419fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10420 init_test(cx, |_| {});
10421
10422 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10423 let mut excerpt1_id = None;
10424 let multibuffer = cx.new(|cx| {
10425 let mut multibuffer = MultiBuffer::new(ReadWrite);
10426 excerpt1_id = multibuffer
10427 .push_excerpts(
10428 buffer.clone(),
10429 [
10430 ExcerptRange {
10431 context: Point::new(0, 0)..Point::new(1, 4),
10432 primary: None,
10433 },
10434 ExcerptRange {
10435 context: Point::new(1, 0)..Point::new(2, 4),
10436 primary: None,
10437 },
10438 ],
10439 cx,
10440 )
10441 .into_iter()
10442 .next();
10443 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10444 multibuffer
10445 });
10446
10447 let editor = cx.add_window(|window, cx| {
10448 let mut editor = build_editor(multibuffer.clone(), window, cx);
10449 let snapshot = editor.snapshot(window, cx);
10450 editor.begin_selection(
10451 Point::new(1, 3).to_display_point(&snapshot),
10452 false,
10453 1,
10454 window,
10455 cx,
10456 );
10457 assert_eq!(
10458 editor.selections.ranges(cx),
10459 [Point::new(1, 3)..Point::new(1, 3)]
10460 );
10461 editor
10462 });
10463
10464 multibuffer.update(cx, |multibuffer, cx| {
10465 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10466 });
10467 _ = editor.update(cx, |editor, window, cx| {
10468 assert_eq!(
10469 editor.selections.ranges(cx),
10470 [Point::new(0, 0)..Point::new(0, 0)]
10471 );
10472
10473 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10474 editor.change_selections(None, window, cx, |s| s.refresh());
10475 assert_eq!(
10476 editor.selections.ranges(cx),
10477 [Point::new(0, 3)..Point::new(0, 3)]
10478 );
10479 assert!(editor.selections.pending_anchor().is_some());
10480 });
10481}
10482
10483#[gpui::test]
10484async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10485 init_test(cx, |_| {});
10486
10487 let language = Arc::new(
10488 Language::new(
10489 LanguageConfig {
10490 brackets: BracketPairConfig {
10491 pairs: vec![
10492 BracketPair {
10493 start: "{".to_string(),
10494 end: "}".to_string(),
10495 close: true,
10496 surround: true,
10497 newline: true,
10498 },
10499 BracketPair {
10500 start: "/* ".to_string(),
10501 end: " */".to_string(),
10502 close: true,
10503 surround: true,
10504 newline: true,
10505 },
10506 ],
10507 ..Default::default()
10508 },
10509 ..Default::default()
10510 },
10511 Some(tree_sitter_rust::LANGUAGE.into()),
10512 )
10513 .with_indents_query("")
10514 .unwrap(),
10515 );
10516
10517 let text = concat!(
10518 "{ }\n", //
10519 " x\n", //
10520 " /* */\n", //
10521 "x\n", //
10522 "{{} }\n", //
10523 );
10524
10525 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10526 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10527 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10528 editor
10529 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10530 .await;
10531
10532 editor.update_in(cx, |editor, window, cx| {
10533 editor.change_selections(None, window, cx, |s| {
10534 s.select_display_ranges([
10535 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10536 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10537 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10538 ])
10539 });
10540 editor.newline(&Newline, window, cx);
10541
10542 assert_eq!(
10543 editor.buffer().read(cx).read(cx).text(),
10544 concat!(
10545 "{ \n", // Suppress rustfmt
10546 "\n", //
10547 "}\n", //
10548 " x\n", //
10549 " /* \n", //
10550 " \n", //
10551 " */\n", //
10552 "x\n", //
10553 "{{} \n", //
10554 "}\n", //
10555 )
10556 );
10557 });
10558}
10559
10560#[gpui::test]
10561fn test_highlighted_ranges(cx: &mut TestAppContext) {
10562 init_test(cx, |_| {});
10563
10564 let editor = cx.add_window(|window, cx| {
10565 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10566 build_editor(buffer.clone(), window, cx)
10567 });
10568
10569 _ = editor.update(cx, |editor, window, cx| {
10570 struct Type1;
10571 struct Type2;
10572
10573 let buffer = editor.buffer.read(cx).snapshot(cx);
10574
10575 let anchor_range =
10576 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10577
10578 editor.highlight_background::<Type1>(
10579 &[
10580 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10581 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10582 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10583 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10584 ],
10585 |_| Hsla::red(),
10586 cx,
10587 );
10588 editor.highlight_background::<Type2>(
10589 &[
10590 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10591 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10592 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10593 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10594 ],
10595 |_| Hsla::green(),
10596 cx,
10597 );
10598
10599 let snapshot = editor.snapshot(window, cx);
10600 let mut highlighted_ranges = editor.background_highlights_in_range(
10601 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10602 &snapshot,
10603 cx.theme().colors(),
10604 );
10605 // Enforce a consistent ordering based on color without relying on the ordering of the
10606 // highlight's `TypeId` which is non-executor.
10607 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10608 assert_eq!(
10609 highlighted_ranges,
10610 &[
10611 (
10612 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10613 Hsla::red(),
10614 ),
10615 (
10616 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10617 Hsla::red(),
10618 ),
10619 (
10620 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10621 Hsla::green(),
10622 ),
10623 (
10624 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10625 Hsla::green(),
10626 ),
10627 ]
10628 );
10629 assert_eq!(
10630 editor.background_highlights_in_range(
10631 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10632 &snapshot,
10633 cx.theme().colors(),
10634 ),
10635 &[(
10636 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10637 Hsla::red(),
10638 )]
10639 );
10640 });
10641}
10642
10643#[gpui::test]
10644async fn test_following(cx: &mut TestAppContext) {
10645 init_test(cx, |_| {});
10646
10647 let fs = FakeFs::new(cx.executor());
10648 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10649
10650 let buffer = project.update(cx, |project, cx| {
10651 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10652 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10653 });
10654 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10655 let follower = cx.update(|cx| {
10656 cx.open_window(
10657 WindowOptions {
10658 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10659 gpui::Point::new(px(0.), px(0.)),
10660 gpui::Point::new(px(10.), px(80.)),
10661 ))),
10662 ..Default::default()
10663 },
10664 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10665 )
10666 .unwrap()
10667 });
10668
10669 let is_still_following = Rc::new(RefCell::new(true));
10670 let follower_edit_event_count = Rc::new(RefCell::new(0));
10671 let pending_update = Rc::new(RefCell::new(None));
10672 let leader_entity = leader.root(cx).unwrap();
10673 let follower_entity = follower.root(cx).unwrap();
10674 _ = follower.update(cx, {
10675 let update = pending_update.clone();
10676 let is_still_following = is_still_following.clone();
10677 let follower_edit_event_count = follower_edit_event_count.clone();
10678 |_, window, cx| {
10679 cx.subscribe_in(
10680 &leader_entity,
10681 window,
10682 move |_, leader, event, window, cx| {
10683 leader.read(cx).add_event_to_update_proto(
10684 event,
10685 &mut update.borrow_mut(),
10686 window,
10687 cx,
10688 );
10689 },
10690 )
10691 .detach();
10692
10693 cx.subscribe_in(
10694 &follower_entity,
10695 window,
10696 move |_, _, event: &EditorEvent, _window, _cx| {
10697 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10698 *is_still_following.borrow_mut() = false;
10699 }
10700
10701 if let EditorEvent::BufferEdited = event {
10702 *follower_edit_event_count.borrow_mut() += 1;
10703 }
10704 },
10705 )
10706 .detach();
10707 }
10708 });
10709
10710 // Update the selections only
10711 _ = leader.update(cx, |leader, window, cx| {
10712 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10713 });
10714 follower
10715 .update(cx, |follower, window, cx| {
10716 follower.apply_update_proto(
10717 &project,
10718 pending_update.borrow_mut().take().unwrap(),
10719 window,
10720 cx,
10721 )
10722 })
10723 .unwrap()
10724 .await
10725 .unwrap();
10726 _ = follower.update(cx, |follower, _, cx| {
10727 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10728 });
10729 assert!(*is_still_following.borrow());
10730 assert_eq!(*follower_edit_event_count.borrow(), 0);
10731
10732 // Update the scroll position only
10733 _ = leader.update(cx, |leader, window, cx| {
10734 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10735 });
10736 follower
10737 .update(cx, |follower, window, cx| {
10738 follower.apply_update_proto(
10739 &project,
10740 pending_update.borrow_mut().take().unwrap(),
10741 window,
10742 cx,
10743 )
10744 })
10745 .unwrap()
10746 .await
10747 .unwrap();
10748 assert_eq!(
10749 follower
10750 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10751 .unwrap(),
10752 gpui::Point::new(1.5, 3.5)
10753 );
10754 assert!(*is_still_following.borrow());
10755 assert_eq!(*follower_edit_event_count.borrow(), 0);
10756
10757 // Update the selections and scroll position. The follower's scroll position is updated
10758 // via autoscroll, not via the leader's exact scroll position.
10759 _ = leader.update(cx, |leader, window, cx| {
10760 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10761 leader.request_autoscroll(Autoscroll::newest(), cx);
10762 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10763 });
10764 follower
10765 .update(cx, |follower, window, cx| {
10766 follower.apply_update_proto(
10767 &project,
10768 pending_update.borrow_mut().take().unwrap(),
10769 window,
10770 cx,
10771 )
10772 })
10773 .unwrap()
10774 .await
10775 .unwrap();
10776 _ = follower.update(cx, |follower, _, cx| {
10777 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10778 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10779 });
10780 assert!(*is_still_following.borrow());
10781
10782 // Creating a pending selection that precedes another selection
10783 _ = leader.update(cx, |leader, window, cx| {
10784 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10785 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10786 });
10787 follower
10788 .update(cx, |follower, window, cx| {
10789 follower.apply_update_proto(
10790 &project,
10791 pending_update.borrow_mut().take().unwrap(),
10792 window,
10793 cx,
10794 )
10795 })
10796 .unwrap()
10797 .await
10798 .unwrap();
10799 _ = follower.update(cx, |follower, _, cx| {
10800 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10801 });
10802 assert!(*is_still_following.borrow());
10803
10804 // Extend the pending selection so that it surrounds another selection
10805 _ = leader.update(cx, |leader, window, cx| {
10806 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10807 });
10808 follower
10809 .update(cx, |follower, window, cx| {
10810 follower.apply_update_proto(
10811 &project,
10812 pending_update.borrow_mut().take().unwrap(),
10813 window,
10814 cx,
10815 )
10816 })
10817 .unwrap()
10818 .await
10819 .unwrap();
10820 _ = follower.update(cx, |follower, _, cx| {
10821 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10822 });
10823
10824 // Scrolling locally breaks the follow
10825 _ = follower.update(cx, |follower, window, cx| {
10826 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10827 follower.set_scroll_anchor(
10828 ScrollAnchor {
10829 anchor: top_anchor,
10830 offset: gpui::Point::new(0.0, 0.5),
10831 },
10832 window,
10833 cx,
10834 );
10835 });
10836 assert!(!(*is_still_following.borrow()));
10837}
10838
10839#[gpui::test]
10840async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10841 init_test(cx, |_| {});
10842
10843 let fs = FakeFs::new(cx.executor());
10844 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10845 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10846 let pane = workspace
10847 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10848 .unwrap();
10849
10850 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10851
10852 let leader = pane.update_in(cx, |_, window, cx| {
10853 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10854 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10855 });
10856
10857 // Start following the editor when it has no excerpts.
10858 let mut state_message =
10859 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10860 let workspace_entity = workspace.root(cx).unwrap();
10861 let follower_1 = cx
10862 .update_window(*workspace.deref(), |_, window, cx| {
10863 Editor::from_state_proto(
10864 workspace_entity,
10865 ViewId {
10866 creator: Default::default(),
10867 id: 0,
10868 },
10869 &mut state_message,
10870 window,
10871 cx,
10872 )
10873 })
10874 .unwrap()
10875 .unwrap()
10876 .await
10877 .unwrap();
10878
10879 let update_message = Rc::new(RefCell::new(None));
10880 follower_1.update_in(cx, {
10881 let update = update_message.clone();
10882 |_, window, cx| {
10883 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10884 leader.read(cx).add_event_to_update_proto(
10885 event,
10886 &mut update.borrow_mut(),
10887 window,
10888 cx,
10889 );
10890 })
10891 .detach();
10892 }
10893 });
10894
10895 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10896 (
10897 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10898 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10899 )
10900 });
10901
10902 // Insert some excerpts.
10903 leader.update(cx, |leader, cx| {
10904 leader.buffer.update(cx, |multibuffer, cx| {
10905 let excerpt_ids = multibuffer.push_excerpts(
10906 buffer_1.clone(),
10907 [
10908 ExcerptRange {
10909 context: 1..6,
10910 primary: None,
10911 },
10912 ExcerptRange {
10913 context: 12..15,
10914 primary: None,
10915 },
10916 ExcerptRange {
10917 context: 0..3,
10918 primary: None,
10919 },
10920 ],
10921 cx,
10922 );
10923 multibuffer.insert_excerpts_after(
10924 excerpt_ids[0],
10925 buffer_2.clone(),
10926 [
10927 ExcerptRange {
10928 context: 8..12,
10929 primary: None,
10930 },
10931 ExcerptRange {
10932 context: 0..6,
10933 primary: None,
10934 },
10935 ],
10936 cx,
10937 );
10938 });
10939 });
10940
10941 // Apply the update of adding the excerpts.
10942 follower_1
10943 .update_in(cx, |follower, window, cx| {
10944 follower.apply_update_proto(
10945 &project,
10946 update_message.borrow().clone().unwrap(),
10947 window,
10948 cx,
10949 )
10950 })
10951 .await
10952 .unwrap();
10953 assert_eq!(
10954 follower_1.update(cx, |editor, cx| editor.text(cx)),
10955 leader.update(cx, |editor, cx| editor.text(cx))
10956 );
10957 update_message.borrow_mut().take();
10958
10959 // Start following separately after it already has excerpts.
10960 let mut state_message =
10961 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10962 let workspace_entity = workspace.root(cx).unwrap();
10963 let follower_2 = cx
10964 .update_window(*workspace.deref(), |_, window, cx| {
10965 Editor::from_state_proto(
10966 workspace_entity,
10967 ViewId {
10968 creator: Default::default(),
10969 id: 0,
10970 },
10971 &mut state_message,
10972 window,
10973 cx,
10974 )
10975 })
10976 .unwrap()
10977 .unwrap()
10978 .await
10979 .unwrap();
10980 assert_eq!(
10981 follower_2.update(cx, |editor, cx| editor.text(cx)),
10982 leader.update(cx, |editor, cx| editor.text(cx))
10983 );
10984
10985 // Remove some excerpts.
10986 leader.update(cx, |leader, cx| {
10987 leader.buffer.update(cx, |multibuffer, cx| {
10988 let excerpt_ids = multibuffer.excerpt_ids();
10989 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10990 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10991 });
10992 });
10993
10994 // Apply the update of removing the excerpts.
10995 follower_1
10996 .update_in(cx, |follower, window, cx| {
10997 follower.apply_update_proto(
10998 &project,
10999 update_message.borrow().clone().unwrap(),
11000 window,
11001 cx,
11002 )
11003 })
11004 .await
11005 .unwrap();
11006 follower_2
11007 .update_in(cx, |follower, window, cx| {
11008 follower.apply_update_proto(
11009 &project,
11010 update_message.borrow().clone().unwrap(),
11011 window,
11012 cx,
11013 )
11014 })
11015 .await
11016 .unwrap();
11017 update_message.borrow_mut().take();
11018 assert_eq!(
11019 follower_1.update(cx, |editor, cx| editor.text(cx)),
11020 leader.update(cx, |editor, cx| editor.text(cx))
11021 );
11022}
11023
11024#[gpui::test]
11025async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11026 init_test(cx, |_| {});
11027
11028 let mut cx = EditorTestContext::new(cx).await;
11029 let lsp_store =
11030 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11031
11032 cx.set_state(indoc! {"
11033 ˇfn func(abc def: i32) -> u32 {
11034 }
11035 "});
11036
11037 cx.update(|_, cx| {
11038 lsp_store.update(cx, |lsp_store, cx| {
11039 lsp_store
11040 .update_diagnostics(
11041 LanguageServerId(0),
11042 lsp::PublishDiagnosticsParams {
11043 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11044 version: None,
11045 diagnostics: vec![
11046 lsp::Diagnostic {
11047 range: lsp::Range::new(
11048 lsp::Position::new(0, 11),
11049 lsp::Position::new(0, 12),
11050 ),
11051 severity: Some(lsp::DiagnosticSeverity::ERROR),
11052 ..Default::default()
11053 },
11054 lsp::Diagnostic {
11055 range: lsp::Range::new(
11056 lsp::Position::new(0, 12),
11057 lsp::Position::new(0, 15),
11058 ),
11059 severity: Some(lsp::DiagnosticSeverity::ERROR),
11060 ..Default::default()
11061 },
11062 lsp::Diagnostic {
11063 range: lsp::Range::new(
11064 lsp::Position::new(0, 25),
11065 lsp::Position::new(0, 28),
11066 ),
11067 severity: Some(lsp::DiagnosticSeverity::ERROR),
11068 ..Default::default()
11069 },
11070 ],
11071 },
11072 &[],
11073 cx,
11074 )
11075 .unwrap()
11076 });
11077 });
11078
11079 executor.run_until_parked();
11080
11081 cx.update_editor(|editor, window, cx| {
11082 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11083 });
11084
11085 cx.assert_editor_state(indoc! {"
11086 fn func(abc def: i32) -> ˇu32 {
11087 }
11088 "});
11089
11090 cx.update_editor(|editor, window, cx| {
11091 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11092 });
11093
11094 cx.assert_editor_state(indoc! {"
11095 fn func(abc ˇdef: i32) -> u32 {
11096 }
11097 "});
11098
11099 cx.update_editor(|editor, window, cx| {
11100 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11101 });
11102
11103 cx.assert_editor_state(indoc! {"
11104 fn func(abcˇ def: i32) -> u32 {
11105 }
11106 "});
11107
11108 cx.update_editor(|editor, window, cx| {
11109 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11110 });
11111
11112 cx.assert_editor_state(indoc! {"
11113 fn func(abc def: i32) -> ˇu32 {
11114 }
11115 "});
11116}
11117
11118#[gpui::test]
11119async fn cycle_through_same_place_diagnostics(
11120 executor: BackgroundExecutor,
11121 cx: &mut TestAppContext,
11122) {
11123 init_test(cx, |_| {});
11124
11125 let mut cx = EditorTestContext::new(cx).await;
11126 let lsp_store =
11127 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11128
11129 cx.set_state(indoc! {"
11130 ˇfn func(abc def: i32) -> u32 {
11131 }
11132 "});
11133
11134 cx.update(|_, cx| {
11135 lsp_store.update(cx, |lsp_store, cx| {
11136 lsp_store
11137 .update_diagnostics(
11138 LanguageServerId(0),
11139 lsp::PublishDiagnosticsParams {
11140 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11141 version: None,
11142 diagnostics: vec![
11143 lsp::Diagnostic {
11144 range: lsp::Range::new(
11145 lsp::Position::new(0, 11),
11146 lsp::Position::new(0, 12),
11147 ),
11148 severity: Some(lsp::DiagnosticSeverity::ERROR),
11149 ..Default::default()
11150 },
11151 lsp::Diagnostic {
11152 range: lsp::Range::new(
11153 lsp::Position::new(0, 12),
11154 lsp::Position::new(0, 15),
11155 ),
11156 severity: Some(lsp::DiagnosticSeverity::ERROR),
11157 ..Default::default()
11158 },
11159 lsp::Diagnostic {
11160 range: lsp::Range::new(
11161 lsp::Position::new(0, 12),
11162 lsp::Position::new(0, 15),
11163 ),
11164 severity: Some(lsp::DiagnosticSeverity::ERROR),
11165 ..Default::default()
11166 },
11167 lsp::Diagnostic {
11168 range: lsp::Range::new(
11169 lsp::Position::new(0, 25),
11170 lsp::Position::new(0, 28),
11171 ),
11172 severity: Some(lsp::DiagnosticSeverity::ERROR),
11173 ..Default::default()
11174 },
11175 ],
11176 },
11177 &[],
11178 cx,
11179 )
11180 .unwrap()
11181 });
11182 });
11183 executor.run_until_parked();
11184
11185 //// Backward
11186
11187 // Fourth diagnostic
11188 cx.update_editor(|editor, window, cx| {
11189 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11190 });
11191 cx.assert_editor_state(indoc! {"
11192 fn func(abc def: i32) -> ˇu32 {
11193 }
11194 "});
11195
11196 // Third diagnostic
11197 cx.update_editor(|editor, window, cx| {
11198 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11199 });
11200 cx.assert_editor_state(indoc! {"
11201 fn func(abc ˇdef: i32) -> u32 {
11202 }
11203 "});
11204
11205 // Second diagnostic, same place
11206 cx.update_editor(|editor, window, cx| {
11207 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11208 });
11209 cx.assert_editor_state(indoc! {"
11210 fn func(abc ˇdef: i32) -> u32 {
11211 }
11212 "});
11213
11214 // First diagnostic
11215 cx.update_editor(|editor, window, cx| {
11216 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11217 });
11218 cx.assert_editor_state(indoc! {"
11219 fn func(abcˇ def: i32) -> u32 {
11220 }
11221 "});
11222
11223 // Wrapped over, fourth diagnostic
11224 cx.update_editor(|editor, window, cx| {
11225 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11226 });
11227 cx.assert_editor_state(indoc! {"
11228 fn func(abc def: i32) -> ˇu32 {
11229 }
11230 "});
11231
11232 cx.update_editor(|editor, window, cx| {
11233 editor.move_to_beginning(&MoveToBeginning, window, cx);
11234 });
11235 cx.assert_editor_state(indoc! {"
11236 ˇfn func(abc def: i32) -> u32 {
11237 }
11238 "});
11239
11240 //// Forward
11241
11242 // First diagnostic
11243 cx.update_editor(|editor, window, cx| {
11244 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11245 });
11246 cx.assert_editor_state(indoc! {"
11247 fn func(abcˇ def: i32) -> u32 {
11248 }
11249 "});
11250
11251 // Second diagnostic
11252 cx.update_editor(|editor, window, cx| {
11253 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11254 });
11255 cx.assert_editor_state(indoc! {"
11256 fn func(abc ˇdef: i32) -> u32 {
11257 }
11258 "});
11259
11260 // Third diagnostic, same place
11261 cx.update_editor(|editor, window, cx| {
11262 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11263 });
11264 cx.assert_editor_state(indoc! {"
11265 fn func(abc ˇdef: i32) -> u32 {
11266 }
11267 "});
11268
11269 // Fourth diagnostic
11270 cx.update_editor(|editor, window, cx| {
11271 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11272 });
11273 cx.assert_editor_state(indoc! {"
11274 fn func(abc def: i32) -> ˇu32 {
11275 }
11276 "});
11277
11278 // Wrapped around, first diagnostic
11279 cx.update_editor(|editor, window, cx| {
11280 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11281 });
11282 cx.assert_editor_state(indoc! {"
11283 fn func(abcˇ def: i32) -> u32 {
11284 }
11285 "});
11286}
11287
11288#[gpui::test]
11289async fn active_diagnostics_dismiss_after_invalidation(
11290 executor: BackgroundExecutor,
11291 cx: &mut TestAppContext,
11292) {
11293 init_test(cx, |_| {});
11294
11295 let mut cx = EditorTestContext::new(cx).await;
11296 let lsp_store =
11297 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11298
11299 cx.set_state(indoc! {"
11300 ˇfn func(abc def: i32) -> u32 {
11301 }
11302 "});
11303
11304 let message = "Something's wrong!";
11305 cx.update(|_, cx| {
11306 lsp_store.update(cx, |lsp_store, cx| {
11307 lsp_store
11308 .update_diagnostics(
11309 LanguageServerId(0),
11310 lsp::PublishDiagnosticsParams {
11311 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11312 version: None,
11313 diagnostics: vec![lsp::Diagnostic {
11314 range: lsp::Range::new(
11315 lsp::Position::new(0, 11),
11316 lsp::Position::new(0, 12),
11317 ),
11318 severity: Some(lsp::DiagnosticSeverity::ERROR),
11319 message: message.to_string(),
11320 ..Default::default()
11321 }],
11322 },
11323 &[],
11324 cx,
11325 )
11326 .unwrap()
11327 });
11328 });
11329 executor.run_until_parked();
11330
11331 cx.update_editor(|editor, window, cx| {
11332 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11333 assert_eq!(
11334 editor
11335 .active_diagnostics
11336 .as_ref()
11337 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11338 Some(message),
11339 "Should have a diagnostics group activated"
11340 );
11341 });
11342 cx.assert_editor_state(indoc! {"
11343 fn func(abcˇ def: i32) -> u32 {
11344 }
11345 "});
11346
11347 cx.update(|_, cx| {
11348 lsp_store.update(cx, |lsp_store, cx| {
11349 lsp_store
11350 .update_diagnostics(
11351 LanguageServerId(0),
11352 lsp::PublishDiagnosticsParams {
11353 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11354 version: None,
11355 diagnostics: Vec::new(),
11356 },
11357 &[],
11358 cx,
11359 )
11360 .unwrap()
11361 });
11362 });
11363 executor.run_until_parked();
11364 cx.update_editor(|editor, _, _| {
11365 assert_eq!(
11366 editor.active_diagnostics, None,
11367 "After no diagnostics set to the editor, no diagnostics should be active"
11368 );
11369 });
11370 cx.assert_editor_state(indoc! {"
11371 fn func(abcˇ def: i32) -> u32 {
11372 }
11373 "});
11374
11375 cx.update_editor(|editor, window, cx| {
11376 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11377 assert_eq!(
11378 editor.active_diagnostics, None,
11379 "Should be no diagnostics to go to and activate"
11380 );
11381 });
11382 cx.assert_editor_state(indoc! {"
11383 fn func(abcˇ def: i32) -> u32 {
11384 }
11385 "});
11386}
11387
11388#[gpui::test]
11389async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11390 init_test(cx, |_| {});
11391
11392 let mut cx = EditorTestContext::new(cx).await;
11393
11394 cx.set_state(indoc! {"
11395 fn func(abˇc def: i32) -> u32 {
11396 }
11397 "});
11398 let lsp_store =
11399 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11400
11401 cx.update(|_, cx| {
11402 lsp_store.update(cx, |lsp_store, cx| {
11403 lsp_store.update_diagnostics(
11404 LanguageServerId(0),
11405 lsp::PublishDiagnosticsParams {
11406 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11407 version: None,
11408 diagnostics: vec![lsp::Diagnostic {
11409 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11410 severity: Some(lsp::DiagnosticSeverity::ERROR),
11411 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11412 ..Default::default()
11413 }],
11414 },
11415 &[],
11416 cx,
11417 )
11418 })
11419 }).unwrap();
11420 cx.run_until_parked();
11421 cx.update_editor(|editor, window, cx| {
11422 hover_popover::hover(editor, &Default::default(), window, cx)
11423 });
11424 cx.run_until_parked();
11425 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11426}
11427
11428#[gpui::test]
11429async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11430 init_test(cx, |_| {});
11431
11432 let mut cx = EditorTestContext::new(cx).await;
11433
11434 let diff_base = r#"
11435 use some::mod;
11436
11437 const A: u32 = 42;
11438
11439 fn main() {
11440 println!("hello");
11441
11442 println!("world");
11443 }
11444 "#
11445 .unindent();
11446
11447 // Edits are modified, removed, modified, added
11448 cx.set_state(
11449 &r#"
11450 use some::modified;
11451
11452 ˇ
11453 fn main() {
11454 println!("hello there");
11455
11456 println!("around the");
11457 println!("world");
11458 }
11459 "#
11460 .unindent(),
11461 );
11462
11463 cx.set_head_text(&diff_base);
11464 executor.run_until_parked();
11465
11466 cx.update_editor(|editor, window, cx| {
11467 //Wrap around the bottom of the buffer
11468 for _ in 0..3 {
11469 editor.go_to_next_hunk(&GoToHunk, window, cx);
11470 }
11471 });
11472
11473 cx.assert_editor_state(
11474 &r#"
11475 ˇuse some::modified;
11476
11477
11478 fn main() {
11479 println!("hello there");
11480
11481 println!("around the");
11482 println!("world");
11483 }
11484 "#
11485 .unindent(),
11486 );
11487
11488 cx.update_editor(|editor, window, cx| {
11489 //Wrap around the top of the buffer
11490 for _ in 0..2 {
11491 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11492 }
11493 });
11494
11495 cx.assert_editor_state(
11496 &r#"
11497 use some::modified;
11498
11499
11500 fn main() {
11501 ˇ println!("hello there");
11502
11503 println!("around the");
11504 println!("world");
11505 }
11506 "#
11507 .unindent(),
11508 );
11509
11510 cx.update_editor(|editor, window, cx| {
11511 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11512 });
11513
11514 cx.assert_editor_state(
11515 &r#"
11516 use some::modified;
11517
11518 ˇ
11519 fn main() {
11520 println!("hello there");
11521
11522 println!("around the");
11523 println!("world");
11524 }
11525 "#
11526 .unindent(),
11527 );
11528
11529 cx.update_editor(|editor, window, cx| {
11530 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11531 });
11532
11533 cx.assert_editor_state(
11534 &r#"
11535 ˇuse some::modified;
11536
11537
11538 fn main() {
11539 println!("hello there");
11540
11541 println!("around the");
11542 println!("world");
11543 }
11544 "#
11545 .unindent(),
11546 );
11547
11548 cx.update_editor(|editor, window, cx| {
11549 for _ in 0..2 {
11550 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11551 }
11552 });
11553
11554 cx.assert_editor_state(
11555 &r#"
11556 use some::modified;
11557
11558
11559 fn main() {
11560 ˇ println!("hello there");
11561
11562 println!("around the");
11563 println!("world");
11564 }
11565 "#
11566 .unindent(),
11567 );
11568
11569 cx.update_editor(|editor, window, cx| {
11570 editor.fold(&Fold, window, cx);
11571 });
11572
11573 cx.update_editor(|editor, window, cx| {
11574 editor.go_to_next_hunk(&GoToHunk, window, cx);
11575 });
11576
11577 cx.assert_editor_state(
11578 &r#"
11579 ˇuse some::modified;
11580
11581
11582 fn main() {
11583 println!("hello there");
11584
11585 println!("around the");
11586 println!("world");
11587 }
11588 "#
11589 .unindent(),
11590 );
11591}
11592
11593#[test]
11594fn test_split_words() {
11595 fn split(text: &str) -> Vec<&str> {
11596 split_words(text).collect()
11597 }
11598
11599 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11600 assert_eq!(split("hello_world"), &["hello_", "world"]);
11601 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11602 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11603 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11604 assert_eq!(split("helloworld"), &["helloworld"]);
11605
11606 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11607}
11608
11609#[gpui::test]
11610async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11611 init_test(cx, |_| {});
11612
11613 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11614 let mut assert = |before, after| {
11615 let _state_context = cx.set_state(before);
11616 cx.run_until_parked();
11617 cx.update_editor(|editor, window, cx| {
11618 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11619 });
11620 cx.assert_editor_state(after);
11621 };
11622
11623 // Outside bracket jumps to outside of matching bracket
11624 assert("console.logˇ(var);", "console.log(var)ˇ;");
11625 assert("console.log(var)ˇ;", "console.logˇ(var);");
11626
11627 // Inside bracket jumps to inside of matching bracket
11628 assert("console.log(ˇvar);", "console.log(varˇ);");
11629 assert("console.log(varˇ);", "console.log(ˇvar);");
11630
11631 // When outside a bracket and inside, favor jumping to the inside bracket
11632 assert(
11633 "console.log('foo', [1, 2, 3]ˇ);",
11634 "console.log(ˇ'foo', [1, 2, 3]);",
11635 );
11636 assert(
11637 "console.log(ˇ'foo', [1, 2, 3]);",
11638 "console.log('foo', [1, 2, 3]ˇ);",
11639 );
11640
11641 // Bias forward if two options are equally likely
11642 assert(
11643 "let result = curried_fun()ˇ();",
11644 "let result = curried_fun()()ˇ;",
11645 );
11646
11647 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11648 assert(
11649 indoc! {"
11650 function test() {
11651 console.log('test')ˇ
11652 }"},
11653 indoc! {"
11654 function test() {
11655 console.logˇ('test')
11656 }"},
11657 );
11658}
11659
11660#[gpui::test]
11661async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11662 init_test(cx, |_| {});
11663
11664 let fs = FakeFs::new(cx.executor());
11665 fs.insert_tree(
11666 path!("/a"),
11667 json!({
11668 "main.rs": "fn main() { let a = 5; }",
11669 "other.rs": "// Test file",
11670 }),
11671 )
11672 .await;
11673 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11674
11675 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11676 language_registry.add(Arc::new(Language::new(
11677 LanguageConfig {
11678 name: "Rust".into(),
11679 matcher: LanguageMatcher {
11680 path_suffixes: vec!["rs".to_string()],
11681 ..Default::default()
11682 },
11683 brackets: BracketPairConfig {
11684 pairs: vec![BracketPair {
11685 start: "{".to_string(),
11686 end: "}".to_string(),
11687 close: true,
11688 surround: true,
11689 newline: true,
11690 }],
11691 disabled_scopes_by_bracket_ix: Vec::new(),
11692 },
11693 ..Default::default()
11694 },
11695 Some(tree_sitter_rust::LANGUAGE.into()),
11696 )));
11697 let mut fake_servers = language_registry.register_fake_lsp(
11698 "Rust",
11699 FakeLspAdapter {
11700 capabilities: lsp::ServerCapabilities {
11701 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11702 first_trigger_character: "{".to_string(),
11703 more_trigger_character: None,
11704 }),
11705 ..Default::default()
11706 },
11707 ..Default::default()
11708 },
11709 );
11710
11711 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11712
11713 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11714
11715 let worktree_id = workspace
11716 .update(cx, |workspace, _, cx| {
11717 workspace.project().update(cx, |project, cx| {
11718 project.worktrees(cx).next().unwrap().read(cx).id()
11719 })
11720 })
11721 .unwrap();
11722
11723 let buffer = project
11724 .update(cx, |project, cx| {
11725 project.open_local_buffer(path!("/a/main.rs"), cx)
11726 })
11727 .await
11728 .unwrap();
11729 let editor_handle = workspace
11730 .update(cx, |workspace, window, cx| {
11731 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11732 })
11733 .unwrap()
11734 .await
11735 .unwrap()
11736 .downcast::<Editor>()
11737 .unwrap();
11738
11739 cx.executor().start_waiting();
11740 let fake_server = fake_servers.next().await.unwrap();
11741
11742 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11743 assert_eq!(
11744 params.text_document_position.text_document.uri,
11745 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11746 );
11747 assert_eq!(
11748 params.text_document_position.position,
11749 lsp::Position::new(0, 21),
11750 );
11751
11752 Ok(Some(vec![lsp::TextEdit {
11753 new_text: "]".to_string(),
11754 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11755 }]))
11756 });
11757
11758 editor_handle.update_in(cx, |editor, window, cx| {
11759 window.focus(&editor.focus_handle(cx));
11760 editor.change_selections(None, window, cx, |s| {
11761 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11762 });
11763 editor.handle_input("{", window, cx);
11764 });
11765
11766 cx.executor().run_until_parked();
11767
11768 buffer.update(cx, |buffer, _| {
11769 assert_eq!(
11770 buffer.text(),
11771 "fn main() { let a = {5}; }",
11772 "No extra braces from on type formatting should appear in the buffer"
11773 )
11774 });
11775}
11776
11777#[gpui::test]
11778async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11779 init_test(cx, |_| {});
11780
11781 let fs = FakeFs::new(cx.executor());
11782 fs.insert_tree(
11783 path!("/a"),
11784 json!({
11785 "main.rs": "fn main() { let a = 5; }",
11786 "other.rs": "// Test file",
11787 }),
11788 )
11789 .await;
11790
11791 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11792
11793 let server_restarts = Arc::new(AtomicUsize::new(0));
11794 let closure_restarts = Arc::clone(&server_restarts);
11795 let language_server_name = "test language server";
11796 let language_name: LanguageName = "Rust".into();
11797
11798 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11799 language_registry.add(Arc::new(Language::new(
11800 LanguageConfig {
11801 name: language_name.clone(),
11802 matcher: LanguageMatcher {
11803 path_suffixes: vec!["rs".to_string()],
11804 ..Default::default()
11805 },
11806 ..Default::default()
11807 },
11808 Some(tree_sitter_rust::LANGUAGE.into()),
11809 )));
11810 let mut fake_servers = language_registry.register_fake_lsp(
11811 "Rust",
11812 FakeLspAdapter {
11813 name: language_server_name,
11814 initialization_options: Some(json!({
11815 "testOptionValue": true
11816 })),
11817 initializer: Some(Box::new(move |fake_server| {
11818 let task_restarts = Arc::clone(&closure_restarts);
11819 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11820 task_restarts.fetch_add(1, atomic::Ordering::Release);
11821 futures::future::ready(Ok(()))
11822 });
11823 })),
11824 ..Default::default()
11825 },
11826 );
11827
11828 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11829 let _buffer = project
11830 .update(cx, |project, cx| {
11831 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11832 })
11833 .await
11834 .unwrap();
11835 let _fake_server = fake_servers.next().await.unwrap();
11836 update_test_language_settings(cx, |language_settings| {
11837 language_settings.languages.insert(
11838 language_name.clone(),
11839 LanguageSettingsContent {
11840 tab_size: NonZeroU32::new(8),
11841 ..Default::default()
11842 },
11843 );
11844 });
11845 cx.executor().run_until_parked();
11846 assert_eq!(
11847 server_restarts.load(atomic::Ordering::Acquire),
11848 0,
11849 "Should not restart LSP server on an unrelated change"
11850 );
11851
11852 update_test_project_settings(cx, |project_settings| {
11853 project_settings.lsp.insert(
11854 "Some other server name".into(),
11855 LspSettings {
11856 binary: None,
11857 settings: None,
11858 initialization_options: Some(json!({
11859 "some other init value": false
11860 })),
11861 },
11862 );
11863 });
11864 cx.executor().run_until_parked();
11865 assert_eq!(
11866 server_restarts.load(atomic::Ordering::Acquire),
11867 0,
11868 "Should not restart LSP server on an unrelated LSP settings change"
11869 );
11870
11871 update_test_project_settings(cx, |project_settings| {
11872 project_settings.lsp.insert(
11873 language_server_name.into(),
11874 LspSettings {
11875 binary: None,
11876 settings: None,
11877 initialization_options: Some(json!({
11878 "anotherInitValue": false
11879 })),
11880 },
11881 );
11882 });
11883 cx.executor().run_until_parked();
11884 assert_eq!(
11885 server_restarts.load(atomic::Ordering::Acquire),
11886 1,
11887 "Should restart LSP server on a related LSP settings change"
11888 );
11889
11890 update_test_project_settings(cx, |project_settings| {
11891 project_settings.lsp.insert(
11892 language_server_name.into(),
11893 LspSettings {
11894 binary: None,
11895 settings: None,
11896 initialization_options: Some(json!({
11897 "anotherInitValue": false
11898 })),
11899 },
11900 );
11901 });
11902 cx.executor().run_until_parked();
11903 assert_eq!(
11904 server_restarts.load(atomic::Ordering::Acquire),
11905 1,
11906 "Should not restart LSP server on a related LSP settings change that is the same"
11907 );
11908
11909 update_test_project_settings(cx, |project_settings| {
11910 project_settings.lsp.insert(
11911 language_server_name.into(),
11912 LspSettings {
11913 binary: None,
11914 settings: None,
11915 initialization_options: None,
11916 },
11917 );
11918 });
11919 cx.executor().run_until_parked();
11920 assert_eq!(
11921 server_restarts.load(atomic::Ordering::Acquire),
11922 2,
11923 "Should restart LSP server on another related LSP settings change"
11924 );
11925}
11926
11927#[gpui::test]
11928async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11929 init_test(cx, |_| {});
11930
11931 let mut cx = EditorLspTestContext::new_rust(
11932 lsp::ServerCapabilities {
11933 completion_provider: Some(lsp::CompletionOptions {
11934 trigger_characters: Some(vec![".".to_string()]),
11935 resolve_provider: Some(true),
11936 ..Default::default()
11937 }),
11938 ..Default::default()
11939 },
11940 cx,
11941 )
11942 .await;
11943
11944 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11945 cx.simulate_keystroke(".");
11946 let completion_item = lsp::CompletionItem {
11947 label: "some".into(),
11948 kind: Some(lsp::CompletionItemKind::SNIPPET),
11949 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11950 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11951 kind: lsp::MarkupKind::Markdown,
11952 value: "```rust\nSome(2)\n```".to_string(),
11953 })),
11954 deprecated: Some(false),
11955 sort_text: Some("fffffff2".to_string()),
11956 filter_text: Some("some".to_string()),
11957 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11958 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11959 range: lsp::Range {
11960 start: lsp::Position {
11961 line: 0,
11962 character: 22,
11963 },
11964 end: lsp::Position {
11965 line: 0,
11966 character: 22,
11967 },
11968 },
11969 new_text: "Some(2)".to_string(),
11970 })),
11971 additional_text_edits: Some(vec![lsp::TextEdit {
11972 range: lsp::Range {
11973 start: lsp::Position {
11974 line: 0,
11975 character: 20,
11976 },
11977 end: lsp::Position {
11978 line: 0,
11979 character: 22,
11980 },
11981 },
11982 new_text: "".to_string(),
11983 }]),
11984 ..Default::default()
11985 };
11986
11987 let closure_completion_item = completion_item.clone();
11988 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11989 let task_completion_item = closure_completion_item.clone();
11990 async move {
11991 Ok(Some(lsp::CompletionResponse::Array(vec![
11992 task_completion_item,
11993 ])))
11994 }
11995 });
11996
11997 request.next().await;
11998
11999 cx.condition(|editor, _| editor.context_menu_visible())
12000 .await;
12001 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12002 editor
12003 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12004 .unwrap()
12005 });
12006 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12007
12008 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12009 let task_completion_item = completion_item.clone();
12010 async move { Ok(task_completion_item) }
12011 })
12012 .next()
12013 .await
12014 .unwrap();
12015 apply_additional_edits.await.unwrap();
12016 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12017}
12018
12019#[gpui::test]
12020async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12021 init_test(cx, |_| {});
12022
12023 let mut cx = EditorLspTestContext::new_rust(
12024 lsp::ServerCapabilities {
12025 completion_provider: Some(lsp::CompletionOptions {
12026 trigger_characters: Some(vec![".".to_string()]),
12027 resolve_provider: Some(true),
12028 ..Default::default()
12029 }),
12030 ..Default::default()
12031 },
12032 cx,
12033 )
12034 .await;
12035
12036 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12037 cx.simulate_keystroke(".");
12038
12039 let item1 = lsp::CompletionItem {
12040 label: "method id()".to_string(),
12041 filter_text: Some("id".to_string()),
12042 detail: None,
12043 documentation: None,
12044 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12045 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12046 new_text: ".id".to_string(),
12047 })),
12048 ..lsp::CompletionItem::default()
12049 };
12050
12051 let item2 = lsp::CompletionItem {
12052 label: "other".to_string(),
12053 filter_text: Some("other".to_string()),
12054 detail: None,
12055 documentation: None,
12056 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12057 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12058 new_text: ".other".to_string(),
12059 })),
12060 ..lsp::CompletionItem::default()
12061 };
12062
12063 let item1 = item1.clone();
12064 cx.handle_request::<lsp::request::Completion, _, _>({
12065 let item1 = item1.clone();
12066 move |_, _, _| {
12067 let item1 = item1.clone();
12068 let item2 = item2.clone();
12069 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12070 }
12071 })
12072 .next()
12073 .await;
12074
12075 cx.condition(|editor, _| editor.context_menu_visible())
12076 .await;
12077 cx.update_editor(|editor, _, _| {
12078 let context_menu = editor.context_menu.borrow_mut();
12079 let context_menu = context_menu
12080 .as_ref()
12081 .expect("Should have the context menu deployed");
12082 match context_menu {
12083 CodeContextMenu::Completions(completions_menu) => {
12084 let completions = completions_menu.completions.borrow_mut();
12085 assert_eq!(
12086 completions
12087 .iter()
12088 .map(|completion| &completion.label.text)
12089 .collect::<Vec<_>>(),
12090 vec!["method id()", "other"]
12091 )
12092 }
12093 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12094 }
12095 });
12096
12097 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12098 let item1 = item1.clone();
12099 move |_, item_to_resolve, _| {
12100 let item1 = item1.clone();
12101 async move {
12102 if item1 == item_to_resolve {
12103 Ok(lsp::CompletionItem {
12104 label: "method id()".to_string(),
12105 filter_text: Some("id".to_string()),
12106 detail: Some("Now resolved!".to_string()),
12107 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12108 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12109 range: lsp::Range::new(
12110 lsp::Position::new(0, 22),
12111 lsp::Position::new(0, 22),
12112 ),
12113 new_text: ".id".to_string(),
12114 })),
12115 ..lsp::CompletionItem::default()
12116 })
12117 } else {
12118 Ok(item_to_resolve)
12119 }
12120 }
12121 }
12122 })
12123 .next()
12124 .await
12125 .unwrap();
12126 cx.run_until_parked();
12127
12128 cx.update_editor(|editor, window, cx| {
12129 editor.context_menu_next(&Default::default(), window, cx);
12130 });
12131
12132 cx.update_editor(|editor, _, _| {
12133 let context_menu = editor.context_menu.borrow_mut();
12134 let context_menu = context_menu
12135 .as_ref()
12136 .expect("Should have the context menu deployed");
12137 match context_menu {
12138 CodeContextMenu::Completions(completions_menu) => {
12139 let completions = completions_menu.completions.borrow_mut();
12140 assert_eq!(
12141 completions
12142 .iter()
12143 .map(|completion| &completion.label.text)
12144 .collect::<Vec<_>>(),
12145 vec!["method id() Now resolved!", "other"],
12146 "Should update first completion label, but not second as the filter text did not match."
12147 );
12148 }
12149 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12150 }
12151 });
12152}
12153
12154#[gpui::test]
12155async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12156 init_test(cx, |_| {});
12157
12158 let mut cx = EditorLspTestContext::new_rust(
12159 lsp::ServerCapabilities {
12160 completion_provider: Some(lsp::CompletionOptions {
12161 trigger_characters: Some(vec![".".to_string()]),
12162 resolve_provider: Some(true),
12163 ..Default::default()
12164 }),
12165 ..Default::default()
12166 },
12167 cx,
12168 )
12169 .await;
12170
12171 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12172 cx.simulate_keystroke(".");
12173
12174 let unresolved_item_1 = lsp::CompletionItem {
12175 label: "id".to_string(),
12176 filter_text: Some("id".to_string()),
12177 detail: None,
12178 documentation: None,
12179 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12180 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12181 new_text: ".id".to_string(),
12182 })),
12183 ..lsp::CompletionItem::default()
12184 };
12185 let resolved_item_1 = lsp::CompletionItem {
12186 additional_text_edits: Some(vec![lsp::TextEdit {
12187 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12188 new_text: "!!".to_string(),
12189 }]),
12190 ..unresolved_item_1.clone()
12191 };
12192 let unresolved_item_2 = lsp::CompletionItem {
12193 label: "other".to_string(),
12194 filter_text: Some("other".to_string()),
12195 detail: None,
12196 documentation: None,
12197 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12198 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12199 new_text: ".other".to_string(),
12200 })),
12201 ..lsp::CompletionItem::default()
12202 };
12203 let resolved_item_2 = lsp::CompletionItem {
12204 additional_text_edits: Some(vec![lsp::TextEdit {
12205 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12206 new_text: "??".to_string(),
12207 }]),
12208 ..unresolved_item_2.clone()
12209 };
12210
12211 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12212 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12213 cx.lsp
12214 .server
12215 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12216 let unresolved_item_1 = unresolved_item_1.clone();
12217 let resolved_item_1 = resolved_item_1.clone();
12218 let unresolved_item_2 = unresolved_item_2.clone();
12219 let resolved_item_2 = resolved_item_2.clone();
12220 let resolve_requests_1 = resolve_requests_1.clone();
12221 let resolve_requests_2 = resolve_requests_2.clone();
12222 move |unresolved_request, _| {
12223 let unresolved_item_1 = unresolved_item_1.clone();
12224 let resolved_item_1 = resolved_item_1.clone();
12225 let unresolved_item_2 = unresolved_item_2.clone();
12226 let resolved_item_2 = resolved_item_2.clone();
12227 let resolve_requests_1 = resolve_requests_1.clone();
12228 let resolve_requests_2 = resolve_requests_2.clone();
12229 async move {
12230 if unresolved_request == unresolved_item_1 {
12231 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12232 Ok(resolved_item_1.clone())
12233 } else if unresolved_request == unresolved_item_2 {
12234 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12235 Ok(resolved_item_2.clone())
12236 } else {
12237 panic!("Unexpected completion item {unresolved_request:?}")
12238 }
12239 }
12240 }
12241 })
12242 .detach();
12243
12244 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12245 let unresolved_item_1 = unresolved_item_1.clone();
12246 let unresolved_item_2 = unresolved_item_2.clone();
12247 async move {
12248 Ok(Some(lsp::CompletionResponse::Array(vec![
12249 unresolved_item_1,
12250 unresolved_item_2,
12251 ])))
12252 }
12253 })
12254 .next()
12255 .await;
12256
12257 cx.condition(|editor, _| editor.context_menu_visible())
12258 .await;
12259 cx.update_editor(|editor, _, _| {
12260 let context_menu = editor.context_menu.borrow_mut();
12261 let context_menu = context_menu
12262 .as_ref()
12263 .expect("Should have the context menu deployed");
12264 match context_menu {
12265 CodeContextMenu::Completions(completions_menu) => {
12266 let completions = completions_menu.completions.borrow_mut();
12267 assert_eq!(
12268 completions
12269 .iter()
12270 .map(|completion| &completion.label.text)
12271 .collect::<Vec<_>>(),
12272 vec!["id", "other"]
12273 )
12274 }
12275 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12276 }
12277 });
12278 cx.run_until_parked();
12279
12280 cx.update_editor(|editor, window, cx| {
12281 editor.context_menu_next(&ContextMenuNext, window, cx);
12282 });
12283 cx.run_until_parked();
12284 cx.update_editor(|editor, window, cx| {
12285 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12286 });
12287 cx.run_until_parked();
12288 cx.update_editor(|editor, window, cx| {
12289 editor.context_menu_next(&ContextMenuNext, window, cx);
12290 });
12291 cx.run_until_parked();
12292 cx.update_editor(|editor, window, cx| {
12293 editor
12294 .compose_completion(&ComposeCompletion::default(), window, cx)
12295 .expect("No task returned")
12296 })
12297 .await
12298 .expect("Completion failed");
12299 cx.run_until_parked();
12300
12301 cx.update_editor(|editor, _, cx| {
12302 assert_eq!(
12303 resolve_requests_1.load(atomic::Ordering::Acquire),
12304 1,
12305 "Should always resolve once despite multiple selections"
12306 );
12307 assert_eq!(
12308 resolve_requests_2.load(atomic::Ordering::Acquire),
12309 1,
12310 "Should always resolve once after multiple selections and applying the completion"
12311 );
12312 assert_eq!(
12313 editor.text(cx),
12314 "fn main() { let a = ??.other; }",
12315 "Should use resolved data when applying the completion"
12316 );
12317 });
12318}
12319
12320#[gpui::test]
12321async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12322 init_test(cx, |_| {});
12323
12324 let item_0 = lsp::CompletionItem {
12325 label: "abs".into(),
12326 insert_text: Some("abs".into()),
12327 data: Some(json!({ "very": "special"})),
12328 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12329 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12330 lsp::InsertReplaceEdit {
12331 new_text: "abs".to_string(),
12332 insert: lsp::Range::default(),
12333 replace: lsp::Range::default(),
12334 },
12335 )),
12336 ..lsp::CompletionItem::default()
12337 };
12338 let items = iter::once(item_0.clone())
12339 .chain((11..51).map(|i| lsp::CompletionItem {
12340 label: format!("item_{}", i),
12341 insert_text: Some(format!("item_{}", i)),
12342 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12343 ..lsp::CompletionItem::default()
12344 }))
12345 .collect::<Vec<_>>();
12346
12347 let default_commit_characters = vec!["?".to_string()];
12348 let default_data = json!({ "default": "data"});
12349 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12350 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12351 let default_edit_range = lsp::Range {
12352 start: lsp::Position {
12353 line: 0,
12354 character: 5,
12355 },
12356 end: lsp::Position {
12357 line: 0,
12358 character: 5,
12359 },
12360 };
12361
12362 let item_0_out = lsp::CompletionItem {
12363 commit_characters: Some(default_commit_characters.clone()),
12364 insert_text_format: Some(default_insert_text_format),
12365 ..item_0
12366 };
12367 let items_out = iter::once(item_0_out)
12368 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
12369 commit_characters: Some(default_commit_characters.clone()),
12370 data: Some(default_data.clone()),
12371 insert_text_mode: Some(default_insert_text_mode),
12372 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12373 range: default_edit_range,
12374 new_text: item.label.clone(),
12375 })),
12376 ..item.clone()
12377 }))
12378 .collect::<Vec<lsp::CompletionItem>>();
12379
12380 let mut cx = EditorLspTestContext::new_rust(
12381 lsp::ServerCapabilities {
12382 completion_provider: Some(lsp::CompletionOptions {
12383 trigger_characters: Some(vec![".".to_string()]),
12384 resolve_provider: Some(true),
12385 ..Default::default()
12386 }),
12387 ..Default::default()
12388 },
12389 cx,
12390 )
12391 .await;
12392
12393 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12394 cx.simulate_keystroke(".");
12395
12396 let completion_data = default_data.clone();
12397 let completion_characters = default_commit_characters.clone();
12398 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12399 let default_data = completion_data.clone();
12400 let default_commit_characters = completion_characters.clone();
12401 let items = items.clone();
12402 async move {
12403 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12404 items,
12405 item_defaults: Some(lsp::CompletionListItemDefaults {
12406 data: Some(default_data.clone()),
12407 commit_characters: Some(default_commit_characters.clone()),
12408 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12409 default_edit_range,
12410 )),
12411 insert_text_format: Some(default_insert_text_format),
12412 insert_text_mode: Some(default_insert_text_mode),
12413 }),
12414 ..lsp::CompletionList::default()
12415 })))
12416 }
12417 })
12418 .next()
12419 .await;
12420
12421 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12422 cx.lsp
12423 .server
12424 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12425 let closure_resolved_items = resolved_items.clone();
12426 move |item_to_resolve, _| {
12427 let closure_resolved_items = closure_resolved_items.clone();
12428 async move {
12429 closure_resolved_items.lock().push(item_to_resolve.clone());
12430 Ok(item_to_resolve)
12431 }
12432 }
12433 })
12434 .detach();
12435
12436 cx.condition(|editor, _| editor.context_menu_visible())
12437 .await;
12438 cx.run_until_parked();
12439 cx.update_editor(|editor, _, _| {
12440 let menu = editor.context_menu.borrow_mut();
12441 match menu.as_ref().expect("should have the completions menu") {
12442 CodeContextMenu::Completions(completions_menu) => {
12443 assert_eq!(
12444 completions_menu
12445 .entries
12446 .borrow()
12447 .iter()
12448 .map(|mat| mat.string.clone())
12449 .collect::<Vec<String>>(),
12450 items_out
12451 .iter()
12452 .map(|completion| completion.label.clone())
12453 .collect::<Vec<String>>()
12454 );
12455 }
12456 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12457 }
12458 });
12459 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12460 // with 4 from the end.
12461 assert_eq!(
12462 *resolved_items.lock(),
12463 [
12464 &items_out[0..16],
12465 &items_out[items_out.len() - 4..items_out.len()]
12466 ]
12467 .concat()
12468 .iter()
12469 .cloned()
12470 .collect::<Vec<lsp::CompletionItem>>()
12471 );
12472 resolved_items.lock().clear();
12473
12474 cx.update_editor(|editor, window, cx| {
12475 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12476 });
12477 cx.run_until_parked();
12478 // Completions that have already been resolved are skipped.
12479 assert_eq!(
12480 *resolved_items.lock(),
12481 items_out[items_out.len() - 16..items_out.len() - 4]
12482 .iter()
12483 .cloned()
12484 .collect::<Vec<lsp::CompletionItem>>()
12485 );
12486 resolved_items.lock().clear();
12487}
12488
12489#[gpui::test]
12490async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12491 init_test(cx, |_| {});
12492
12493 let mut cx = EditorLspTestContext::new(
12494 Language::new(
12495 LanguageConfig {
12496 matcher: LanguageMatcher {
12497 path_suffixes: vec!["jsx".into()],
12498 ..Default::default()
12499 },
12500 overrides: [(
12501 "element".into(),
12502 LanguageConfigOverride {
12503 word_characters: Override::Set(['-'].into_iter().collect()),
12504 ..Default::default()
12505 },
12506 )]
12507 .into_iter()
12508 .collect(),
12509 ..Default::default()
12510 },
12511 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12512 )
12513 .with_override_query("(jsx_self_closing_element) @element")
12514 .unwrap(),
12515 lsp::ServerCapabilities {
12516 completion_provider: Some(lsp::CompletionOptions {
12517 trigger_characters: Some(vec![":".to_string()]),
12518 ..Default::default()
12519 }),
12520 ..Default::default()
12521 },
12522 cx,
12523 )
12524 .await;
12525
12526 cx.lsp
12527 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12528 Ok(Some(lsp::CompletionResponse::Array(vec![
12529 lsp::CompletionItem {
12530 label: "bg-blue".into(),
12531 ..Default::default()
12532 },
12533 lsp::CompletionItem {
12534 label: "bg-red".into(),
12535 ..Default::default()
12536 },
12537 lsp::CompletionItem {
12538 label: "bg-yellow".into(),
12539 ..Default::default()
12540 },
12541 ])))
12542 });
12543
12544 cx.set_state(r#"<p class="bgˇ" />"#);
12545
12546 // Trigger completion when typing a dash, because the dash is an extra
12547 // word character in the 'element' scope, which contains the cursor.
12548 cx.simulate_keystroke("-");
12549 cx.executor().run_until_parked();
12550 cx.update_editor(|editor, _, _| {
12551 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12552 {
12553 assert_eq!(
12554 completion_menu_entries(&menu),
12555 &["bg-red", "bg-blue", "bg-yellow"]
12556 );
12557 } else {
12558 panic!("expected completion menu to be open");
12559 }
12560 });
12561
12562 cx.simulate_keystroke("l");
12563 cx.executor().run_until_parked();
12564 cx.update_editor(|editor, _, _| {
12565 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12566 {
12567 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12568 } else {
12569 panic!("expected completion menu to be open");
12570 }
12571 });
12572
12573 // When filtering completions, consider the character after the '-' to
12574 // be the start of a subword.
12575 cx.set_state(r#"<p class="yelˇ" />"#);
12576 cx.simulate_keystroke("l");
12577 cx.executor().run_until_parked();
12578 cx.update_editor(|editor, _, _| {
12579 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12580 {
12581 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12582 } else {
12583 panic!("expected completion menu to be open");
12584 }
12585 });
12586}
12587
12588fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12589 let entries = menu.entries.borrow();
12590 entries.iter().map(|mat| mat.string.clone()).collect()
12591}
12592
12593#[gpui::test]
12594async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12595 init_test(cx, |settings| {
12596 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12597 FormatterList(vec![Formatter::Prettier].into()),
12598 ))
12599 });
12600
12601 let fs = FakeFs::new(cx.executor());
12602 fs.insert_file(path!("/file.ts"), Default::default()).await;
12603
12604 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12605 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12606
12607 language_registry.add(Arc::new(Language::new(
12608 LanguageConfig {
12609 name: "TypeScript".into(),
12610 matcher: LanguageMatcher {
12611 path_suffixes: vec!["ts".to_string()],
12612 ..Default::default()
12613 },
12614 ..Default::default()
12615 },
12616 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12617 )));
12618 update_test_language_settings(cx, |settings| {
12619 settings.defaults.prettier = Some(PrettierSettings {
12620 allowed: true,
12621 ..PrettierSettings::default()
12622 });
12623 });
12624
12625 let test_plugin = "test_plugin";
12626 let _ = language_registry.register_fake_lsp(
12627 "TypeScript",
12628 FakeLspAdapter {
12629 prettier_plugins: vec![test_plugin],
12630 ..Default::default()
12631 },
12632 );
12633
12634 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12635 let buffer = project
12636 .update(cx, |project, cx| {
12637 project.open_local_buffer(path!("/file.ts"), cx)
12638 })
12639 .await
12640 .unwrap();
12641
12642 let buffer_text = "one\ntwo\nthree\n";
12643 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12644 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12645 editor.update_in(cx, |editor, window, cx| {
12646 editor.set_text(buffer_text, window, cx)
12647 });
12648
12649 editor
12650 .update_in(cx, |editor, window, cx| {
12651 editor.perform_format(
12652 project.clone(),
12653 FormatTrigger::Manual,
12654 FormatTarget::Buffers,
12655 window,
12656 cx,
12657 )
12658 })
12659 .unwrap()
12660 .await;
12661 assert_eq!(
12662 editor.update(cx, |editor, cx| editor.text(cx)),
12663 buffer_text.to_string() + prettier_format_suffix,
12664 "Test prettier formatting was not applied to the original buffer text",
12665 );
12666
12667 update_test_language_settings(cx, |settings| {
12668 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12669 });
12670 let format = editor.update_in(cx, |editor, window, cx| {
12671 editor.perform_format(
12672 project.clone(),
12673 FormatTrigger::Manual,
12674 FormatTarget::Buffers,
12675 window,
12676 cx,
12677 )
12678 });
12679 format.await.unwrap();
12680 assert_eq!(
12681 editor.update(cx, |editor, cx| editor.text(cx)),
12682 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12683 "Autoformatting (via test prettier) was not applied to the original buffer text",
12684 );
12685}
12686
12687#[gpui::test]
12688async fn test_addition_reverts(cx: &mut TestAppContext) {
12689 init_test(cx, |_| {});
12690 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12691 let base_text = indoc! {r#"
12692 struct Row;
12693 struct Row1;
12694 struct Row2;
12695
12696 struct Row4;
12697 struct Row5;
12698 struct Row6;
12699
12700 struct Row8;
12701 struct Row9;
12702 struct Row10;"#};
12703
12704 // When addition hunks are not adjacent to carets, no hunk revert is performed
12705 assert_hunk_revert(
12706 indoc! {r#"struct Row;
12707 struct Row1;
12708 struct Row1.1;
12709 struct Row1.2;
12710 struct Row2;ˇ
12711
12712 struct Row4;
12713 struct Row5;
12714 struct Row6;
12715
12716 struct Row8;
12717 ˇstruct Row9;
12718 struct Row9.1;
12719 struct Row9.2;
12720 struct Row9.3;
12721 struct Row10;"#},
12722 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12723 indoc! {r#"struct Row;
12724 struct Row1;
12725 struct Row1.1;
12726 struct Row1.2;
12727 struct Row2;ˇ
12728
12729 struct Row4;
12730 struct Row5;
12731 struct Row6;
12732
12733 struct Row8;
12734 ˇstruct Row9;
12735 struct Row9.1;
12736 struct Row9.2;
12737 struct Row9.3;
12738 struct Row10;"#},
12739 base_text,
12740 &mut cx,
12741 );
12742 // Same for selections
12743 assert_hunk_revert(
12744 indoc! {r#"struct Row;
12745 struct Row1;
12746 struct Row2;
12747 struct Row2.1;
12748 struct Row2.2;
12749 «ˇ
12750 struct Row4;
12751 struct» Row5;
12752 «struct Row6;
12753 ˇ»
12754 struct Row9.1;
12755 struct Row9.2;
12756 struct Row9.3;
12757 struct Row8;
12758 struct Row9;
12759 struct Row10;"#},
12760 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12761 indoc! {r#"struct Row;
12762 struct Row1;
12763 struct Row2;
12764 struct Row2.1;
12765 struct Row2.2;
12766 «ˇ
12767 struct Row4;
12768 struct» Row5;
12769 «struct Row6;
12770 ˇ»
12771 struct Row9.1;
12772 struct Row9.2;
12773 struct Row9.3;
12774 struct Row8;
12775 struct Row9;
12776 struct Row10;"#},
12777 base_text,
12778 &mut cx,
12779 );
12780
12781 // When carets and selections intersect the addition hunks, those are reverted.
12782 // Adjacent carets got merged.
12783 assert_hunk_revert(
12784 indoc! {r#"struct Row;
12785 ˇ// something on the top
12786 struct Row1;
12787 struct Row2;
12788 struct Roˇw3.1;
12789 struct Row2.2;
12790 struct Row2.3;ˇ
12791
12792 struct Row4;
12793 struct ˇRow5.1;
12794 struct Row5.2;
12795 struct «Rowˇ»5.3;
12796 struct Row5;
12797 struct Row6;
12798 ˇ
12799 struct Row9.1;
12800 struct «Rowˇ»9.2;
12801 struct «ˇRow»9.3;
12802 struct Row8;
12803 struct Row9;
12804 «ˇ// something on bottom»
12805 struct Row10;"#},
12806 vec![
12807 DiffHunkStatusKind::Added,
12808 DiffHunkStatusKind::Added,
12809 DiffHunkStatusKind::Added,
12810 DiffHunkStatusKind::Added,
12811 DiffHunkStatusKind::Added,
12812 ],
12813 indoc! {r#"struct Row;
12814 ˇstruct Row1;
12815 struct Row2;
12816 ˇ
12817 struct Row4;
12818 ˇstruct Row5;
12819 struct Row6;
12820 ˇ
12821 ˇstruct Row8;
12822 struct Row9;
12823 ˇstruct Row10;"#},
12824 base_text,
12825 &mut cx,
12826 );
12827}
12828
12829#[gpui::test]
12830async fn test_modification_reverts(cx: &mut TestAppContext) {
12831 init_test(cx, |_| {});
12832 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12833 let base_text = indoc! {r#"
12834 struct Row;
12835 struct Row1;
12836 struct Row2;
12837
12838 struct Row4;
12839 struct Row5;
12840 struct Row6;
12841
12842 struct Row8;
12843 struct Row9;
12844 struct Row10;"#};
12845
12846 // Modification hunks behave the same as the addition ones.
12847 assert_hunk_revert(
12848 indoc! {r#"struct Row;
12849 struct Row1;
12850 struct Row33;
12851 ˇ
12852 struct Row4;
12853 struct Row5;
12854 struct Row6;
12855 ˇ
12856 struct Row99;
12857 struct Row9;
12858 struct Row10;"#},
12859 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12860 indoc! {r#"struct Row;
12861 struct Row1;
12862 struct Row33;
12863 ˇ
12864 struct Row4;
12865 struct Row5;
12866 struct Row6;
12867 ˇ
12868 struct Row99;
12869 struct Row9;
12870 struct Row10;"#},
12871 base_text,
12872 &mut cx,
12873 );
12874 assert_hunk_revert(
12875 indoc! {r#"struct Row;
12876 struct Row1;
12877 struct Row33;
12878 «ˇ
12879 struct Row4;
12880 struct» Row5;
12881 «struct Row6;
12882 ˇ»
12883 struct Row99;
12884 struct Row9;
12885 struct Row10;"#},
12886 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12887 indoc! {r#"struct Row;
12888 struct Row1;
12889 struct Row33;
12890 «ˇ
12891 struct Row4;
12892 struct» Row5;
12893 «struct Row6;
12894 ˇ»
12895 struct Row99;
12896 struct Row9;
12897 struct Row10;"#},
12898 base_text,
12899 &mut cx,
12900 );
12901
12902 assert_hunk_revert(
12903 indoc! {r#"ˇstruct Row1.1;
12904 struct Row1;
12905 «ˇstr»uct Row22;
12906
12907 struct ˇRow44;
12908 struct Row5;
12909 struct «Rˇ»ow66;ˇ
12910
12911 «struˇ»ct Row88;
12912 struct Row9;
12913 struct Row1011;ˇ"#},
12914 vec![
12915 DiffHunkStatusKind::Modified,
12916 DiffHunkStatusKind::Modified,
12917 DiffHunkStatusKind::Modified,
12918 DiffHunkStatusKind::Modified,
12919 DiffHunkStatusKind::Modified,
12920 DiffHunkStatusKind::Modified,
12921 ],
12922 indoc! {r#"struct Row;
12923 ˇstruct Row1;
12924 struct Row2;
12925 ˇ
12926 struct Row4;
12927 ˇstruct Row5;
12928 struct Row6;
12929 ˇ
12930 struct Row8;
12931 ˇstruct Row9;
12932 struct Row10;ˇ"#},
12933 base_text,
12934 &mut cx,
12935 );
12936}
12937
12938#[gpui::test]
12939async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12940 init_test(cx, |_| {});
12941 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12942 let base_text = indoc! {r#"
12943 one
12944
12945 two
12946 three
12947 "#};
12948
12949 cx.set_head_text(base_text);
12950 cx.set_state("\nˇ\n");
12951 cx.executor().run_until_parked();
12952 cx.update_editor(|editor, _window, cx| {
12953 editor.expand_selected_diff_hunks(cx);
12954 });
12955 cx.executor().run_until_parked();
12956 cx.update_editor(|editor, window, cx| {
12957 editor.backspace(&Default::default(), window, cx);
12958 });
12959 cx.run_until_parked();
12960 cx.assert_state_with_diff(
12961 indoc! {r#"
12962
12963 - two
12964 - threeˇ
12965 +
12966 "#}
12967 .to_string(),
12968 );
12969}
12970
12971#[gpui::test]
12972async fn test_deletion_reverts(cx: &mut TestAppContext) {
12973 init_test(cx, |_| {});
12974 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12975 let base_text = indoc! {r#"struct Row;
12976struct Row1;
12977struct Row2;
12978
12979struct Row4;
12980struct Row5;
12981struct Row6;
12982
12983struct Row8;
12984struct Row9;
12985struct Row10;"#};
12986
12987 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12988 assert_hunk_revert(
12989 indoc! {r#"struct Row;
12990 struct Row2;
12991
12992 ˇstruct Row4;
12993 struct Row5;
12994 struct Row6;
12995 ˇ
12996 struct Row8;
12997 struct Row10;"#},
12998 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12999 indoc! {r#"struct Row;
13000 struct Row2;
13001
13002 ˇstruct Row4;
13003 struct Row5;
13004 struct Row6;
13005 ˇ
13006 struct Row8;
13007 struct Row10;"#},
13008 base_text,
13009 &mut cx,
13010 );
13011 assert_hunk_revert(
13012 indoc! {r#"struct Row;
13013 struct Row2;
13014
13015 «ˇstruct Row4;
13016 struct» Row5;
13017 «struct Row6;
13018 ˇ»
13019 struct Row8;
13020 struct Row10;"#},
13021 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13022 indoc! {r#"struct Row;
13023 struct Row2;
13024
13025 «ˇstruct Row4;
13026 struct» Row5;
13027 «struct Row6;
13028 ˇ»
13029 struct Row8;
13030 struct Row10;"#},
13031 base_text,
13032 &mut cx,
13033 );
13034
13035 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13036 assert_hunk_revert(
13037 indoc! {r#"struct Row;
13038 ˇstruct Row2;
13039
13040 struct Row4;
13041 struct Row5;
13042 struct Row6;
13043
13044 struct Row8;ˇ
13045 struct Row10;"#},
13046 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13047 indoc! {r#"struct Row;
13048 struct Row1;
13049 ˇstruct Row2;
13050
13051 struct Row4;
13052 struct Row5;
13053 struct Row6;
13054
13055 struct Row8;ˇ
13056 struct Row9;
13057 struct Row10;"#},
13058 base_text,
13059 &mut cx,
13060 );
13061 assert_hunk_revert(
13062 indoc! {r#"struct Row;
13063 struct Row2«ˇ;
13064 struct Row4;
13065 struct» Row5;
13066 «struct Row6;
13067
13068 struct Row8;ˇ»
13069 struct Row10;"#},
13070 vec![
13071 DiffHunkStatusKind::Deleted,
13072 DiffHunkStatusKind::Deleted,
13073 DiffHunkStatusKind::Deleted,
13074 ],
13075 indoc! {r#"struct Row;
13076 struct Row1;
13077 struct Row2«ˇ;
13078
13079 struct Row4;
13080 struct» Row5;
13081 «struct Row6;
13082
13083 struct Row8;ˇ»
13084 struct Row9;
13085 struct Row10;"#},
13086 base_text,
13087 &mut cx,
13088 );
13089}
13090
13091#[gpui::test]
13092async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13093 init_test(cx, |_| {});
13094
13095 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13096 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13097 let base_text_3 =
13098 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13099
13100 let text_1 = edit_first_char_of_every_line(base_text_1);
13101 let text_2 = edit_first_char_of_every_line(base_text_2);
13102 let text_3 = edit_first_char_of_every_line(base_text_3);
13103
13104 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13105 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13106 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13107
13108 let multibuffer = cx.new(|cx| {
13109 let mut multibuffer = MultiBuffer::new(ReadWrite);
13110 multibuffer.push_excerpts(
13111 buffer_1.clone(),
13112 [
13113 ExcerptRange {
13114 context: Point::new(0, 0)..Point::new(3, 0),
13115 primary: None,
13116 },
13117 ExcerptRange {
13118 context: Point::new(5, 0)..Point::new(7, 0),
13119 primary: None,
13120 },
13121 ExcerptRange {
13122 context: Point::new(9, 0)..Point::new(10, 4),
13123 primary: None,
13124 },
13125 ],
13126 cx,
13127 );
13128 multibuffer.push_excerpts(
13129 buffer_2.clone(),
13130 [
13131 ExcerptRange {
13132 context: Point::new(0, 0)..Point::new(3, 0),
13133 primary: None,
13134 },
13135 ExcerptRange {
13136 context: Point::new(5, 0)..Point::new(7, 0),
13137 primary: None,
13138 },
13139 ExcerptRange {
13140 context: Point::new(9, 0)..Point::new(10, 4),
13141 primary: None,
13142 },
13143 ],
13144 cx,
13145 );
13146 multibuffer.push_excerpts(
13147 buffer_3.clone(),
13148 [
13149 ExcerptRange {
13150 context: Point::new(0, 0)..Point::new(3, 0),
13151 primary: None,
13152 },
13153 ExcerptRange {
13154 context: Point::new(5, 0)..Point::new(7, 0),
13155 primary: None,
13156 },
13157 ExcerptRange {
13158 context: Point::new(9, 0)..Point::new(10, 4),
13159 primary: None,
13160 },
13161 ],
13162 cx,
13163 );
13164 multibuffer
13165 });
13166
13167 let fs = FakeFs::new(cx.executor());
13168 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13169 let (editor, cx) = cx
13170 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13171 editor.update_in(cx, |editor, _window, cx| {
13172 for (buffer, diff_base) in [
13173 (buffer_1.clone(), base_text_1),
13174 (buffer_2.clone(), base_text_2),
13175 (buffer_3.clone(), base_text_3),
13176 ] {
13177 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13178 editor
13179 .buffer
13180 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13181 }
13182 });
13183 cx.executor().run_until_parked();
13184
13185 editor.update_in(cx, |editor, window, cx| {
13186 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}");
13187 editor.select_all(&SelectAll, window, cx);
13188 editor.git_restore(&Default::default(), window, cx);
13189 });
13190 cx.executor().run_until_parked();
13191
13192 // When all ranges are selected, all buffer hunks are reverted.
13193 editor.update(cx, |editor, cx| {
13194 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");
13195 });
13196 buffer_1.update(cx, |buffer, _| {
13197 assert_eq!(buffer.text(), base_text_1);
13198 });
13199 buffer_2.update(cx, |buffer, _| {
13200 assert_eq!(buffer.text(), base_text_2);
13201 });
13202 buffer_3.update(cx, |buffer, _| {
13203 assert_eq!(buffer.text(), base_text_3);
13204 });
13205
13206 editor.update_in(cx, |editor, window, cx| {
13207 editor.undo(&Default::default(), window, cx);
13208 });
13209
13210 editor.update_in(cx, |editor, window, cx| {
13211 editor.change_selections(None, window, cx, |s| {
13212 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13213 });
13214 editor.git_restore(&Default::default(), window, cx);
13215 });
13216
13217 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13218 // but not affect buffer_2 and its related excerpts.
13219 editor.update(cx, |editor, cx| {
13220 assert_eq!(
13221 editor.text(cx),
13222 "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}"
13223 );
13224 });
13225 buffer_1.update(cx, |buffer, _| {
13226 assert_eq!(buffer.text(), base_text_1);
13227 });
13228 buffer_2.update(cx, |buffer, _| {
13229 assert_eq!(
13230 buffer.text(),
13231 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13232 );
13233 });
13234 buffer_3.update(cx, |buffer, _| {
13235 assert_eq!(
13236 buffer.text(),
13237 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13238 );
13239 });
13240
13241 fn edit_first_char_of_every_line(text: &str) -> String {
13242 text.split('\n')
13243 .map(|line| format!("X{}", &line[1..]))
13244 .collect::<Vec<_>>()
13245 .join("\n")
13246 }
13247}
13248
13249#[gpui::test]
13250async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13251 init_test(cx, |_| {});
13252
13253 let cols = 4;
13254 let rows = 10;
13255 let sample_text_1 = sample_text(rows, cols, 'a');
13256 assert_eq!(
13257 sample_text_1,
13258 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13259 );
13260 let sample_text_2 = sample_text(rows, cols, 'l');
13261 assert_eq!(
13262 sample_text_2,
13263 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13264 );
13265 let sample_text_3 = sample_text(rows, cols, 'v');
13266 assert_eq!(
13267 sample_text_3,
13268 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13269 );
13270
13271 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13272 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13273 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13274
13275 let multi_buffer = cx.new(|cx| {
13276 let mut multibuffer = MultiBuffer::new(ReadWrite);
13277 multibuffer.push_excerpts(
13278 buffer_1.clone(),
13279 [
13280 ExcerptRange {
13281 context: Point::new(0, 0)..Point::new(3, 0),
13282 primary: None,
13283 },
13284 ExcerptRange {
13285 context: Point::new(5, 0)..Point::new(7, 0),
13286 primary: None,
13287 },
13288 ExcerptRange {
13289 context: Point::new(9, 0)..Point::new(10, 4),
13290 primary: None,
13291 },
13292 ],
13293 cx,
13294 );
13295 multibuffer.push_excerpts(
13296 buffer_2.clone(),
13297 [
13298 ExcerptRange {
13299 context: Point::new(0, 0)..Point::new(3, 0),
13300 primary: None,
13301 },
13302 ExcerptRange {
13303 context: Point::new(5, 0)..Point::new(7, 0),
13304 primary: None,
13305 },
13306 ExcerptRange {
13307 context: Point::new(9, 0)..Point::new(10, 4),
13308 primary: None,
13309 },
13310 ],
13311 cx,
13312 );
13313 multibuffer.push_excerpts(
13314 buffer_3.clone(),
13315 [
13316 ExcerptRange {
13317 context: Point::new(0, 0)..Point::new(3, 0),
13318 primary: None,
13319 },
13320 ExcerptRange {
13321 context: Point::new(5, 0)..Point::new(7, 0),
13322 primary: None,
13323 },
13324 ExcerptRange {
13325 context: Point::new(9, 0)..Point::new(10, 4),
13326 primary: None,
13327 },
13328 ],
13329 cx,
13330 );
13331 multibuffer
13332 });
13333
13334 let fs = FakeFs::new(cx.executor());
13335 fs.insert_tree(
13336 "/a",
13337 json!({
13338 "main.rs": sample_text_1,
13339 "other.rs": sample_text_2,
13340 "lib.rs": sample_text_3,
13341 }),
13342 )
13343 .await;
13344 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13345 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13346 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13347 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13348 Editor::new(
13349 EditorMode::Full,
13350 multi_buffer,
13351 Some(project.clone()),
13352 true,
13353 window,
13354 cx,
13355 )
13356 });
13357 let multibuffer_item_id = workspace
13358 .update(cx, |workspace, window, cx| {
13359 assert!(
13360 workspace.active_item(cx).is_none(),
13361 "active item should be None before the first item is added"
13362 );
13363 workspace.add_item_to_active_pane(
13364 Box::new(multi_buffer_editor.clone()),
13365 None,
13366 true,
13367 window,
13368 cx,
13369 );
13370 let active_item = workspace
13371 .active_item(cx)
13372 .expect("should have an active item after adding the multi buffer");
13373 assert!(
13374 !active_item.is_singleton(cx),
13375 "A multi buffer was expected to active after adding"
13376 );
13377 active_item.item_id()
13378 })
13379 .unwrap();
13380 cx.executor().run_until_parked();
13381
13382 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13383 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13384 s.select_ranges(Some(1..2))
13385 });
13386 editor.open_excerpts(&OpenExcerpts, window, cx);
13387 });
13388 cx.executor().run_until_parked();
13389 let first_item_id = workspace
13390 .update(cx, |workspace, window, cx| {
13391 let active_item = workspace
13392 .active_item(cx)
13393 .expect("should have an active item after navigating into the 1st buffer");
13394 let first_item_id = active_item.item_id();
13395 assert_ne!(
13396 first_item_id, multibuffer_item_id,
13397 "Should navigate into the 1st buffer and activate it"
13398 );
13399 assert!(
13400 active_item.is_singleton(cx),
13401 "New active item should be a singleton buffer"
13402 );
13403 assert_eq!(
13404 active_item
13405 .act_as::<Editor>(cx)
13406 .expect("should have navigated into an editor for the 1st buffer")
13407 .read(cx)
13408 .text(cx),
13409 sample_text_1
13410 );
13411
13412 workspace
13413 .go_back(workspace.active_pane().downgrade(), window, cx)
13414 .detach_and_log_err(cx);
13415
13416 first_item_id
13417 })
13418 .unwrap();
13419 cx.executor().run_until_parked();
13420 workspace
13421 .update(cx, |workspace, _, cx| {
13422 let active_item = workspace
13423 .active_item(cx)
13424 .expect("should have an active item after navigating back");
13425 assert_eq!(
13426 active_item.item_id(),
13427 multibuffer_item_id,
13428 "Should navigate back to the multi buffer"
13429 );
13430 assert!(!active_item.is_singleton(cx));
13431 })
13432 .unwrap();
13433
13434 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13435 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13436 s.select_ranges(Some(39..40))
13437 });
13438 editor.open_excerpts(&OpenExcerpts, window, cx);
13439 });
13440 cx.executor().run_until_parked();
13441 let second_item_id = workspace
13442 .update(cx, |workspace, window, cx| {
13443 let active_item = workspace
13444 .active_item(cx)
13445 .expect("should have an active item after navigating into the 2nd buffer");
13446 let second_item_id = active_item.item_id();
13447 assert_ne!(
13448 second_item_id, multibuffer_item_id,
13449 "Should navigate away from the multibuffer"
13450 );
13451 assert_ne!(
13452 second_item_id, first_item_id,
13453 "Should navigate into the 2nd buffer and activate it"
13454 );
13455 assert!(
13456 active_item.is_singleton(cx),
13457 "New active item should be a singleton buffer"
13458 );
13459 assert_eq!(
13460 active_item
13461 .act_as::<Editor>(cx)
13462 .expect("should have navigated into an editor")
13463 .read(cx)
13464 .text(cx),
13465 sample_text_2
13466 );
13467
13468 workspace
13469 .go_back(workspace.active_pane().downgrade(), window, cx)
13470 .detach_and_log_err(cx);
13471
13472 second_item_id
13473 })
13474 .unwrap();
13475 cx.executor().run_until_parked();
13476 workspace
13477 .update(cx, |workspace, _, cx| {
13478 let active_item = workspace
13479 .active_item(cx)
13480 .expect("should have an active item after navigating back from the 2nd buffer");
13481 assert_eq!(
13482 active_item.item_id(),
13483 multibuffer_item_id,
13484 "Should navigate back from the 2nd buffer to the multi buffer"
13485 );
13486 assert!(!active_item.is_singleton(cx));
13487 })
13488 .unwrap();
13489
13490 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13491 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13492 s.select_ranges(Some(70..70))
13493 });
13494 editor.open_excerpts(&OpenExcerpts, window, cx);
13495 });
13496 cx.executor().run_until_parked();
13497 workspace
13498 .update(cx, |workspace, window, cx| {
13499 let active_item = workspace
13500 .active_item(cx)
13501 .expect("should have an active item after navigating into the 3rd buffer");
13502 let third_item_id = active_item.item_id();
13503 assert_ne!(
13504 third_item_id, multibuffer_item_id,
13505 "Should navigate into the 3rd buffer and activate it"
13506 );
13507 assert_ne!(third_item_id, first_item_id);
13508 assert_ne!(third_item_id, second_item_id);
13509 assert!(
13510 active_item.is_singleton(cx),
13511 "New active item should be a singleton buffer"
13512 );
13513 assert_eq!(
13514 active_item
13515 .act_as::<Editor>(cx)
13516 .expect("should have navigated into an editor")
13517 .read(cx)
13518 .text(cx),
13519 sample_text_3
13520 );
13521
13522 workspace
13523 .go_back(workspace.active_pane().downgrade(), window, cx)
13524 .detach_and_log_err(cx);
13525 })
13526 .unwrap();
13527 cx.executor().run_until_parked();
13528 workspace
13529 .update(cx, |workspace, _, cx| {
13530 let active_item = workspace
13531 .active_item(cx)
13532 .expect("should have an active item after navigating back from the 3rd buffer");
13533 assert_eq!(
13534 active_item.item_id(),
13535 multibuffer_item_id,
13536 "Should navigate back from the 3rd buffer to the multi buffer"
13537 );
13538 assert!(!active_item.is_singleton(cx));
13539 })
13540 .unwrap();
13541}
13542
13543#[gpui::test]
13544async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13545 init_test(cx, |_| {});
13546
13547 let mut cx = EditorTestContext::new(cx).await;
13548
13549 let diff_base = r#"
13550 use some::mod;
13551
13552 const A: u32 = 42;
13553
13554 fn main() {
13555 println!("hello");
13556
13557 println!("world");
13558 }
13559 "#
13560 .unindent();
13561
13562 cx.set_state(
13563 &r#"
13564 use some::modified;
13565
13566 ˇ
13567 fn main() {
13568 println!("hello there");
13569
13570 println!("around the");
13571 println!("world");
13572 }
13573 "#
13574 .unindent(),
13575 );
13576
13577 cx.set_head_text(&diff_base);
13578 executor.run_until_parked();
13579
13580 cx.update_editor(|editor, window, cx| {
13581 editor.go_to_next_hunk(&GoToHunk, window, cx);
13582 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13583 });
13584 executor.run_until_parked();
13585 cx.assert_state_with_diff(
13586 r#"
13587 use some::modified;
13588
13589
13590 fn main() {
13591 - println!("hello");
13592 + ˇ println!("hello there");
13593
13594 println!("around the");
13595 println!("world");
13596 }
13597 "#
13598 .unindent(),
13599 );
13600
13601 cx.update_editor(|editor, window, cx| {
13602 for _ in 0..2 {
13603 editor.go_to_next_hunk(&GoToHunk, window, cx);
13604 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13605 }
13606 });
13607 executor.run_until_parked();
13608 cx.assert_state_with_diff(
13609 r#"
13610 - use some::mod;
13611 + ˇuse some::modified;
13612
13613
13614 fn main() {
13615 - println!("hello");
13616 + println!("hello there");
13617
13618 + println!("around the");
13619 println!("world");
13620 }
13621 "#
13622 .unindent(),
13623 );
13624
13625 cx.update_editor(|editor, window, cx| {
13626 editor.go_to_next_hunk(&GoToHunk, window, cx);
13627 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13628 });
13629 executor.run_until_parked();
13630 cx.assert_state_with_diff(
13631 r#"
13632 - use some::mod;
13633 + use some::modified;
13634
13635 - const A: u32 = 42;
13636 ˇ
13637 fn main() {
13638 - println!("hello");
13639 + println!("hello there");
13640
13641 + println!("around the");
13642 println!("world");
13643 }
13644 "#
13645 .unindent(),
13646 );
13647
13648 cx.update_editor(|editor, window, cx| {
13649 editor.cancel(&Cancel, window, cx);
13650 });
13651
13652 cx.assert_state_with_diff(
13653 r#"
13654 use some::modified;
13655
13656 ˇ
13657 fn main() {
13658 println!("hello there");
13659
13660 println!("around the");
13661 println!("world");
13662 }
13663 "#
13664 .unindent(),
13665 );
13666}
13667
13668#[gpui::test]
13669async fn test_diff_base_change_with_expanded_diff_hunks(
13670 executor: BackgroundExecutor,
13671 cx: &mut TestAppContext,
13672) {
13673 init_test(cx, |_| {});
13674
13675 let mut cx = EditorTestContext::new(cx).await;
13676
13677 let diff_base = r#"
13678 use some::mod1;
13679 use some::mod2;
13680
13681 const A: u32 = 42;
13682 const B: u32 = 42;
13683 const C: u32 = 42;
13684
13685 fn main() {
13686 println!("hello");
13687
13688 println!("world");
13689 }
13690 "#
13691 .unindent();
13692
13693 cx.set_state(
13694 &r#"
13695 use some::mod2;
13696
13697 const A: u32 = 42;
13698 const C: u32 = 42;
13699
13700 fn main(ˇ) {
13701 //println!("hello");
13702
13703 println!("world");
13704 //
13705 //
13706 }
13707 "#
13708 .unindent(),
13709 );
13710
13711 cx.set_head_text(&diff_base);
13712 executor.run_until_parked();
13713
13714 cx.update_editor(|editor, window, cx| {
13715 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13716 });
13717 executor.run_until_parked();
13718 cx.assert_state_with_diff(
13719 r#"
13720 - use some::mod1;
13721 use some::mod2;
13722
13723 const A: u32 = 42;
13724 - const B: u32 = 42;
13725 const C: u32 = 42;
13726
13727 fn main(ˇ) {
13728 - println!("hello");
13729 + //println!("hello");
13730
13731 println!("world");
13732 + //
13733 + //
13734 }
13735 "#
13736 .unindent(),
13737 );
13738
13739 cx.set_head_text("new diff base!");
13740 executor.run_until_parked();
13741 cx.assert_state_with_diff(
13742 r#"
13743 - new diff base!
13744 + use some::mod2;
13745 +
13746 + const A: u32 = 42;
13747 + const C: u32 = 42;
13748 +
13749 + fn main(ˇ) {
13750 + //println!("hello");
13751 +
13752 + println!("world");
13753 + //
13754 + //
13755 + }
13756 "#
13757 .unindent(),
13758 );
13759}
13760
13761#[gpui::test]
13762async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13763 init_test(cx, |_| {});
13764
13765 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13766 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13767 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13768 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13769 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13770 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13771
13772 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13773 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13774 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13775
13776 let multi_buffer = cx.new(|cx| {
13777 let mut multibuffer = MultiBuffer::new(ReadWrite);
13778 multibuffer.push_excerpts(
13779 buffer_1.clone(),
13780 [
13781 ExcerptRange {
13782 context: Point::new(0, 0)..Point::new(3, 0),
13783 primary: None,
13784 },
13785 ExcerptRange {
13786 context: Point::new(5, 0)..Point::new(7, 0),
13787 primary: None,
13788 },
13789 ExcerptRange {
13790 context: Point::new(9, 0)..Point::new(10, 3),
13791 primary: None,
13792 },
13793 ],
13794 cx,
13795 );
13796 multibuffer.push_excerpts(
13797 buffer_2.clone(),
13798 [
13799 ExcerptRange {
13800 context: Point::new(0, 0)..Point::new(3, 0),
13801 primary: None,
13802 },
13803 ExcerptRange {
13804 context: Point::new(5, 0)..Point::new(7, 0),
13805 primary: None,
13806 },
13807 ExcerptRange {
13808 context: Point::new(9, 0)..Point::new(10, 3),
13809 primary: None,
13810 },
13811 ],
13812 cx,
13813 );
13814 multibuffer.push_excerpts(
13815 buffer_3.clone(),
13816 [
13817 ExcerptRange {
13818 context: Point::new(0, 0)..Point::new(3, 0),
13819 primary: None,
13820 },
13821 ExcerptRange {
13822 context: Point::new(5, 0)..Point::new(7, 0),
13823 primary: None,
13824 },
13825 ExcerptRange {
13826 context: Point::new(9, 0)..Point::new(10, 3),
13827 primary: None,
13828 },
13829 ],
13830 cx,
13831 );
13832 multibuffer
13833 });
13834
13835 let editor = cx.add_window(|window, cx| {
13836 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13837 });
13838 editor
13839 .update(cx, |editor, _window, cx| {
13840 for (buffer, diff_base) in [
13841 (buffer_1.clone(), file_1_old),
13842 (buffer_2.clone(), file_2_old),
13843 (buffer_3.clone(), file_3_old),
13844 ] {
13845 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13846 editor
13847 .buffer
13848 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13849 }
13850 })
13851 .unwrap();
13852
13853 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13854 cx.run_until_parked();
13855
13856 cx.assert_editor_state(
13857 &"
13858 ˇaaa
13859 ccc
13860 ddd
13861
13862 ggg
13863 hhh
13864
13865
13866 lll
13867 mmm
13868 NNN
13869
13870 qqq
13871 rrr
13872
13873 uuu
13874 111
13875 222
13876 333
13877
13878 666
13879 777
13880
13881 000
13882 !!!"
13883 .unindent(),
13884 );
13885
13886 cx.update_editor(|editor, window, cx| {
13887 editor.select_all(&SelectAll, window, cx);
13888 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13889 });
13890 cx.executor().run_until_parked();
13891
13892 cx.assert_state_with_diff(
13893 "
13894 «aaa
13895 - bbb
13896 ccc
13897 ddd
13898
13899 ggg
13900 hhh
13901
13902
13903 lll
13904 mmm
13905 - nnn
13906 + NNN
13907
13908 qqq
13909 rrr
13910
13911 uuu
13912 111
13913 222
13914 333
13915
13916 + 666
13917 777
13918
13919 000
13920 !!!ˇ»"
13921 .unindent(),
13922 );
13923}
13924
13925#[gpui::test]
13926async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13927 init_test(cx, |_| {});
13928
13929 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13930 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13931
13932 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13933 let multi_buffer = cx.new(|cx| {
13934 let mut multibuffer = MultiBuffer::new(ReadWrite);
13935 multibuffer.push_excerpts(
13936 buffer.clone(),
13937 [
13938 ExcerptRange {
13939 context: Point::new(0, 0)..Point::new(2, 0),
13940 primary: None,
13941 },
13942 ExcerptRange {
13943 context: Point::new(4, 0)..Point::new(7, 0),
13944 primary: None,
13945 },
13946 ExcerptRange {
13947 context: Point::new(9, 0)..Point::new(10, 0),
13948 primary: None,
13949 },
13950 ],
13951 cx,
13952 );
13953 multibuffer
13954 });
13955
13956 let editor = cx.add_window(|window, cx| {
13957 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13958 });
13959 editor
13960 .update(cx, |editor, _window, cx| {
13961 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13962 editor
13963 .buffer
13964 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13965 })
13966 .unwrap();
13967
13968 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13969 cx.run_until_parked();
13970
13971 cx.update_editor(|editor, window, cx| {
13972 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13973 });
13974 cx.executor().run_until_parked();
13975
13976 // When the start of a hunk coincides with the start of its excerpt,
13977 // the hunk is expanded. When the start of a a hunk is earlier than
13978 // the start of its excerpt, the hunk is not expanded.
13979 cx.assert_state_with_diff(
13980 "
13981 ˇaaa
13982 - bbb
13983 + BBB
13984
13985 - ddd
13986 - eee
13987 + DDD
13988 + EEE
13989 fff
13990
13991 iii
13992 "
13993 .unindent(),
13994 );
13995}
13996
13997#[gpui::test]
13998async fn test_edits_around_expanded_insertion_hunks(
13999 executor: BackgroundExecutor,
14000 cx: &mut TestAppContext,
14001) {
14002 init_test(cx, |_| {});
14003
14004 let mut cx = EditorTestContext::new(cx).await;
14005
14006 let diff_base = r#"
14007 use some::mod1;
14008 use some::mod2;
14009
14010 const A: u32 = 42;
14011
14012 fn main() {
14013 println!("hello");
14014
14015 println!("world");
14016 }
14017 "#
14018 .unindent();
14019 executor.run_until_parked();
14020 cx.set_state(
14021 &r#"
14022 use some::mod1;
14023 use some::mod2;
14024
14025 const A: u32 = 42;
14026 const B: u32 = 42;
14027 const C: u32 = 42;
14028 ˇ
14029
14030 fn main() {
14031 println!("hello");
14032
14033 println!("world");
14034 }
14035 "#
14036 .unindent(),
14037 );
14038
14039 cx.set_head_text(&diff_base);
14040 executor.run_until_parked();
14041
14042 cx.update_editor(|editor, window, cx| {
14043 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14044 });
14045 executor.run_until_parked();
14046
14047 cx.assert_state_with_diff(
14048 r#"
14049 use some::mod1;
14050 use some::mod2;
14051
14052 const A: u32 = 42;
14053 + const B: u32 = 42;
14054 + const C: u32 = 42;
14055 + ˇ
14056
14057 fn main() {
14058 println!("hello");
14059
14060 println!("world");
14061 }
14062 "#
14063 .unindent(),
14064 );
14065
14066 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14067 executor.run_until_parked();
14068
14069 cx.assert_state_with_diff(
14070 r#"
14071 use some::mod1;
14072 use some::mod2;
14073
14074 const A: u32 = 42;
14075 + const B: u32 = 42;
14076 + const C: u32 = 42;
14077 + const D: u32 = 42;
14078 + ˇ
14079
14080 fn main() {
14081 println!("hello");
14082
14083 println!("world");
14084 }
14085 "#
14086 .unindent(),
14087 );
14088
14089 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14090 executor.run_until_parked();
14091
14092 cx.assert_state_with_diff(
14093 r#"
14094 use some::mod1;
14095 use some::mod2;
14096
14097 const A: u32 = 42;
14098 + const B: u32 = 42;
14099 + const C: u32 = 42;
14100 + const D: u32 = 42;
14101 + const E: u32 = 42;
14102 + ˇ
14103
14104 fn main() {
14105 println!("hello");
14106
14107 println!("world");
14108 }
14109 "#
14110 .unindent(),
14111 );
14112
14113 cx.update_editor(|editor, window, cx| {
14114 editor.delete_line(&DeleteLine, window, cx);
14115 });
14116 executor.run_until_parked();
14117
14118 cx.assert_state_with_diff(
14119 r#"
14120 use some::mod1;
14121 use some::mod2;
14122
14123 const A: u32 = 42;
14124 + const B: u32 = 42;
14125 + const C: u32 = 42;
14126 + const D: u32 = 42;
14127 + const E: u32 = 42;
14128 ˇ
14129 fn main() {
14130 println!("hello");
14131
14132 println!("world");
14133 }
14134 "#
14135 .unindent(),
14136 );
14137
14138 cx.update_editor(|editor, window, cx| {
14139 editor.move_up(&MoveUp, window, cx);
14140 editor.delete_line(&DeleteLine, window, cx);
14141 editor.move_up(&MoveUp, window, cx);
14142 editor.delete_line(&DeleteLine, window, cx);
14143 editor.move_up(&MoveUp, window, cx);
14144 editor.delete_line(&DeleteLine, window, cx);
14145 });
14146 executor.run_until_parked();
14147 cx.assert_state_with_diff(
14148 r#"
14149 use some::mod1;
14150 use some::mod2;
14151
14152 const A: u32 = 42;
14153 + const B: u32 = 42;
14154 ˇ
14155 fn main() {
14156 println!("hello");
14157
14158 println!("world");
14159 }
14160 "#
14161 .unindent(),
14162 );
14163
14164 cx.update_editor(|editor, window, cx| {
14165 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14166 editor.delete_line(&DeleteLine, window, cx);
14167 });
14168 executor.run_until_parked();
14169 cx.assert_state_with_diff(
14170 r#"
14171 ˇ
14172 fn main() {
14173 println!("hello");
14174
14175 println!("world");
14176 }
14177 "#
14178 .unindent(),
14179 );
14180}
14181
14182#[gpui::test]
14183async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14184 init_test(cx, |_| {});
14185
14186 let mut cx = EditorTestContext::new(cx).await;
14187 cx.set_head_text(indoc! { "
14188 one
14189 two
14190 three
14191 four
14192 five
14193 "
14194 });
14195 cx.set_state(indoc! { "
14196 one
14197 ˇthree
14198 five
14199 "});
14200 cx.run_until_parked();
14201 cx.update_editor(|editor, window, cx| {
14202 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14203 });
14204 cx.assert_state_with_diff(
14205 indoc! { "
14206 one
14207 - two
14208 ˇthree
14209 - four
14210 five
14211 "}
14212 .to_string(),
14213 );
14214 cx.update_editor(|editor, window, cx| {
14215 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14216 });
14217
14218 cx.assert_state_with_diff(
14219 indoc! { "
14220 one
14221 ˇthree
14222 five
14223 "}
14224 .to_string(),
14225 );
14226
14227 cx.set_state(indoc! { "
14228 one
14229 ˇTWO
14230 three
14231 four
14232 five
14233 "});
14234 cx.run_until_parked();
14235 cx.update_editor(|editor, window, cx| {
14236 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14237 });
14238
14239 cx.assert_state_with_diff(
14240 indoc! { "
14241 one
14242 - two
14243 + ˇTWO
14244 three
14245 four
14246 five
14247 "}
14248 .to_string(),
14249 );
14250 cx.update_editor(|editor, window, cx| {
14251 editor.move_up(&Default::default(), window, cx);
14252 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14253 });
14254 cx.assert_state_with_diff(
14255 indoc! { "
14256 one
14257 ˇTWO
14258 three
14259 four
14260 five
14261 "}
14262 .to_string(),
14263 );
14264}
14265
14266#[gpui::test]
14267async fn test_edits_around_expanded_deletion_hunks(
14268 executor: BackgroundExecutor,
14269 cx: &mut TestAppContext,
14270) {
14271 init_test(cx, |_| {});
14272
14273 let mut cx = EditorTestContext::new(cx).await;
14274
14275 let diff_base = r#"
14276 use some::mod1;
14277 use some::mod2;
14278
14279 const A: u32 = 42;
14280 const B: u32 = 42;
14281 const C: u32 = 42;
14282
14283
14284 fn main() {
14285 println!("hello");
14286
14287 println!("world");
14288 }
14289 "#
14290 .unindent();
14291 executor.run_until_parked();
14292 cx.set_state(
14293 &r#"
14294 use some::mod1;
14295 use some::mod2;
14296
14297 ˇconst B: u32 = 42;
14298 const C: u32 = 42;
14299
14300
14301 fn main() {
14302 println!("hello");
14303
14304 println!("world");
14305 }
14306 "#
14307 .unindent(),
14308 );
14309
14310 cx.set_head_text(&diff_base);
14311 executor.run_until_parked();
14312
14313 cx.update_editor(|editor, window, cx| {
14314 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14315 });
14316 executor.run_until_parked();
14317
14318 cx.assert_state_with_diff(
14319 r#"
14320 use some::mod1;
14321 use some::mod2;
14322
14323 - const A: u32 = 42;
14324 ˇconst B: u32 = 42;
14325 const C: u32 = 42;
14326
14327
14328 fn main() {
14329 println!("hello");
14330
14331 println!("world");
14332 }
14333 "#
14334 .unindent(),
14335 );
14336
14337 cx.update_editor(|editor, window, cx| {
14338 editor.delete_line(&DeleteLine, window, cx);
14339 });
14340 executor.run_until_parked();
14341 cx.assert_state_with_diff(
14342 r#"
14343 use some::mod1;
14344 use some::mod2;
14345
14346 - const A: u32 = 42;
14347 - const B: u32 = 42;
14348 ˇconst C: u32 = 42;
14349
14350
14351 fn main() {
14352 println!("hello");
14353
14354 println!("world");
14355 }
14356 "#
14357 .unindent(),
14358 );
14359
14360 cx.update_editor(|editor, window, cx| {
14361 editor.delete_line(&DeleteLine, window, cx);
14362 });
14363 executor.run_until_parked();
14364 cx.assert_state_with_diff(
14365 r#"
14366 use some::mod1;
14367 use some::mod2;
14368
14369 - const A: u32 = 42;
14370 - const B: u32 = 42;
14371 - const C: u32 = 42;
14372 ˇ
14373
14374 fn main() {
14375 println!("hello");
14376
14377 println!("world");
14378 }
14379 "#
14380 .unindent(),
14381 );
14382
14383 cx.update_editor(|editor, window, cx| {
14384 editor.handle_input("replacement", window, cx);
14385 });
14386 executor.run_until_parked();
14387 cx.assert_state_with_diff(
14388 r#"
14389 use some::mod1;
14390 use some::mod2;
14391
14392 - const A: u32 = 42;
14393 - const B: u32 = 42;
14394 - const C: u32 = 42;
14395 -
14396 + replacementˇ
14397
14398 fn main() {
14399 println!("hello");
14400
14401 println!("world");
14402 }
14403 "#
14404 .unindent(),
14405 );
14406}
14407
14408#[gpui::test]
14409async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14410 init_test(cx, |_| {});
14411
14412 let mut cx = EditorTestContext::new(cx).await;
14413
14414 let base_text = r#"
14415 one
14416 two
14417 three
14418 four
14419 five
14420 "#
14421 .unindent();
14422 executor.run_until_parked();
14423 cx.set_state(
14424 &r#"
14425 one
14426 two
14427 fˇour
14428 five
14429 "#
14430 .unindent(),
14431 );
14432
14433 cx.set_head_text(&base_text);
14434 executor.run_until_parked();
14435
14436 cx.update_editor(|editor, window, cx| {
14437 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14438 });
14439 executor.run_until_parked();
14440
14441 cx.assert_state_with_diff(
14442 r#"
14443 one
14444 two
14445 - three
14446 fˇour
14447 five
14448 "#
14449 .unindent(),
14450 );
14451
14452 cx.update_editor(|editor, window, cx| {
14453 editor.backspace(&Backspace, window, cx);
14454 editor.backspace(&Backspace, window, cx);
14455 });
14456 executor.run_until_parked();
14457 cx.assert_state_with_diff(
14458 r#"
14459 one
14460 two
14461 - threeˇ
14462 - four
14463 + our
14464 five
14465 "#
14466 .unindent(),
14467 );
14468}
14469
14470#[gpui::test]
14471async fn test_edit_after_expanded_modification_hunk(
14472 executor: BackgroundExecutor,
14473 cx: &mut TestAppContext,
14474) {
14475 init_test(cx, |_| {});
14476
14477 let mut cx = EditorTestContext::new(cx).await;
14478
14479 let diff_base = r#"
14480 use some::mod1;
14481 use some::mod2;
14482
14483 const A: u32 = 42;
14484 const B: u32 = 42;
14485 const C: u32 = 42;
14486 const D: u32 = 42;
14487
14488
14489 fn main() {
14490 println!("hello");
14491
14492 println!("world");
14493 }"#
14494 .unindent();
14495
14496 cx.set_state(
14497 &r#"
14498 use some::mod1;
14499 use some::mod2;
14500
14501 const A: u32 = 42;
14502 const B: u32 = 42;
14503 const C: u32 = 43ˇ
14504 const D: u32 = 42;
14505
14506
14507 fn main() {
14508 println!("hello");
14509
14510 println!("world");
14511 }"#
14512 .unindent(),
14513 );
14514
14515 cx.set_head_text(&diff_base);
14516 executor.run_until_parked();
14517 cx.update_editor(|editor, window, cx| {
14518 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14519 });
14520 executor.run_until_parked();
14521
14522 cx.assert_state_with_diff(
14523 r#"
14524 use some::mod1;
14525 use some::mod2;
14526
14527 const A: u32 = 42;
14528 const B: u32 = 42;
14529 - const C: u32 = 42;
14530 + const C: u32 = 43ˇ
14531 const D: u32 = 42;
14532
14533
14534 fn main() {
14535 println!("hello");
14536
14537 println!("world");
14538 }"#
14539 .unindent(),
14540 );
14541
14542 cx.update_editor(|editor, window, cx| {
14543 editor.handle_input("\nnew_line\n", window, cx);
14544 });
14545 executor.run_until_parked();
14546
14547 cx.assert_state_with_diff(
14548 r#"
14549 use some::mod1;
14550 use some::mod2;
14551
14552 const A: u32 = 42;
14553 const B: u32 = 42;
14554 - const C: u32 = 42;
14555 + const C: u32 = 43
14556 + new_line
14557 + ˇ
14558 const D: u32 = 42;
14559
14560
14561 fn main() {
14562 println!("hello");
14563
14564 println!("world");
14565 }"#
14566 .unindent(),
14567 );
14568}
14569
14570#[gpui::test]
14571async fn test_stage_and_unstage_added_file_hunk(
14572 executor: BackgroundExecutor,
14573 cx: &mut TestAppContext,
14574) {
14575 init_test(cx, |_| {});
14576
14577 let mut cx = EditorTestContext::new(cx).await;
14578 cx.update_editor(|editor, _, cx| {
14579 editor.set_expand_all_diff_hunks(cx);
14580 });
14581
14582 let working_copy = r#"
14583 ˇfn main() {
14584 println!("hello, world!");
14585 }
14586 "#
14587 .unindent();
14588
14589 cx.set_state(&working_copy);
14590 executor.run_until_parked();
14591
14592 cx.assert_state_with_diff(
14593 r#"
14594 + ˇfn main() {
14595 + println!("hello, world!");
14596 + }
14597 "#
14598 .unindent(),
14599 );
14600 cx.assert_index_text(None);
14601
14602 cx.update_editor(|editor, window, cx| {
14603 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14604 });
14605 executor.run_until_parked();
14606 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14607 cx.assert_state_with_diff(
14608 r#"
14609 + ˇfn main() {
14610 + println!("hello, world!");
14611 + }
14612 "#
14613 .unindent(),
14614 );
14615
14616 cx.update_editor(|editor, window, cx| {
14617 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14618 });
14619 executor.run_until_parked();
14620 cx.assert_index_text(None);
14621}
14622
14623async fn setup_indent_guides_editor(
14624 text: &str,
14625 cx: &mut TestAppContext,
14626) -> (BufferId, EditorTestContext) {
14627 init_test(cx, |_| {});
14628
14629 let mut cx = EditorTestContext::new(cx).await;
14630
14631 let buffer_id = cx.update_editor(|editor, window, cx| {
14632 editor.set_text(text, window, cx);
14633 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14634
14635 buffer_ids[0]
14636 });
14637
14638 (buffer_id, cx)
14639}
14640
14641fn assert_indent_guides(
14642 range: Range<u32>,
14643 expected: Vec<IndentGuide>,
14644 active_indices: Option<Vec<usize>>,
14645 cx: &mut EditorTestContext,
14646) {
14647 let indent_guides = cx.update_editor(|editor, window, cx| {
14648 let snapshot = editor.snapshot(window, cx).display_snapshot;
14649 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14650 editor,
14651 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14652 true,
14653 &snapshot,
14654 cx,
14655 );
14656
14657 indent_guides.sort_by(|a, b| {
14658 a.depth.cmp(&b.depth).then(
14659 a.start_row
14660 .cmp(&b.start_row)
14661 .then(a.end_row.cmp(&b.end_row)),
14662 )
14663 });
14664 indent_guides
14665 });
14666
14667 if let Some(expected) = active_indices {
14668 let active_indices = cx.update_editor(|editor, window, cx| {
14669 let snapshot = editor.snapshot(window, cx).display_snapshot;
14670 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14671 });
14672
14673 assert_eq!(
14674 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14675 expected,
14676 "Active indent guide indices do not match"
14677 );
14678 }
14679
14680 assert_eq!(indent_guides, expected, "Indent guides do not match");
14681}
14682
14683fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14684 IndentGuide {
14685 buffer_id,
14686 start_row: MultiBufferRow(start_row),
14687 end_row: MultiBufferRow(end_row),
14688 depth,
14689 tab_size: 4,
14690 settings: IndentGuideSettings {
14691 enabled: true,
14692 line_width: 1,
14693 active_line_width: 1,
14694 ..Default::default()
14695 },
14696 }
14697}
14698
14699#[gpui::test]
14700async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14701 let (buffer_id, mut cx) = setup_indent_guides_editor(
14702 &"
14703 fn main() {
14704 let a = 1;
14705 }"
14706 .unindent(),
14707 cx,
14708 )
14709 .await;
14710
14711 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14712}
14713
14714#[gpui::test]
14715async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14716 let (buffer_id, mut cx) = setup_indent_guides_editor(
14717 &"
14718 fn main() {
14719 let a = 1;
14720 let b = 2;
14721 }"
14722 .unindent(),
14723 cx,
14724 )
14725 .await;
14726
14727 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14728}
14729
14730#[gpui::test]
14731async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14732 let (buffer_id, mut cx) = setup_indent_guides_editor(
14733 &"
14734 fn main() {
14735 let a = 1;
14736 if a == 3 {
14737 let b = 2;
14738 } else {
14739 let c = 3;
14740 }
14741 }"
14742 .unindent(),
14743 cx,
14744 )
14745 .await;
14746
14747 assert_indent_guides(
14748 0..8,
14749 vec![
14750 indent_guide(buffer_id, 1, 6, 0),
14751 indent_guide(buffer_id, 3, 3, 1),
14752 indent_guide(buffer_id, 5, 5, 1),
14753 ],
14754 None,
14755 &mut cx,
14756 );
14757}
14758
14759#[gpui::test]
14760async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14761 let (buffer_id, mut cx) = setup_indent_guides_editor(
14762 &"
14763 fn main() {
14764 let a = 1;
14765 let b = 2;
14766 let c = 3;
14767 }"
14768 .unindent(),
14769 cx,
14770 )
14771 .await;
14772
14773 assert_indent_guides(
14774 0..5,
14775 vec![
14776 indent_guide(buffer_id, 1, 3, 0),
14777 indent_guide(buffer_id, 2, 2, 1),
14778 ],
14779 None,
14780 &mut cx,
14781 );
14782}
14783
14784#[gpui::test]
14785async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14786 let (buffer_id, mut cx) = setup_indent_guides_editor(
14787 &"
14788 fn main() {
14789 let a = 1;
14790
14791 let c = 3;
14792 }"
14793 .unindent(),
14794 cx,
14795 )
14796 .await;
14797
14798 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14799}
14800
14801#[gpui::test]
14802async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14803 let (buffer_id, mut cx) = setup_indent_guides_editor(
14804 &"
14805 fn main() {
14806 let a = 1;
14807
14808 let c = 3;
14809
14810 if a == 3 {
14811 let b = 2;
14812 } else {
14813 let c = 3;
14814 }
14815 }"
14816 .unindent(),
14817 cx,
14818 )
14819 .await;
14820
14821 assert_indent_guides(
14822 0..11,
14823 vec![
14824 indent_guide(buffer_id, 1, 9, 0),
14825 indent_guide(buffer_id, 6, 6, 1),
14826 indent_guide(buffer_id, 8, 8, 1),
14827 ],
14828 None,
14829 &mut cx,
14830 );
14831}
14832
14833#[gpui::test]
14834async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14835 let (buffer_id, mut cx) = setup_indent_guides_editor(
14836 &"
14837 fn main() {
14838 let a = 1;
14839
14840 let c = 3;
14841
14842 if a == 3 {
14843 let b = 2;
14844 } else {
14845 let c = 3;
14846 }
14847 }"
14848 .unindent(),
14849 cx,
14850 )
14851 .await;
14852
14853 assert_indent_guides(
14854 1..11,
14855 vec![
14856 indent_guide(buffer_id, 1, 9, 0),
14857 indent_guide(buffer_id, 6, 6, 1),
14858 indent_guide(buffer_id, 8, 8, 1),
14859 ],
14860 None,
14861 &mut cx,
14862 );
14863}
14864
14865#[gpui::test]
14866async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14867 let (buffer_id, mut cx) = setup_indent_guides_editor(
14868 &"
14869 fn main() {
14870 let a = 1;
14871
14872 let c = 3;
14873
14874 if a == 3 {
14875 let b = 2;
14876 } else {
14877 let c = 3;
14878 }
14879 }"
14880 .unindent(),
14881 cx,
14882 )
14883 .await;
14884
14885 assert_indent_guides(
14886 1..10,
14887 vec![
14888 indent_guide(buffer_id, 1, 9, 0),
14889 indent_guide(buffer_id, 6, 6, 1),
14890 indent_guide(buffer_id, 8, 8, 1),
14891 ],
14892 None,
14893 &mut cx,
14894 );
14895}
14896
14897#[gpui::test]
14898async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14899 let (buffer_id, mut cx) = setup_indent_guides_editor(
14900 &"
14901 block1
14902 block2
14903 block3
14904 block4
14905 block2
14906 block1
14907 block1"
14908 .unindent(),
14909 cx,
14910 )
14911 .await;
14912
14913 assert_indent_guides(
14914 1..10,
14915 vec![
14916 indent_guide(buffer_id, 1, 4, 0),
14917 indent_guide(buffer_id, 2, 3, 1),
14918 indent_guide(buffer_id, 3, 3, 2),
14919 ],
14920 None,
14921 &mut cx,
14922 );
14923}
14924
14925#[gpui::test]
14926async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14927 let (buffer_id, mut cx) = setup_indent_guides_editor(
14928 &"
14929 block1
14930 block2
14931 block3
14932
14933 block1
14934 block1"
14935 .unindent(),
14936 cx,
14937 )
14938 .await;
14939
14940 assert_indent_guides(
14941 0..6,
14942 vec![
14943 indent_guide(buffer_id, 1, 2, 0),
14944 indent_guide(buffer_id, 2, 2, 1),
14945 ],
14946 None,
14947 &mut cx,
14948 );
14949}
14950
14951#[gpui::test]
14952async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14953 let (buffer_id, mut cx) = setup_indent_guides_editor(
14954 &"
14955 block1
14956
14957
14958
14959 block2
14960 "
14961 .unindent(),
14962 cx,
14963 )
14964 .await;
14965
14966 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14967}
14968
14969#[gpui::test]
14970async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14971 let (buffer_id, mut cx) = setup_indent_guides_editor(
14972 &"
14973 def a:
14974 \tb = 3
14975 \tif True:
14976 \t\tc = 4
14977 \t\td = 5
14978 \tprint(b)
14979 "
14980 .unindent(),
14981 cx,
14982 )
14983 .await;
14984
14985 assert_indent_guides(
14986 0..6,
14987 vec![
14988 indent_guide(buffer_id, 1, 6, 0),
14989 indent_guide(buffer_id, 3, 4, 1),
14990 ],
14991 None,
14992 &mut cx,
14993 );
14994}
14995
14996#[gpui::test]
14997async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14998 let (buffer_id, mut cx) = setup_indent_guides_editor(
14999 &"
15000 fn main() {
15001 let a = 1;
15002 }"
15003 .unindent(),
15004 cx,
15005 )
15006 .await;
15007
15008 cx.update_editor(|editor, window, cx| {
15009 editor.change_selections(None, window, cx, |s| {
15010 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15011 });
15012 });
15013
15014 assert_indent_guides(
15015 0..3,
15016 vec![indent_guide(buffer_id, 1, 1, 0)],
15017 Some(vec![0]),
15018 &mut cx,
15019 );
15020}
15021
15022#[gpui::test]
15023async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15024 let (buffer_id, mut cx) = setup_indent_guides_editor(
15025 &"
15026 fn main() {
15027 if 1 == 2 {
15028 let a = 1;
15029 }
15030 }"
15031 .unindent(),
15032 cx,
15033 )
15034 .await;
15035
15036 cx.update_editor(|editor, window, cx| {
15037 editor.change_selections(None, window, cx, |s| {
15038 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15039 });
15040 });
15041
15042 assert_indent_guides(
15043 0..4,
15044 vec![
15045 indent_guide(buffer_id, 1, 3, 0),
15046 indent_guide(buffer_id, 2, 2, 1),
15047 ],
15048 Some(vec![1]),
15049 &mut cx,
15050 );
15051
15052 cx.update_editor(|editor, window, cx| {
15053 editor.change_selections(None, window, cx, |s| {
15054 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15055 });
15056 });
15057
15058 assert_indent_guides(
15059 0..4,
15060 vec![
15061 indent_guide(buffer_id, 1, 3, 0),
15062 indent_guide(buffer_id, 2, 2, 1),
15063 ],
15064 Some(vec![1]),
15065 &mut cx,
15066 );
15067
15068 cx.update_editor(|editor, window, cx| {
15069 editor.change_selections(None, window, cx, |s| {
15070 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15071 });
15072 });
15073
15074 assert_indent_guides(
15075 0..4,
15076 vec![
15077 indent_guide(buffer_id, 1, 3, 0),
15078 indent_guide(buffer_id, 2, 2, 1),
15079 ],
15080 Some(vec![0]),
15081 &mut cx,
15082 );
15083}
15084
15085#[gpui::test]
15086async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15087 let (buffer_id, mut cx) = setup_indent_guides_editor(
15088 &"
15089 fn main() {
15090 let a = 1;
15091
15092 let b = 2;
15093 }"
15094 .unindent(),
15095 cx,
15096 )
15097 .await;
15098
15099 cx.update_editor(|editor, window, cx| {
15100 editor.change_selections(None, window, cx, |s| {
15101 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15102 });
15103 });
15104
15105 assert_indent_guides(
15106 0..5,
15107 vec![indent_guide(buffer_id, 1, 3, 0)],
15108 Some(vec![0]),
15109 &mut cx,
15110 );
15111}
15112
15113#[gpui::test]
15114async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15115 let (buffer_id, mut cx) = setup_indent_guides_editor(
15116 &"
15117 def m:
15118 a = 1
15119 pass"
15120 .unindent(),
15121 cx,
15122 )
15123 .await;
15124
15125 cx.update_editor(|editor, window, cx| {
15126 editor.change_selections(None, window, cx, |s| {
15127 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15128 });
15129 });
15130
15131 assert_indent_guides(
15132 0..3,
15133 vec![indent_guide(buffer_id, 1, 2, 0)],
15134 Some(vec![0]),
15135 &mut cx,
15136 );
15137}
15138
15139#[gpui::test]
15140async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15141 init_test(cx, |_| {});
15142 let mut cx = EditorTestContext::new(cx).await;
15143 let text = indoc! {
15144 "
15145 impl A {
15146 fn b() {
15147 0;
15148 3;
15149 5;
15150 6;
15151 7;
15152 }
15153 }
15154 "
15155 };
15156 let base_text = indoc! {
15157 "
15158 impl A {
15159 fn b() {
15160 0;
15161 1;
15162 2;
15163 3;
15164 4;
15165 }
15166 fn c() {
15167 5;
15168 6;
15169 7;
15170 }
15171 }
15172 "
15173 };
15174
15175 cx.update_editor(|editor, window, cx| {
15176 editor.set_text(text, window, cx);
15177
15178 editor.buffer().update(cx, |multibuffer, cx| {
15179 let buffer = multibuffer.as_singleton().unwrap();
15180 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15181
15182 multibuffer.set_all_diff_hunks_expanded(cx);
15183 multibuffer.add_diff(diff, cx);
15184
15185 buffer.read(cx).remote_id()
15186 })
15187 });
15188 cx.run_until_parked();
15189
15190 cx.assert_state_with_diff(
15191 indoc! { "
15192 impl A {
15193 fn b() {
15194 0;
15195 - 1;
15196 - 2;
15197 3;
15198 - 4;
15199 - }
15200 - fn c() {
15201 5;
15202 6;
15203 7;
15204 }
15205 }
15206 ˇ"
15207 }
15208 .to_string(),
15209 );
15210
15211 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15212 editor
15213 .snapshot(window, cx)
15214 .buffer_snapshot
15215 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15216 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15217 .collect::<Vec<_>>()
15218 });
15219 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15220 assert_eq!(
15221 actual_guides,
15222 vec![
15223 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15224 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15225 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15226 ]
15227 );
15228}
15229
15230#[gpui::test]
15231async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15232 init_test(cx, |_| {});
15233 let mut cx = EditorTestContext::new(cx).await;
15234
15235 let diff_base = r#"
15236 a
15237 b
15238 c
15239 "#
15240 .unindent();
15241
15242 cx.set_state(
15243 &r#"
15244 ˇA
15245 b
15246 C
15247 "#
15248 .unindent(),
15249 );
15250 cx.set_head_text(&diff_base);
15251 cx.update_editor(|editor, window, cx| {
15252 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15253 });
15254 executor.run_until_parked();
15255
15256 let both_hunks_expanded = r#"
15257 - a
15258 + ˇA
15259 b
15260 - c
15261 + C
15262 "#
15263 .unindent();
15264
15265 cx.assert_state_with_diff(both_hunks_expanded.clone());
15266
15267 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15268 let snapshot = editor.snapshot(window, cx);
15269 let hunks = editor
15270 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15271 .collect::<Vec<_>>();
15272 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15273 let buffer_id = hunks[0].buffer_id;
15274 hunks
15275 .into_iter()
15276 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15277 .collect::<Vec<_>>()
15278 });
15279 assert_eq!(hunk_ranges.len(), 2);
15280
15281 cx.update_editor(|editor, _, cx| {
15282 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15283 });
15284 executor.run_until_parked();
15285
15286 let second_hunk_expanded = r#"
15287 ˇA
15288 b
15289 - c
15290 + C
15291 "#
15292 .unindent();
15293
15294 cx.assert_state_with_diff(second_hunk_expanded);
15295
15296 cx.update_editor(|editor, _, cx| {
15297 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15298 });
15299 executor.run_until_parked();
15300
15301 cx.assert_state_with_diff(both_hunks_expanded.clone());
15302
15303 cx.update_editor(|editor, _, cx| {
15304 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15305 });
15306 executor.run_until_parked();
15307
15308 let first_hunk_expanded = r#"
15309 - a
15310 + ˇA
15311 b
15312 C
15313 "#
15314 .unindent();
15315
15316 cx.assert_state_with_diff(first_hunk_expanded);
15317
15318 cx.update_editor(|editor, _, cx| {
15319 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15320 });
15321 executor.run_until_parked();
15322
15323 cx.assert_state_with_diff(both_hunks_expanded);
15324
15325 cx.set_state(
15326 &r#"
15327 ˇA
15328 b
15329 "#
15330 .unindent(),
15331 );
15332 cx.run_until_parked();
15333
15334 // TODO this cursor position seems bad
15335 cx.assert_state_with_diff(
15336 r#"
15337 - ˇa
15338 + A
15339 b
15340 "#
15341 .unindent(),
15342 );
15343
15344 cx.update_editor(|editor, window, cx| {
15345 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15346 });
15347
15348 cx.assert_state_with_diff(
15349 r#"
15350 - ˇa
15351 + A
15352 b
15353 - c
15354 "#
15355 .unindent(),
15356 );
15357
15358 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15359 let snapshot = editor.snapshot(window, cx);
15360 let hunks = editor
15361 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15362 .collect::<Vec<_>>();
15363 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15364 let buffer_id = hunks[0].buffer_id;
15365 hunks
15366 .into_iter()
15367 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15368 .collect::<Vec<_>>()
15369 });
15370 assert_eq!(hunk_ranges.len(), 2);
15371
15372 cx.update_editor(|editor, _, cx| {
15373 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15374 });
15375 executor.run_until_parked();
15376
15377 cx.assert_state_with_diff(
15378 r#"
15379 - ˇa
15380 + A
15381 b
15382 "#
15383 .unindent(),
15384 );
15385}
15386
15387#[gpui::test]
15388async fn test_toggle_deletion_hunk_at_start_of_file(
15389 executor: BackgroundExecutor,
15390 cx: &mut TestAppContext,
15391) {
15392 init_test(cx, |_| {});
15393 let mut cx = EditorTestContext::new(cx).await;
15394
15395 let diff_base = r#"
15396 a
15397 b
15398 c
15399 "#
15400 .unindent();
15401
15402 cx.set_state(
15403 &r#"
15404 ˇb
15405 c
15406 "#
15407 .unindent(),
15408 );
15409 cx.set_head_text(&diff_base);
15410 cx.update_editor(|editor, window, cx| {
15411 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15412 });
15413 executor.run_until_parked();
15414
15415 let hunk_expanded = r#"
15416 - a
15417 ˇb
15418 c
15419 "#
15420 .unindent();
15421
15422 cx.assert_state_with_diff(hunk_expanded.clone());
15423
15424 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15425 let snapshot = editor.snapshot(window, cx);
15426 let hunks = editor
15427 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15428 .collect::<Vec<_>>();
15429 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15430 let buffer_id = hunks[0].buffer_id;
15431 hunks
15432 .into_iter()
15433 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15434 .collect::<Vec<_>>()
15435 });
15436 assert_eq!(hunk_ranges.len(), 1);
15437
15438 cx.update_editor(|editor, _, cx| {
15439 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15440 });
15441 executor.run_until_parked();
15442
15443 let hunk_collapsed = r#"
15444 ˇb
15445 c
15446 "#
15447 .unindent();
15448
15449 cx.assert_state_with_diff(hunk_collapsed);
15450
15451 cx.update_editor(|editor, _, cx| {
15452 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15453 });
15454 executor.run_until_parked();
15455
15456 cx.assert_state_with_diff(hunk_expanded.clone());
15457}
15458
15459#[gpui::test]
15460async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15461 init_test(cx, |_| {});
15462
15463 let fs = FakeFs::new(cx.executor());
15464 fs.insert_tree(
15465 path!("/test"),
15466 json!({
15467 ".git": {},
15468 "file-1": "ONE\n",
15469 "file-2": "TWO\n",
15470 "file-3": "THREE\n",
15471 }),
15472 )
15473 .await;
15474
15475 fs.set_head_for_repo(
15476 path!("/test/.git").as_ref(),
15477 &[
15478 ("file-1".into(), "one\n".into()),
15479 ("file-2".into(), "two\n".into()),
15480 ("file-3".into(), "three\n".into()),
15481 ],
15482 );
15483
15484 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15485 let mut buffers = vec![];
15486 for i in 1..=3 {
15487 let buffer = project
15488 .update(cx, |project, cx| {
15489 let path = format!(path!("/test/file-{}"), i);
15490 project.open_local_buffer(path, cx)
15491 })
15492 .await
15493 .unwrap();
15494 buffers.push(buffer);
15495 }
15496
15497 let multibuffer = cx.new(|cx| {
15498 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15499 multibuffer.set_all_diff_hunks_expanded(cx);
15500 for buffer in &buffers {
15501 let snapshot = buffer.read(cx).snapshot();
15502 multibuffer.set_excerpts_for_path(
15503 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15504 buffer.clone(),
15505 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15506 DEFAULT_MULTIBUFFER_CONTEXT,
15507 cx,
15508 );
15509 }
15510 multibuffer
15511 });
15512
15513 let editor = cx.add_window(|window, cx| {
15514 Editor::new(
15515 EditorMode::Full,
15516 multibuffer,
15517 Some(project),
15518 true,
15519 window,
15520 cx,
15521 )
15522 });
15523 cx.run_until_parked();
15524
15525 let snapshot = editor
15526 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15527 .unwrap();
15528 let hunks = snapshot
15529 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15530 .map(|hunk| match hunk {
15531 DisplayDiffHunk::Unfolded {
15532 display_row_range, ..
15533 } => display_row_range,
15534 DisplayDiffHunk::Folded { .. } => unreachable!(),
15535 })
15536 .collect::<Vec<_>>();
15537 assert_eq!(
15538 hunks,
15539 [
15540 DisplayRow(3)..DisplayRow(5),
15541 DisplayRow(10)..DisplayRow(12),
15542 DisplayRow(17)..DisplayRow(19),
15543 ]
15544 );
15545}
15546
15547#[gpui::test]
15548async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15549 init_test(cx, |_| {});
15550
15551 let mut cx = EditorTestContext::new(cx).await;
15552 cx.set_head_text(indoc! { "
15553 one
15554 two
15555 three
15556 four
15557 five
15558 "
15559 });
15560 cx.set_index_text(indoc! { "
15561 one
15562 two
15563 three
15564 four
15565 five
15566 "
15567 });
15568 cx.set_state(indoc! {"
15569 one
15570 TWO
15571 ˇTHREE
15572 FOUR
15573 five
15574 "});
15575 cx.run_until_parked();
15576 cx.update_editor(|editor, window, cx| {
15577 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15578 });
15579 cx.run_until_parked();
15580 cx.assert_index_text(Some(indoc! {"
15581 one
15582 TWO
15583 THREE
15584 FOUR
15585 five
15586 "}));
15587 cx.set_state(indoc! { "
15588 one
15589 TWO
15590 ˇTHREE-HUNDRED
15591 FOUR
15592 five
15593 "});
15594 cx.run_until_parked();
15595 cx.update_editor(|editor, window, cx| {
15596 let snapshot = editor.snapshot(window, cx);
15597 let hunks = editor
15598 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15599 .collect::<Vec<_>>();
15600 assert_eq!(hunks.len(), 1);
15601 assert_eq!(
15602 hunks[0].status(),
15603 DiffHunkStatus {
15604 kind: DiffHunkStatusKind::Modified,
15605 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15606 }
15607 );
15608
15609 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15610 });
15611 cx.run_until_parked();
15612 cx.assert_index_text(Some(indoc! {"
15613 one
15614 TWO
15615 THREE-HUNDRED
15616 FOUR
15617 five
15618 "}));
15619}
15620
15621#[gpui::test]
15622fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15623 init_test(cx, |_| {});
15624
15625 let editor = cx.add_window(|window, cx| {
15626 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15627 build_editor(buffer, window, cx)
15628 });
15629
15630 let render_args = Arc::new(Mutex::new(None));
15631 let snapshot = editor
15632 .update(cx, |editor, window, cx| {
15633 let snapshot = editor.buffer().read(cx).snapshot(cx);
15634 let range =
15635 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15636
15637 struct RenderArgs {
15638 row: MultiBufferRow,
15639 folded: bool,
15640 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15641 }
15642
15643 let crease = Crease::inline(
15644 range,
15645 FoldPlaceholder::test(),
15646 {
15647 let toggle_callback = render_args.clone();
15648 move |row, folded, callback, _window, _cx| {
15649 *toggle_callback.lock() = Some(RenderArgs {
15650 row,
15651 folded,
15652 callback,
15653 });
15654 div()
15655 }
15656 },
15657 |_row, _folded, _window, _cx| div(),
15658 );
15659
15660 editor.insert_creases(Some(crease), cx);
15661 let snapshot = editor.snapshot(window, cx);
15662 let _div = snapshot.render_crease_toggle(
15663 MultiBufferRow(1),
15664 false,
15665 cx.entity().clone(),
15666 window,
15667 cx,
15668 );
15669 snapshot
15670 })
15671 .unwrap();
15672
15673 let render_args = render_args.lock().take().unwrap();
15674 assert_eq!(render_args.row, MultiBufferRow(1));
15675 assert!(!render_args.folded);
15676 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15677
15678 cx.update_window(*editor, |_, window, cx| {
15679 (render_args.callback)(true, window, cx)
15680 })
15681 .unwrap();
15682 let snapshot = editor
15683 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15684 .unwrap();
15685 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15686
15687 cx.update_window(*editor, |_, window, cx| {
15688 (render_args.callback)(false, window, cx)
15689 })
15690 .unwrap();
15691 let snapshot = editor
15692 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15693 .unwrap();
15694 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15695}
15696
15697#[gpui::test]
15698async fn test_input_text(cx: &mut TestAppContext) {
15699 init_test(cx, |_| {});
15700 let mut cx = EditorTestContext::new(cx).await;
15701
15702 cx.set_state(
15703 &r#"ˇone
15704 two
15705
15706 three
15707 fourˇ
15708 five
15709
15710 siˇx"#
15711 .unindent(),
15712 );
15713
15714 cx.dispatch_action(HandleInput(String::new()));
15715 cx.assert_editor_state(
15716 &r#"ˇone
15717 two
15718
15719 three
15720 fourˇ
15721 five
15722
15723 siˇx"#
15724 .unindent(),
15725 );
15726
15727 cx.dispatch_action(HandleInput("AAAA".to_string()));
15728 cx.assert_editor_state(
15729 &r#"AAAAˇone
15730 two
15731
15732 three
15733 fourAAAAˇ
15734 five
15735
15736 siAAAAˇx"#
15737 .unindent(),
15738 );
15739}
15740
15741#[gpui::test]
15742async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15743 init_test(cx, |_| {});
15744
15745 let mut cx = EditorTestContext::new(cx).await;
15746 cx.set_state(
15747 r#"let foo = 1;
15748let foo = 2;
15749let foo = 3;
15750let fooˇ = 4;
15751let foo = 5;
15752let foo = 6;
15753let foo = 7;
15754let foo = 8;
15755let foo = 9;
15756let foo = 10;
15757let foo = 11;
15758let foo = 12;
15759let foo = 13;
15760let foo = 14;
15761let foo = 15;"#,
15762 );
15763
15764 cx.update_editor(|e, window, cx| {
15765 assert_eq!(
15766 e.next_scroll_position,
15767 NextScrollCursorCenterTopBottom::Center,
15768 "Default next scroll direction is center",
15769 );
15770
15771 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15772 assert_eq!(
15773 e.next_scroll_position,
15774 NextScrollCursorCenterTopBottom::Top,
15775 "After center, next scroll direction should be top",
15776 );
15777
15778 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15779 assert_eq!(
15780 e.next_scroll_position,
15781 NextScrollCursorCenterTopBottom::Bottom,
15782 "After top, next scroll direction should be bottom",
15783 );
15784
15785 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15786 assert_eq!(
15787 e.next_scroll_position,
15788 NextScrollCursorCenterTopBottom::Center,
15789 "After bottom, scrolling should start over",
15790 );
15791
15792 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15793 assert_eq!(
15794 e.next_scroll_position,
15795 NextScrollCursorCenterTopBottom::Top,
15796 "Scrolling continues if retriggered fast enough"
15797 );
15798 });
15799
15800 cx.executor()
15801 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15802 cx.executor().run_until_parked();
15803 cx.update_editor(|e, _, _| {
15804 assert_eq!(
15805 e.next_scroll_position,
15806 NextScrollCursorCenterTopBottom::Center,
15807 "If scrolling is not triggered fast enough, it should reset"
15808 );
15809 });
15810}
15811
15812#[gpui::test]
15813async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15814 init_test(cx, |_| {});
15815 let mut cx = EditorLspTestContext::new_rust(
15816 lsp::ServerCapabilities {
15817 definition_provider: Some(lsp::OneOf::Left(true)),
15818 references_provider: Some(lsp::OneOf::Left(true)),
15819 ..lsp::ServerCapabilities::default()
15820 },
15821 cx,
15822 )
15823 .await;
15824
15825 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15826 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15827 move |params, _| async move {
15828 if empty_go_to_definition {
15829 Ok(None)
15830 } else {
15831 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15832 uri: params.text_document_position_params.text_document.uri,
15833 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15834 })))
15835 }
15836 },
15837 );
15838 let references =
15839 cx.lsp
15840 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15841 Ok(Some(vec![lsp::Location {
15842 uri: params.text_document_position.text_document.uri,
15843 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15844 }]))
15845 });
15846 (go_to_definition, references)
15847 };
15848
15849 cx.set_state(
15850 &r#"fn one() {
15851 let mut a = ˇtwo();
15852 }
15853
15854 fn two() {}"#
15855 .unindent(),
15856 );
15857 set_up_lsp_handlers(false, &mut cx);
15858 let navigated = cx
15859 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15860 .await
15861 .expect("Failed to navigate to definition");
15862 assert_eq!(
15863 navigated,
15864 Navigated::Yes,
15865 "Should have navigated to definition from the GetDefinition response"
15866 );
15867 cx.assert_editor_state(
15868 &r#"fn one() {
15869 let mut a = two();
15870 }
15871
15872 fn «twoˇ»() {}"#
15873 .unindent(),
15874 );
15875
15876 let editors = cx.update_workspace(|workspace, _, cx| {
15877 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15878 });
15879 cx.update_editor(|_, _, test_editor_cx| {
15880 assert_eq!(
15881 editors.len(),
15882 1,
15883 "Initially, only one, test, editor should be open in the workspace"
15884 );
15885 assert_eq!(
15886 test_editor_cx.entity(),
15887 editors.last().expect("Asserted len is 1").clone()
15888 );
15889 });
15890
15891 set_up_lsp_handlers(true, &mut cx);
15892 let navigated = cx
15893 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15894 .await
15895 .expect("Failed to navigate to lookup references");
15896 assert_eq!(
15897 navigated,
15898 Navigated::Yes,
15899 "Should have navigated to references as a fallback after empty GoToDefinition response"
15900 );
15901 // We should not change the selections in the existing file,
15902 // if opening another milti buffer with the references
15903 cx.assert_editor_state(
15904 &r#"fn one() {
15905 let mut a = two();
15906 }
15907
15908 fn «twoˇ»() {}"#
15909 .unindent(),
15910 );
15911 let editors = cx.update_workspace(|workspace, _, cx| {
15912 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15913 });
15914 cx.update_editor(|_, _, test_editor_cx| {
15915 assert_eq!(
15916 editors.len(),
15917 2,
15918 "After falling back to references search, we open a new editor with the results"
15919 );
15920 let references_fallback_text = editors
15921 .into_iter()
15922 .find(|new_editor| *new_editor != test_editor_cx.entity())
15923 .expect("Should have one non-test editor now")
15924 .read(test_editor_cx)
15925 .text(test_editor_cx);
15926 assert_eq!(
15927 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15928 "Should use the range from the references response and not the GoToDefinition one"
15929 );
15930 });
15931}
15932
15933#[gpui::test]
15934async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15935 init_test(cx, |_| {});
15936
15937 let language = Arc::new(Language::new(
15938 LanguageConfig::default(),
15939 Some(tree_sitter_rust::LANGUAGE.into()),
15940 ));
15941
15942 let text = r#"
15943 #[cfg(test)]
15944 mod tests() {
15945 #[test]
15946 fn runnable_1() {
15947 let a = 1;
15948 }
15949
15950 #[test]
15951 fn runnable_2() {
15952 let a = 1;
15953 let b = 2;
15954 }
15955 }
15956 "#
15957 .unindent();
15958
15959 let fs = FakeFs::new(cx.executor());
15960 fs.insert_file("/file.rs", Default::default()).await;
15961
15962 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15963 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15964 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15965 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15966 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15967
15968 let editor = cx.new_window_entity(|window, cx| {
15969 Editor::new(
15970 EditorMode::Full,
15971 multi_buffer,
15972 Some(project.clone()),
15973 true,
15974 window,
15975 cx,
15976 )
15977 });
15978
15979 editor.update_in(cx, |editor, window, cx| {
15980 let snapshot = editor.buffer().read(cx).snapshot(cx);
15981 editor.tasks.insert(
15982 (buffer.read(cx).remote_id(), 3),
15983 RunnableTasks {
15984 templates: vec![],
15985 offset: snapshot.anchor_before(43),
15986 column: 0,
15987 extra_variables: HashMap::default(),
15988 context_range: BufferOffset(43)..BufferOffset(85),
15989 },
15990 );
15991 editor.tasks.insert(
15992 (buffer.read(cx).remote_id(), 8),
15993 RunnableTasks {
15994 templates: vec![],
15995 offset: snapshot.anchor_before(86),
15996 column: 0,
15997 extra_variables: HashMap::default(),
15998 context_range: BufferOffset(86)..BufferOffset(191),
15999 },
16000 );
16001
16002 // Test finding task when cursor is inside function body
16003 editor.change_selections(None, window, cx, |s| {
16004 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16005 });
16006 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16007 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16008
16009 // Test finding task when cursor is on function name
16010 editor.change_selections(None, window, cx, |s| {
16011 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16012 });
16013 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16014 assert_eq!(row, 8, "Should find task when cursor is on function name");
16015 });
16016}
16017
16018#[gpui::test]
16019async fn test_folding_buffers(cx: &mut TestAppContext) {
16020 init_test(cx, |_| {});
16021
16022 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16023 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16024 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16025
16026 let fs = FakeFs::new(cx.executor());
16027 fs.insert_tree(
16028 path!("/a"),
16029 json!({
16030 "first.rs": sample_text_1,
16031 "second.rs": sample_text_2,
16032 "third.rs": sample_text_3,
16033 }),
16034 )
16035 .await;
16036 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16037 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16038 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16039 let worktree = project.update(cx, |project, cx| {
16040 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16041 assert_eq!(worktrees.len(), 1);
16042 worktrees.pop().unwrap()
16043 });
16044 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16045
16046 let buffer_1 = project
16047 .update(cx, |project, cx| {
16048 project.open_buffer((worktree_id, "first.rs"), cx)
16049 })
16050 .await
16051 .unwrap();
16052 let buffer_2 = project
16053 .update(cx, |project, cx| {
16054 project.open_buffer((worktree_id, "second.rs"), cx)
16055 })
16056 .await
16057 .unwrap();
16058 let buffer_3 = project
16059 .update(cx, |project, cx| {
16060 project.open_buffer((worktree_id, "third.rs"), cx)
16061 })
16062 .await
16063 .unwrap();
16064
16065 let multi_buffer = cx.new(|cx| {
16066 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16067 multi_buffer.push_excerpts(
16068 buffer_1.clone(),
16069 [
16070 ExcerptRange {
16071 context: Point::new(0, 0)..Point::new(3, 0),
16072 primary: None,
16073 },
16074 ExcerptRange {
16075 context: Point::new(5, 0)..Point::new(7, 0),
16076 primary: None,
16077 },
16078 ExcerptRange {
16079 context: Point::new(9, 0)..Point::new(10, 4),
16080 primary: None,
16081 },
16082 ],
16083 cx,
16084 );
16085 multi_buffer.push_excerpts(
16086 buffer_2.clone(),
16087 [
16088 ExcerptRange {
16089 context: Point::new(0, 0)..Point::new(3, 0),
16090 primary: None,
16091 },
16092 ExcerptRange {
16093 context: Point::new(5, 0)..Point::new(7, 0),
16094 primary: None,
16095 },
16096 ExcerptRange {
16097 context: Point::new(9, 0)..Point::new(10, 4),
16098 primary: None,
16099 },
16100 ],
16101 cx,
16102 );
16103 multi_buffer.push_excerpts(
16104 buffer_3.clone(),
16105 [
16106 ExcerptRange {
16107 context: Point::new(0, 0)..Point::new(3, 0),
16108 primary: None,
16109 },
16110 ExcerptRange {
16111 context: Point::new(5, 0)..Point::new(7, 0),
16112 primary: None,
16113 },
16114 ExcerptRange {
16115 context: Point::new(9, 0)..Point::new(10, 4),
16116 primary: None,
16117 },
16118 ],
16119 cx,
16120 );
16121 multi_buffer
16122 });
16123 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16124 Editor::new(
16125 EditorMode::Full,
16126 multi_buffer.clone(),
16127 Some(project.clone()),
16128 true,
16129 window,
16130 cx,
16131 )
16132 });
16133
16134 assert_eq!(
16135 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16136 "\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",
16137 );
16138
16139 multi_buffer_editor.update(cx, |editor, cx| {
16140 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16141 });
16142 assert_eq!(
16143 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16144 "\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",
16145 "After folding the first buffer, its text should not be displayed"
16146 );
16147
16148 multi_buffer_editor.update(cx, |editor, cx| {
16149 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16150 });
16151 assert_eq!(
16152 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16153 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16154 "After folding the second buffer, its text should not be displayed"
16155 );
16156
16157 multi_buffer_editor.update(cx, |editor, cx| {
16158 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16159 });
16160 assert_eq!(
16161 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16162 "\n\n\n\n\n",
16163 "After folding the third buffer, its text should not be displayed"
16164 );
16165
16166 // Emulate selection inside the fold logic, that should work
16167 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16168 editor
16169 .snapshot(window, cx)
16170 .next_line_boundary(Point::new(0, 4));
16171 });
16172
16173 multi_buffer_editor.update(cx, |editor, cx| {
16174 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16175 });
16176 assert_eq!(
16177 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16178 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16179 "After unfolding the second buffer, its text should be displayed"
16180 );
16181
16182 // Typing inside of buffer 1 causes that buffer to be unfolded.
16183 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16184 assert_eq!(
16185 multi_buffer
16186 .read(cx)
16187 .snapshot(cx)
16188 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16189 .collect::<String>(),
16190 "bbbb"
16191 );
16192 editor.change_selections(None, window, cx, |selections| {
16193 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16194 });
16195 editor.handle_input("B", window, cx);
16196 });
16197
16198 assert_eq!(
16199 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16200 "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16201 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16202 );
16203
16204 multi_buffer_editor.update(cx, |editor, cx| {
16205 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16206 });
16207 assert_eq!(
16208 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16209 "\n\n\nB\n\n\n\n\n\n\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",
16210 "After unfolding the all buffers, all original text should be displayed"
16211 );
16212}
16213
16214#[gpui::test]
16215async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16216 init_test(cx, |_| {});
16217
16218 let sample_text_1 = "1111\n2222\n3333".to_string();
16219 let sample_text_2 = "4444\n5555\n6666".to_string();
16220 let sample_text_3 = "7777\n8888\n9999".to_string();
16221
16222 let fs = FakeFs::new(cx.executor());
16223 fs.insert_tree(
16224 path!("/a"),
16225 json!({
16226 "first.rs": sample_text_1,
16227 "second.rs": sample_text_2,
16228 "third.rs": sample_text_3,
16229 }),
16230 )
16231 .await;
16232 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16233 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16234 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16235 let worktree = project.update(cx, |project, cx| {
16236 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16237 assert_eq!(worktrees.len(), 1);
16238 worktrees.pop().unwrap()
16239 });
16240 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16241
16242 let buffer_1 = project
16243 .update(cx, |project, cx| {
16244 project.open_buffer((worktree_id, "first.rs"), cx)
16245 })
16246 .await
16247 .unwrap();
16248 let buffer_2 = project
16249 .update(cx, |project, cx| {
16250 project.open_buffer((worktree_id, "second.rs"), cx)
16251 })
16252 .await
16253 .unwrap();
16254 let buffer_3 = project
16255 .update(cx, |project, cx| {
16256 project.open_buffer((worktree_id, "third.rs"), cx)
16257 })
16258 .await
16259 .unwrap();
16260
16261 let multi_buffer = cx.new(|cx| {
16262 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16263 multi_buffer.push_excerpts(
16264 buffer_1.clone(),
16265 [ExcerptRange {
16266 context: Point::new(0, 0)..Point::new(3, 0),
16267 primary: None,
16268 }],
16269 cx,
16270 );
16271 multi_buffer.push_excerpts(
16272 buffer_2.clone(),
16273 [ExcerptRange {
16274 context: Point::new(0, 0)..Point::new(3, 0),
16275 primary: None,
16276 }],
16277 cx,
16278 );
16279 multi_buffer.push_excerpts(
16280 buffer_3.clone(),
16281 [ExcerptRange {
16282 context: Point::new(0, 0)..Point::new(3, 0),
16283 primary: None,
16284 }],
16285 cx,
16286 );
16287 multi_buffer
16288 });
16289
16290 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16291 Editor::new(
16292 EditorMode::Full,
16293 multi_buffer,
16294 Some(project.clone()),
16295 true,
16296 window,
16297 cx,
16298 )
16299 });
16300
16301 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16302 assert_eq!(
16303 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16304 full_text,
16305 );
16306
16307 multi_buffer_editor.update(cx, |editor, cx| {
16308 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16309 });
16310 assert_eq!(
16311 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16312 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16313 "After folding the first buffer, its text should not be displayed"
16314 );
16315
16316 multi_buffer_editor.update(cx, |editor, cx| {
16317 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16318 });
16319
16320 assert_eq!(
16321 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16322 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16323 "After folding the second buffer, its text should not be displayed"
16324 );
16325
16326 multi_buffer_editor.update(cx, |editor, cx| {
16327 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16328 });
16329 assert_eq!(
16330 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16331 "\n\n\n\n\n",
16332 "After folding the third buffer, its text should not be displayed"
16333 );
16334
16335 multi_buffer_editor.update(cx, |editor, cx| {
16336 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16337 });
16338 assert_eq!(
16339 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16340 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16341 "After unfolding the second buffer, its text should be displayed"
16342 );
16343
16344 multi_buffer_editor.update(cx, |editor, cx| {
16345 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16346 });
16347 assert_eq!(
16348 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16349 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16350 "After unfolding the first buffer, its text should be displayed"
16351 );
16352
16353 multi_buffer_editor.update(cx, |editor, cx| {
16354 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16355 });
16356 assert_eq!(
16357 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16358 full_text,
16359 "After unfolding all buffers, all original text should be displayed"
16360 );
16361}
16362
16363#[gpui::test]
16364async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16365 init_test(cx, |_| {});
16366
16367 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16368
16369 let fs = FakeFs::new(cx.executor());
16370 fs.insert_tree(
16371 path!("/a"),
16372 json!({
16373 "main.rs": sample_text,
16374 }),
16375 )
16376 .await;
16377 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16378 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16379 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16380 let worktree = project.update(cx, |project, cx| {
16381 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16382 assert_eq!(worktrees.len(), 1);
16383 worktrees.pop().unwrap()
16384 });
16385 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16386
16387 let buffer_1 = project
16388 .update(cx, |project, cx| {
16389 project.open_buffer((worktree_id, "main.rs"), cx)
16390 })
16391 .await
16392 .unwrap();
16393
16394 let multi_buffer = cx.new(|cx| {
16395 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16396 multi_buffer.push_excerpts(
16397 buffer_1.clone(),
16398 [ExcerptRange {
16399 context: Point::new(0, 0)
16400 ..Point::new(
16401 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16402 0,
16403 ),
16404 primary: None,
16405 }],
16406 cx,
16407 );
16408 multi_buffer
16409 });
16410 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16411 Editor::new(
16412 EditorMode::Full,
16413 multi_buffer,
16414 Some(project.clone()),
16415 true,
16416 window,
16417 cx,
16418 )
16419 });
16420
16421 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16422 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16423 enum TestHighlight {}
16424 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16425 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16426 editor.highlight_text::<TestHighlight>(
16427 vec![highlight_range.clone()],
16428 HighlightStyle::color(Hsla::green()),
16429 cx,
16430 );
16431 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16432 });
16433
16434 let full_text = format!("\n\n\n{sample_text}\n");
16435 assert_eq!(
16436 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16437 full_text,
16438 );
16439}
16440
16441#[gpui::test]
16442async fn test_inline_completion_text(cx: &mut TestAppContext) {
16443 init_test(cx, |_| {});
16444
16445 // Simple insertion
16446 assert_highlighted_edits(
16447 "Hello, world!",
16448 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16449 true,
16450 cx,
16451 |highlighted_edits, cx| {
16452 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16453 assert_eq!(highlighted_edits.highlights.len(), 1);
16454 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16455 assert_eq!(
16456 highlighted_edits.highlights[0].1.background_color,
16457 Some(cx.theme().status().created_background)
16458 );
16459 },
16460 )
16461 .await;
16462
16463 // Replacement
16464 assert_highlighted_edits(
16465 "This is a test.",
16466 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16467 false,
16468 cx,
16469 |highlighted_edits, cx| {
16470 assert_eq!(highlighted_edits.text, "That is a test.");
16471 assert_eq!(highlighted_edits.highlights.len(), 1);
16472 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16473 assert_eq!(
16474 highlighted_edits.highlights[0].1.background_color,
16475 Some(cx.theme().status().created_background)
16476 );
16477 },
16478 )
16479 .await;
16480
16481 // Multiple edits
16482 assert_highlighted_edits(
16483 "Hello, world!",
16484 vec![
16485 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16486 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16487 ],
16488 false,
16489 cx,
16490 |highlighted_edits, cx| {
16491 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16492 assert_eq!(highlighted_edits.highlights.len(), 2);
16493 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16494 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16495 assert_eq!(
16496 highlighted_edits.highlights[0].1.background_color,
16497 Some(cx.theme().status().created_background)
16498 );
16499 assert_eq!(
16500 highlighted_edits.highlights[1].1.background_color,
16501 Some(cx.theme().status().created_background)
16502 );
16503 },
16504 )
16505 .await;
16506
16507 // Multiple lines with edits
16508 assert_highlighted_edits(
16509 "First line\nSecond line\nThird line\nFourth line",
16510 vec![
16511 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16512 (
16513 Point::new(2, 0)..Point::new(2, 10),
16514 "New third line".to_string(),
16515 ),
16516 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16517 ],
16518 false,
16519 cx,
16520 |highlighted_edits, cx| {
16521 assert_eq!(
16522 highlighted_edits.text,
16523 "Second modified\nNew third line\nFourth updated line"
16524 );
16525 assert_eq!(highlighted_edits.highlights.len(), 3);
16526 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16527 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16528 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16529 for highlight in &highlighted_edits.highlights {
16530 assert_eq!(
16531 highlight.1.background_color,
16532 Some(cx.theme().status().created_background)
16533 );
16534 }
16535 },
16536 )
16537 .await;
16538}
16539
16540#[gpui::test]
16541async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16542 init_test(cx, |_| {});
16543
16544 // Deletion
16545 assert_highlighted_edits(
16546 "Hello, world!",
16547 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16548 true,
16549 cx,
16550 |highlighted_edits, cx| {
16551 assert_eq!(highlighted_edits.text, "Hello, world!");
16552 assert_eq!(highlighted_edits.highlights.len(), 1);
16553 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16554 assert_eq!(
16555 highlighted_edits.highlights[0].1.background_color,
16556 Some(cx.theme().status().deleted_background)
16557 );
16558 },
16559 )
16560 .await;
16561
16562 // Insertion
16563 assert_highlighted_edits(
16564 "Hello, world!",
16565 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16566 true,
16567 cx,
16568 |highlighted_edits, cx| {
16569 assert_eq!(highlighted_edits.highlights.len(), 1);
16570 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16571 assert_eq!(
16572 highlighted_edits.highlights[0].1.background_color,
16573 Some(cx.theme().status().created_background)
16574 );
16575 },
16576 )
16577 .await;
16578}
16579
16580async fn assert_highlighted_edits(
16581 text: &str,
16582 edits: Vec<(Range<Point>, String)>,
16583 include_deletions: bool,
16584 cx: &mut TestAppContext,
16585 assertion_fn: impl Fn(HighlightedText, &App),
16586) {
16587 let window = cx.add_window(|window, cx| {
16588 let buffer = MultiBuffer::build_simple(text, cx);
16589 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16590 });
16591 let cx = &mut VisualTestContext::from_window(*window, cx);
16592
16593 let (buffer, snapshot) = window
16594 .update(cx, |editor, _window, cx| {
16595 (
16596 editor.buffer().clone(),
16597 editor.buffer().read(cx).snapshot(cx),
16598 )
16599 })
16600 .unwrap();
16601
16602 let edits = edits
16603 .into_iter()
16604 .map(|(range, edit)| {
16605 (
16606 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16607 edit,
16608 )
16609 })
16610 .collect::<Vec<_>>();
16611
16612 let text_anchor_edits = edits
16613 .clone()
16614 .into_iter()
16615 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16616 .collect::<Vec<_>>();
16617
16618 let edit_preview = window
16619 .update(cx, |_, _window, cx| {
16620 buffer
16621 .read(cx)
16622 .as_singleton()
16623 .unwrap()
16624 .read(cx)
16625 .preview_edits(text_anchor_edits.into(), cx)
16626 })
16627 .unwrap()
16628 .await;
16629
16630 cx.update(|_window, cx| {
16631 let highlighted_edits = inline_completion_edit_text(
16632 &snapshot.as_singleton().unwrap().2,
16633 &edits,
16634 &edit_preview,
16635 include_deletions,
16636 cx,
16637 );
16638 assertion_fn(highlighted_edits, cx)
16639 });
16640}
16641
16642#[gpui::test]
16643async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16644 init_test(cx, |_| {});
16645 let capabilities = lsp::ServerCapabilities {
16646 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16647 prepare_provider: Some(true),
16648 work_done_progress_options: Default::default(),
16649 })),
16650 ..Default::default()
16651 };
16652 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16653
16654 cx.set_state(indoc! {"
16655 struct Fˇoo {}
16656 "});
16657
16658 cx.update_editor(|editor, _, cx| {
16659 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16660 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16661 editor.highlight_background::<DocumentHighlightRead>(
16662 &[highlight_range],
16663 |c| c.editor_document_highlight_read_background,
16664 cx,
16665 );
16666 });
16667
16668 let mut prepare_rename_handler =
16669 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16670 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16671 start: lsp::Position {
16672 line: 0,
16673 character: 7,
16674 },
16675 end: lsp::Position {
16676 line: 0,
16677 character: 10,
16678 },
16679 })))
16680 });
16681 let prepare_rename_task = cx
16682 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16683 .expect("Prepare rename was not started");
16684 prepare_rename_handler.next().await.unwrap();
16685 prepare_rename_task.await.expect("Prepare rename failed");
16686
16687 let mut rename_handler =
16688 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16689 let edit = lsp::TextEdit {
16690 range: lsp::Range {
16691 start: lsp::Position {
16692 line: 0,
16693 character: 7,
16694 },
16695 end: lsp::Position {
16696 line: 0,
16697 character: 10,
16698 },
16699 },
16700 new_text: "FooRenamed".to_string(),
16701 };
16702 Ok(Some(lsp::WorkspaceEdit::new(
16703 // Specify the same edit twice
16704 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16705 )))
16706 });
16707 let rename_task = cx
16708 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16709 .expect("Confirm rename was not started");
16710 rename_handler.next().await.unwrap();
16711 rename_task.await.expect("Confirm rename failed");
16712 cx.run_until_parked();
16713
16714 // Despite two edits, only one is actually applied as those are identical
16715 cx.assert_editor_state(indoc! {"
16716 struct FooRenamedˇ {}
16717 "});
16718}
16719
16720#[gpui::test]
16721async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16722 init_test(cx, |_| {});
16723 // These capabilities indicate that the server does not support prepare rename.
16724 let capabilities = lsp::ServerCapabilities {
16725 rename_provider: Some(lsp::OneOf::Left(true)),
16726 ..Default::default()
16727 };
16728 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16729
16730 cx.set_state(indoc! {"
16731 struct Fˇoo {}
16732 "});
16733
16734 cx.update_editor(|editor, _window, cx| {
16735 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16736 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16737 editor.highlight_background::<DocumentHighlightRead>(
16738 &[highlight_range],
16739 |c| c.editor_document_highlight_read_background,
16740 cx,
16741 );
16742 });
16743
16744 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16745 .expect("Prepare rename was not started")
16746 .await
16747 .expect("Prepare rename failed");
16748
16749 let mut rename_handler =
16750 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16751 let edit = lsp::TextEdit {
16752 range: lsp::Range {
16753 start: lsp::Position {
16754 line: 0,
16755 character: 7,
16756 },
16757 end: lsp::Position {
16758 line: 0,
16759 character: 10,
16760 },
16761 },
16762 new_text: "FooRenamed".to_string(),
16763 };
16764 Ok(Some(lsp::WorkspaceEdit::new(
16765 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16766 )))
16767 });
16768 let rename_task = cx
16769 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16770 .expect("Confirm rename was not started");
16771 rename_handler.next().await.unwrap();
16772 rename_task.await.expect("Confirm rename failed");
16773 cx.run_until_parked();
16774
16775 // Correct range is renamed, as `surrounding_word` is used to find it.
16776 cx.assert_editor_state(indoc! {"
16777 struct FooRenamedˇ {}
16778 "});
16779}
16780
16781#[gpui::test]
16782async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16783 init_test(cx, |_| {});
16784 let mut cx = EditorTestContext::new(cx).await;
16785
16786 let language = Arc::new(
16787 Language::new(
16788 LanguageConfig::default(),
16789 Some(tree_sitter_html::LANGUAGE.into()),
16790 )
16791 .with_brackets_query(
16792 r#"
16793 ("<" @open "/>" @close)
16794 ("</" @open ">" @close)
16795 ("<" @open ">" @close)
16796 ("\"" @open "\"" @close)
16797 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16798 "#,
16799 )
16800 .unwrap(),
16801 );
16802 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16803
16804 cx.set_state(indoc! {"
16805 <span>ˇ</span>
16806 "});
16807 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16808 cx.assert_editor_state(indoc! {"
16809 <span>
16810 ˇ
16811 </span>
16812 "});
16813
16814 cx.set_state(indoc! {"
16815 <span><span></span>ˇ</span>
16816 "});
16817 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16818 cx.assert_editor_state(indoc! {"
16819 <span><span></span>
16820 ˇ</span>
16821 "});
16822
16823 cx.set_state(indoc! {"
16824 <span>ˇ
16825 </span>
16826 "});
16827 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16828 cx.assert_editor_state(indoc! {"
16829 <span>
16830 ˇ
16831 </span>
16832 "});
16833}
16834
16835fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16836 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16837 point..point
16838}
16839
16840fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16841 let (text, ranges) = marked_text_ranges(marked_text, true);
16842 assert_eq!(editor.text(cx), text);
16843 assert_eq!(
16844 editor.selections.ranges(cx),
16845 ranges,
16846 "Assert selections are {}",
16847 marked_text
16848 );
16849}
16850
16851pub fn handle_signature_help_request(
16852 cx: &mut EditorLspTestContext,
16853 mocked_response: lsp::SignatureHelp,
16854) -> impl Future<Output = ()> {
16855 let mut request =
16856 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16857 let mocked_response = mocked_response.clone();
16858 async move { Ok(Some(mocked_response)) }
16859 });
16860
16861 async move {
16862 request.next().await;
16863 }
16864}
16865
16866/// Handle completion request passing a marked string specifying where the completion
16867/// should be triggered from using '|' character, what range should be replaced, and what completions
16868/// should be returned using '<' and '>' to delimit the range
16869pub fn handle_completion_request(
16870 cx: &mut EditorLspTestContext,
16871 marked_string: &str,
16872 completions: Vec<&'static str>,
16873 counter: Arc<AtomicUsize>,
16874) -> impl Future<Output = ()> {
16875 let complete_from_marker: TextRangeMarker = '|'.into();
16876 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16877 let (_, mut marked_ranges) = marked_text_ranges_by(
16878 marked_string,
16879 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16880 );
16881
16882 let complete_from_position =
16883 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16884 let replace_range =
16885 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16886
16887 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16888 let completions = completions.clone();
16889 counter.fetch_add(1, atomic::Ordering::Release);
16890 async move {
16891 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16892 assert_eq!(
16893 params.text_document_position.position,
16894 complete_from_position
16895 );
16896 Ok(Some(lsp::CompletionResponse::Array(
16897 completions
16898 .iter()
16899 .map(|completion_text| lsp::CompletionItem {
16900 label: completion_text.to_string(),
16901 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16902 range: replace_range,
16903 new_text: completion_text.to_string(),
16904 })),
16905 ..Default::default()
16906 })
16907 .collect(),
16908 )))
16909 }
16910 });
16911
16912 async move {
16913 request.next().await;
16914 }
16915}
16916
16917fn handle_resolve_completion_request(
16918 cx: &mut EditorLspTestContext,
16919 edits: Option<Vec<(&'static str, &'static str)>>,
16920) -> impl Future<Output = ()> {
16921 let edits = edits.map(|edits| {
16922 edits
16923 .iter()
16924 .map(|(marked_string, new_text)| {
16925 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16926 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16927 lsp::TextEdit::new(replace_range, new_text.to_string())
16928 })
16929 .collect::<Vec<_>>()
16930 });
16931
16932 let mut request =
16933 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16934 let edits = edits.clone();
16935 async move {
16936 Ok(lsp::CompletionItem {
16937 additional_text_edits: edits,
16938 ..Default::default()
16939 })
16940 }
16941 });
16942
16943 async move {
16944 request.next().await;
16945 }
16946}
16947
16948pub(crate) fn update_test_language_settings(
16949 cx: &mut TestAppContext,
16950 f: impl Fn(&mut AllLanguageSettingsContent),
16951) {
16952 cx.update(|cx| {
16953 SettingsStore::update_global(cx, |store, cx| {
16954 store.update_user_settings::<AllLanguageSettings>(cx, f);
16955 });
16956 });
16957}
16958
16959pub(crate) fn update_test_project_settings(
16960 cx: &mut TestAppContext,
16961 f: impl Fn(&mut ProjectSettings),
16962) {
16963 cx.update(|cx| {
16964 SettingsStore::update_global(cx, |store, cx| {
16965 store.update_user_settings::<ProjectSettings>(cx, f);
16966 });
16967 });
16968}
16969
16970pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16971 cx.update(|cx| {
16972 assets::Assets.load_test_fonts(cx);
16973 let store = SettingsStore::test(cx);
16974 cx.set_global(store);
16975 theme::init(theme::LoadThemes::JustBase, cx);
16976 release_channel::init(SemanticVersion::default(), cx);
16977 client::init_settings(cx);
16978 language::init(cx);
16979 Project::init_settings(cx);
16980 workspace::init_settings(cx);
16981 crate::init(cx);
16982 });
16983
16984 update_test_language_settings(cx, f);
16985}
16986
16987#[track_caller]
16988fn assert_hunk_revert(
16989 not_reverted_text_with_selections: &str,
16990 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
16991 expected_reverted_text_with_selections: &str,
16992 base_text: &str,
16993 cx: &mut EditorLspTestContext,
16994) {
16995 cx.set_state(not_reverted_text_with_selections);
16996 cx.set_head_text(base_text);
16997 cx.executor().run_until_parked();
16998
16999 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17000 let snapshot = editor.snapshot(window, cx);
17001 let reverted_hunk_statuses = snapshot
17002 .buffer_snapshot
17003 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17004 .map(|hunk| hunk.status().kind)
17005 .collect::<Vec<_>>();
17006
17007 editor.git_restore(&Default::default(), window, cx);
17008 reverted_hunk_statuses
17009 });
17010 cx.executor().run_until_parked();
17011 cx.assert_editor_state(expected_reverted_text_with_selections);
17012 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17013}