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_clipboard(cx: &mut TestAppContext) {
4742 init_test(cx, |_| {});
4743
4744 let mut cx = EditorTestContext::new(cx).await;
4745
4746 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4747 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4748 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4749
4750 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4751 cx.set_state("two ˇfour ˇsix ˇ");
4752 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4753 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4754
4755 // Paste again but with only two cursors. Since the number of cursors doesn't
4756 // match the number of slices in the clipboard, the entire clipboard text
4757 // is pasted at each cursor.
4758 cx.set_state("ˇtwo one✅ four three six five ˇ");
4759 cx.update_editor(|e, window, cx| {
4760 e.handle_input("( ", window, cx);
4761 e.paste(&Paste, window, cx);
4762 e.handle_input(") ", window, cx);
4763 });
4764 cx.assert_editor_state(
4765 &([
4766 "( one✅ ",
4767 "three ",
4768 "five ) ˇtwo one✅ four three six five ( one✅ ",
4769 "three ",
4770 "five ) ˇ",
4771 ]
4772 .join("\n")),
4773 );
4774
4775 // Cut with three selections, one of which is full-line.
4776 cx.set_state(indoc! {"
4777 1«2ˇ»3
4778 4ˇ567
4779 «8ˇ»9"});
4780 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4781 cx.assert_editor_state(indoc! {"
4782 1ˇ3
4783 ˇ9"});
4784
4785 // Paste with three selections, noticing how the copied selection that was full-line
4786 // gets inserted before the second cursor.
4787 cx.set_state(indoc! {"
4788 1ˇ3
4789 9ˇ
4790 «oˇ»ne"});
4791 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4792 cx.assert_editor_state(indoc! {"
4793 12ˇ3
4794 4567
4795 9ˇ
4796 8ˇne"});
4797
4798 // Copy with a single cursor only, which writes the whole line into the clipboard.
4799 cx.set_state(indoc! {"
4800 The quick brown
4801 fox juˇmps over
4802 the lazy dog"});
4803 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4804 assert_eq!(
4805 cx.read_from_clipboard()
4806 .and_then(|item| item.text().as_deref().map(str::to_string)),
4807 Some("fox jumps over\n".to_string())
4808 );
4809
4810 // Paste with three selections, noticing how the copied full-line selection is inserted
4811 // before the empty selections but replaces the selection that is non-empty.
4812 cx.set_state(indoc! {"
4813 Tˇhe quick brown
4814 «foˇ»x jumps over
4815 tˇhe lazy dog"});
4816 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4817 cx.assert_editor_state(indoc! {"
4818 fox jumps over
4819 Tˇhe quick brown
4820 fox jumps over
4821 ˇx jumps over
4822 fox jumps over
4823 tˇhe lazy dog"});
4824}
4825
4826#[gpui::test]
4827async fn test_paste_multiline(cx: &mut TestAppContext) {
4828 init_test(cx, |_| {});
4829
4830 let mut cx = EditorTestContext::new(cx).await;
4831 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4832
4833 // Cut an indented block, without the leading whitespace.
4834 cx.set_state(indoc! {"
4835 const a: B = (
4836 c(),
4837 «d(
4838 e,
4839 f
4840 )ˇ»
4841 );
4842 "});
4843 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4844 cx.assert_editor_state(indoc! {"
4845 const a: B = (
4846 c(),
4847 ˇ
4848 );
4849 "});
4850
4851 // Paste it at the same position.
4852 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4853 cx.assert_editor_state(indoc! {"
4854 const a: B = (
4855 c(),
4856 d(
4857 e,
4858 f
4859 )ˇ
4860 );
4861 "});
4862
4863 // Paste it at a line with a lower indent level.
4864 cx.set_state(indoc! {"
4865 ˇ
4866 const a: B = (
4867 c(),
4868 );
4869 "});
4870 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4871 cx.assert_editor_state(indoc! {"
4872 d(
4873 e,
4874 f
4875 )ˇ
4876 const a: B = (
4877 c(),
4878 );
4879 "});
4880
4881 // Cut an indented block, with the leading whitespace.
4882 cx.set_state(indoc! {"
4883 const a: B = (
4884 c(),
4885 « d(
4886 e,
4887 f
4888 )
4889 ˇ»);
4890 "});
4891 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4892 cx.assert_editor_state(indoc! {"
4893 const a: B = (
4894 c(),
4895 ˇ);
4896 "});
4897
4898 // Paste it at the same position.
4899 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4900 cx.assert_editor_state(indoc! {"
4901 const a: B = (
4902 c(),
4903 d(
4904 e,
4905 f
4906 )
4907 ˇ);
4908 "});
4909
4910 // Paste it at a line with a higher indent level.
4911 cx.set_state(indoc! {"
4912 const a: B = (
4913 c(),
4914 d(
4915 e,
4916 fˇ
4917 )
4918 );
4919 "});
4920 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4921 cx.assert_editor_state(indoc! {"
4922 const a: B = (
4923 c(),
4924 d(
4925 e,
4926 f d(
4927 e,
4928 f
4929 )
4930 ˇ
4931 )
4932 );
4933 "});
4934}
4935
4936#[gpui::test]
4937async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4938 init_test(cx, |_| {});
4939
4940 cx.write_to_clipboard(ClipboardItem::new_string(
4941 " d(\n e\n );\n".into(),
4942 ));
4943
4944 let mut cx = EditorTestContext::new(cx).await;
4945 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4946
4947 cx.set_state(indoc! {"
4948 fn a() {
4949 b();
4950 if c() {
4951 ˇ
4952 }
4953 }
4954 "});
4955
4956 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4957 cx.assert_editor_state(indoc! {"
4958 fn a() {
4959 b();
4960 if c() {
4961 d(
4962 e
4963 );
4964 ˇ
4965 }
4966 }
4967 "});
4968
4969 cx.set_state(indoc! {"
4970 fn a() {
4971 b();
4972 ˇ
4973 }
4974 "});
4975
4976 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4977 cx.assert_editor_state(indoc! {"
4978 fn a() {
4979 b();
4980 d(
4981 e
4982 );
4983 ˇ
4984 }
4985 "});
4986}
4987
4988#[gpui::test]
4989fn test_select_all(cx: &mut TestAppContext) {
4990 init_test(cx, |_| {});
4991
4992 let editor = cx.add_window(|window, cx| {
4993 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4994 build_editor(buffer, window, cx)
4995 });
4996 _ = editor.update(cx, |editor, window, cx| {
4997 editor.select_all(&SelectAll, window, cx);
4998 assert_eq!(
4999 editor.selections.display_ranges(cx),
5000 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5001 );
5002 });
5003}
5004
5005#[gpui::test]
5006fn test_select_line(cx: &mut TestAppContext) {
5007 init_test(cx, |_| {});
5008
5009 let editor = cx.add_window(|window, cx| {
5010 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5011 build_editor(buffer, window, cx)
5012 });
5013 _ = editor.update(cx, |editor, window, cx| {
5014 editor.change_selections(None, window, cx, |s| {
5015 s.select_display_ranges([
5016 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5017 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5018 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5019 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5020 ])
5021 });
5022 editor.select_line(&SelectLine, window, cx);
5023 assert_eq!(
5024 editor.selections.display_ranges(cx),
5025 vec![
5026 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5027 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5028 ]
5029 );
5030 });
5031
5032 _ = editor.update(cx, |editor, window, cx| {
5033 editor.select_line(&SelectLine, window, cx);
5034 assert_eq!(
5035 editor.selections.display_ranges(cx),
5036 vec![
5037 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5038 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5039 ]
5040 );
5041 });
5042
5043 _ = editor.update(cx, |editor, window, cx| {
5044 editor.select_line(&SelectLine, window, cx);
5045 assert_eq!(
5046 editor.selections.display_ranges(cx),
5047 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5048 );
5049 });
5050}
5051
5052#[gpui::test]
5053async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5054 init_test(cx, |_| {});
5055 let mut cx = EditorTestContext::new(cx).await;
5056
5057 #[track_caller]
5058 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5059 cx.set_state(initial_state);
5060 cx.update_editor(|e, window, cx| {
5061 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5062 });
5063 cx.assert_editor_state(expected_state);
5064 }
5065
5066 // Selection starts and ends at the middle of lines, left-to-right
5067 test(
5068 &mut cx,
5069 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5070 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5071 );
5072 // Same thing, right-to-left
5073 test(
5074 &mut cx,
5075 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5076 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5077 );
5078
5079 // Whole buffer, left-to-right, last line *doesn't* end with newline
5080 test(
5081 &mut cx,
5082 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5083 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5084 );
5085 // Same thing, right-to-left
5086 test(
5087 &mut cx,
5088 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5089 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5090 );
5091
5092 // Whole buffer, left-to-right, last line ends with newline
5093 test(
5094 &mut cx,
5095 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5096 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5097 );
5098 // Same thing, right-to-left
5099 test(
5100 &mut cx,
5101 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5102 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5103 );
5104
5105 // Starts at the end of a line, ends at the start of another
5106 test(
5107 &mut cx,
5108 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5109 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5110 );
5111}
5112
5113#[gpui::test]
5114async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5115 init_test(cx, |_| {});
5116
5117 let editor = cx.add_window(|window, cx| {
5118 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5119 build_editor(buffer, window, cx)
5120 });
5121
5122 // setup
5123 _ = editor.update(cx, |editor, window, cx| {
5124 editor.fold_creases(
5125 vec![
5126 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5127 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5128 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5129 ],
5130 true,
5131 window,
5132 cx,
5133 );
5134 assert_eq!(
5135 editor.display_text(cx),
5136 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5137 );
5138 });
5139
5140 _ = editor.update(cx, |editor, window, cx| {
5141 editor.change_selections(None, window, cx, |s| {
5142 s.select_display_ranges([
5143 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5144 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5145 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5146 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5147 ])
5148 });
5149 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5150 assert_eq!(
5151 editor.display_text(cx),
5152 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5153 );
5154 });
5155 EditorTestContext::for_editor(editor, cx)
5156 .await
5157 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5158
5159 _ = editor.update(cx, |editor, window, cx| {
5160 editor.change_selections(None, window, cx, |s| {
5161 s.select_display_ranges([
5162 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5163 ])
5164 });
5165 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5166 assert_eq!(
5167 editor.display_text(cx),
5168 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5169 );
5170 assert_eq!(
5171 editor.selections.display_ranges(cx),
5172 [
5173 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5174 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5175 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5176 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5177 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5178 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5179 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5180 ]
5181 );
5182 });
5183 EditorTestContext::for_editor(editor, cx)
5184 .await
5185 .assert_editor_state(
5186 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5187 );
5188}
5189
5190#[gpui::test]
5191async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5192 init_test(cx, |_| {});
5193
5194 let mut cx = EditorTestContext::new(cx).await;
5195
5196 cx.set_state(indoc!(
5197 r#"abc
5198 defˇghi
5199
5200 jk
5201 nlmo
5202 "#
5203 ));
5204
5205 cx.update_editor(|editor, window, cx| {
5206 editor.add_selection_above(&Default::default(), window, cx);
5207 });
5208
5209 cx.assert_editor_state(indoc!(
5210 r#"abcˇ
5211 defˇghi
5212
5213 jk
5214 nlmo
5215 "#
5216 ));
5217
5218 cx.update_editor(|editor, window, cx| {
5219 editor.add_selection_above(&Default::default(), window, cx);
5220 });
5221
5222 cx.assert_editor_state(indoc!(
5223 r#"abcˇ
5224 defˇghi
5225
5226 jk
5227 nlmo
5228 "#
5229 ));
5230
5231 cx.update_editor(|editor, window, cx| {
5232 editor.add_selection_below(&Default::default(), window, cx);
5233 });
5234
5235 cx.assert_editor_state(indoc!(
5236 r#"abc
5237 defˇghi
5238
5239 jk
5240 nlmo
5241 "#
5242 ));
5243
5244 cx.update_editor(|editor, window, cx| {
5245 editor.undo_selection(&Default::default(), window, cx);
5246 });
5247
5248 cx.assert_editor_state(indoc!(
5249 r#"abcˇ
5250 defˇghi
5251
5252 jk
5253 nlmo
5254 "#
5255 ));
5256
5257 cx.update_editor(|editor, window, cx| {
5258 editor.redo_selection(&Default::default(), window, cx);
5259 });
5260
5261 cx.assert_editor_state(indoc!(
5262 r#"abc
5263 defˇghi
5264
5265 jk
5266 nlmo
5267 "#
5268 ));
5269
5270 cx.update_editor(|editor, window, cx| {
5271 editor.add_selection_below(&Default::default(), window, cx);
5272 });
5273
5274 cx.assert_editor_state(indoc!(
5275 r#"abc
5276 defˇghi
5277
5278 jk
5279 nlmˇo
5280 "#
5281 ));
5282
5283 cx.update_editor(|editor, window, cx| {
5284 editor.add_selection_below(&Default::default(), window, cx);
5285 });
5286
5287 cx.assert_editor_state(indoc!(
5288 r#"abc
5289 defˇghi
5290
5291 jk
5292 nlmˇo
5293 "#
5294 ));
5295
5296 // change selections
5297 cx.set_state(indoc!(
5298 r#"abc
5299 def«ˇg»hi
5300
5301 jk
5302 nlmo
5303 "#
5304 ));
5305
5306 cx.update_editor(|editor, window, cx| {
5307 editor.add_selection_below(&Default::default(), window, cx);
5308 });
5309
5310 cx.assert_editor_state(indoc!(
5311 r#"abc
5312 def«ˇg»hi
5313
5314 jk
5315 nlm«ˇo»
5316 "#
5317 ));
5318
5319 cx.update_editor(|editor, window, cx| {
5320 editor.add_selection_below(&Default::default(), window, cx);
5321 });
5322
5323 cx.assert_editor_state(indoc!(
5324 r#"abc
5325 def«ˇg»hi
5326
5327 jk
5328 nlm«ˇo»
5329 "#
5330 ));
5331
5332 cx.update_editor(|editor, window, cx| {
5333 editor.add_selection_above(&Default::default(), window, cx);
5334 });
5335
5336 cx.assert_editor_state(indoc!(
5337 r#"abc
5338 def«ˇg»hi
5339
5340 jk
5341 nlmo
5342 "#
5343 ));
5344
5345 cx.update_editor(|editor, window, cx| {
5346 editor.add_selection_above(&Default::default(), window, cx);
5347 });
5348
5349 cx.assert_editor_state(indoc!(
5350 r#"abc
5351 def«ˇg»hi
5352
5353 jk
5354 nlmo
5355 "#
5356 ));
5357
5358 // Change selections again
5359 cx.set_state(indoc!(
5360 r#"a«bc
5361 defgˇ»hi
5362
5363 jk
5364 nlmo
5365 "#
5366 ));
5367
5368 cx.update_editor(|editor, window, cx| {
5369 editor.add_selection_below(&Default::default(), window, cx);
5370 });
5371
5372 cx.assert_editor_state(indoc!(
5373 r#"a«bcˇ»
5374 d«efgˇ»hi
5375
5376 j«kˇ»
5377 nlmo
5378 "#
5379 ));
5380
5381 cx.update_editor(|editor, window, cx| {
5382 editor.add_selection_below(&Default::default(), window, cx);
5383 });
5384 cx.assert_editor_state(indoc!(
5385 r#"a«bcˇ»
5386 d«efgˇ»hi
5387
5388 j«kˇ»
5389 n«lmoˇ»
5390 "#
5391 ));
5392 cx.update_editor(|editor, window, cx| {
5393 editor.add_selection_above(&Default::default(), window, cx);
5394 });
5395
5396 cx.assert_editor_state(indoc!(
5397 r#"a«bcˇ»
5398 d«efgˇ»hi
5399
5400 j«kˇ»
5401 nlmo
5402 "#
5403 ));
5404
5405 // Change selections again
5406 cx.set_state(indoc!(
5407 r#"abc
5408 d«ˇefghi
5409
5410 jk
5411 nlm»o
5412 "#
5413 ));
5414
5415 cx.update_editor(|editor, window, cx| {
5416 editor.add_selection_above(&Default::default(), window, cx);
5417 });
5418
5419 cx.assert_editor_state(indoc!(
5420 r#"a«ˇbc»
5421 d«ˇef»ghi
5422
5423 j«ˇk»
5424 n«ˇlm»o
5425 "#
5426 ));
5427
5428 cx.update_editor(|editor, window, cx| {
5429 editor.add_selection_below(&Default::default(), window, cx);
5430 });
5431
5432 cx.assert_editor_state(indoc!(
5433 r#"abc
5434 d«ˇef»ghi
5435
5436 j«ˇk»
5437 n«ˇlm»o
5438 "#
5439 ));
5440}
5441
5442#[gpui::test]
5443async fn test_select_next(cx: &mut TestAppContext) {
5444 init_test(cx, |_| {});
5445
5446 let mut cx = EditorTestContext::new(cx).await;
5447 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5448
5449 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5450 .unwrap();
5451 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5452
5453 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5454 .unwrap();
5455 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5456
5457 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5458 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5459
5460 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5461 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5462
5463 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5464 .unwrap();
5465 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5466
5467 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5468 .unwrap();
5469 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5470}
5471
5472#[gpui::test]
5473async fn test_select_all_matches(cx: &mut TestAppContext) {
5474 init_test(cx, |_| {});
5475
5476 let mut cx = EditorTestContext::new(cx).await;
5477
5478 // Test caret-only selections
5479 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5480 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5481 .unwrap();
5482 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5483
5484 // Test left-to-right selections
5485 cx.set_state("abc\n«abcˇ»\nabc");
5486 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5487 .unwrap();
5488 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5489
5490 // Test right-to-left selections
5491 cx.set_state("abc\n«ˇabc»\nabc");
5492 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5493 .unwrap();
5494 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5495
5496 // Test selecting whitespace with caret selection
5497 cx.set_state("abc\nˇ abc\nabc");
5498 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5499 .unwrap();
5500 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5501
5502 // Test selecting whitespace with left-to-right selection
5503 cx.set_state("abc\n«ˇ »abc\nabc");
5504 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5505 .unwrap();
5506 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5507
5508 // Test no matches with right-to-left selection
5509 cx.set_state("abc\n« ˇ»abc\nabc");
5510 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5511 .unwrap();
5512 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5513}
5514
5515#[gpui::test]
5516async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5517 init_test(cx, |_| {});
5518
5519 let mut cx = EditorTestContext::new(cx).await;
5520 cx.set_state(
5521 r#"let foo = 2;
5522lˇet foo = 2;
5523let fooˇ = 2;
5524let foo = 2;
5525let foo = ˇ2;"#,
5526 );
5527
5528 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5529 .unwrap();
5530 cx.assert_editor_state(
5531 r#"let foo = 2;
5532«letˇ» foo = 2;
5533let «fooˇ» = 2;
5534let foo = 2;
5535let foo = «2ˇ»;"#,
5536 );
5537
5538 // noop for multiple selections with different contents
5539 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5540 .unwrap();
5541 cx.assert_editor_state(
5542 r#"let foo = 2;
5543«letˇ» foo = 2;
5544let «fooˇ» = 2;
5545let foo = 2;
5546let foo = «2ˇ»;"#,
5547 );
5548}
5549
5550#[gpui::test]
5551async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5552 init_test(cx, |_| {});
5553
5554 let mut cx =
5555 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5556
5557 cx.assert_editor_state(indoc! {"
5558 ˇbbb
5559 ccc
5560
5561 bbb
5562 ccc
5563 "});
5564 cx.dispatch_action(SelectPrevious::default());
5565 cx.assert_editor_state(indoc! {"
5566 «bbbˇ»
5567 ccc
5568
5569 bbb
5570 ccc
5571 "});
5572 cx.dispatch_action(SelectPrevious::default());
5573 cx.assert_editor_state(indoc! {"
5574 «bbbˇ»
5575 ccc
5576
5577 «bbbˇ»
5578 ccc
5579 "});
5580}
5581
5582#[gpui::test]
5583async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5584 init_test(cx, |_| {});
5585
5586 let mut cx = EditorTestContext::new(cx).await;
5587 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5588
5589 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5590 .unwrap();
5591 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5592
5593 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5594 .unwrap();
5595 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5596
5597 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5598 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5599
5600 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5601 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5602
5603 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5604 .unwrap();
5605 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5606
5607 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5608 .unwrap();
5609 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5610
5611 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5612 .unwrap();
5613 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5614}
5615
5616#[gpui::test]
5617async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5618 init_test(cx, |_| {});
5619
5620 let mut cx = EditorTestContext::new(cx).await;
5621 cx.set_state("aˇ");
5622
5623 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5624 .unwrap();
5625 cx.assert_editor_state("«aˇ»");
5626 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5627 .unwrap();
5628 cx.assert_editor_state("«aˇ»");
5629}
5630
5631#[gpui::test]
5632async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5633 init_test(cx, |_| {});
5634
5635 let mut cx = EditorTestContext::new(cx).await;
5636 cx.set_state(
5637 r#"let foo = 2;
5638lˇet foo = 2;
5639let fooˇ = 2;
5640let foo = 2;
5641let foo = ˇ2;"#,
5642 );
5643
5644 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5645 .unwrap();
5646 cx.assert_editor_state(
5647 r#"let foo = 2;
5648«letˇ» foo = 2;
5649let «fooˇ» = 2;
5650let foo = 2;
5651let foo = «2ˇ»;"#,
5652 );
5653
5654 // noop for multiple selections with different contents
5655 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5656 .unwrap();
5657 cx.assert_editor_state(
5658 r#"let foo = 2;
5659«letˇ» foo = 2;
5660let «fooˇ» = 2;
5661let foo = 2;
5662let foo = «2ˇ»;"#,
5663 );
5664}
5665
5666#[gpui::test]
5667async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5668 init_test(cx, |_| {});
5669
5670 let mut cx = EditorTestContext::new(cx).await;
5671 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5672
5673 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5674 .unwrap();
5675 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5676
5677 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5678 .unwrap();
5679 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5680
5681 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5682 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5683
5684 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5685 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5686
5687 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5688 .unwrap();
5689 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5690
5691 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5692 .unwrap();
5693 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5694}
5695
5696#[gpui::test]
5697async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5698 init_test(cx, |_| {});
5699
5700 let language = Arc::new(Language::new(
5701 LanguageConfig::default(),
5702 Some(tree_sitter_rust::LANGUAGE.into()),
5703 ));
5704
5705 let text = r#"
5706 use mod1::mod2::{mod3, mod4};
5707
5708 fn fn_1(param1: bool, param2: &str) {
5709 let var1 = "text";
5710 }
5711 "#
5712 .unindent();
5713
5714 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5715 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5716 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5717
5718 editor
5719 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5720 .await;
5721
5722 editor.update_in(cx, |editor, window, cx| {
5723 editor.change_selections(None, window, cx, |s| {
5724 s.select_display_ranges([
5725 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5726 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5727 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5728 ]);
5729 });
5730 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5731 });
5732 editor.update(cx, |editor, cx| {
5733 assert_text_with_selections(
5734 editor,
5735 indoc! {r#"
5736 use mod1::mod2::{mod3, «mod4ˇ»};
5737
5738 fn fn_1«ˇ(param1: bool, param2: &str)» {
5739 let var1 = "«textˇ»";
5740 }
5741 "#},
5742 cx,
5743 );
5744 });
5745
5746 editor.update_in(cx, |editor, window, cx| {
5747 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5748 });
5749 editor.update(cx, |editor, cx| {
5750 assert_text_with_selections(
5751 editor,
5752 indoc! {r#"
5753 use mod1::mod2::«{mod3, mod4}ˇ»;
5754
5755 «ˇfn fn_1(param1: bool, param2: &str) {
5756 let var1 = "text";
5757 }»
5758 "#},
5759 cx,
5760 );
5761 });
5762
5763 editor.update_in(cx, |editor, window, cx| {
5764 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5765 });
5766 assert_eq!(
5767 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5768 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5769 );
5770
5771 // Trying to expand the selected syntax node one more time has no effect.
5772 editor.update_in(cx, |editor, window, cx| {
5773 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5774 });
5775 assert_eq!(
5776 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5777 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5778 );
5779
5780 editor.update_in(cx, |editor, window, cx| {
5781 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5782 });
5783 editor.update(cx, |editor, cx| {
5784 assert_text_with_selections(
5785 editor,
5786 indoc! {r#"
5787 use mod1::mod2::«{mod3, mod4}ˇ»;
5788
5789 «ˇfn fn_1(param1: bool, param2: &str) {
5790 let var1 = "text";
5791 }»
5792 "#},
5793 cx,
5794 );
5795 });
5796
5797 editor.update_in(cx, |editor, window, cx| {
5798 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5799 });
5800 editor.update(cx, |editor, cx| {
5801 assert_text_with_selections(
5802 editor,
5803 indoc! {r#"
5804 use mod1::mod2::{mod3, «mod4ˇ»};
5805
5806 fn fn_1«ˇ(param1: bool, param2: &str)» {
5807 let var1 = "«textˇ»";
5808 }
5809 "#},
5810 cx,
5811 );
5812 });
5813
5814 editor.update_in(cx, |editor, window, cx| {
5815 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5816 });
5817 editor.update(cx, |editor, cx| {
5818 assert_text_with_selections(
5819 editor,
5820 indoc! {r#"
5821 use mod1::mod2::{mod3, mo«ˇ»d4};
5822
5823 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5824 let var1 = "te«ˇ»xt";
5825 }
5826 "#},
5827 cx,
5828 );
5829 });
5830
5831 // Trying to shrink the selected syntax node one more time has no effect.
5832 editor.update_in(cx, |editor, window, cx| {
5833 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5834 });
5835 editor.update_in(cx, |editor, _, cx| {
5836 assert_text_with_selections(
5837 editor,
5838 indoc! {r#"
5839 use mod1::mod2::{mod3, mo«ˇ»d4};
5840
5841 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5842 let var1 = "te«ˇ»xt";
5843 }
5844 "#},
5845 cx,
5846 );
5847 });
5848
5849 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5850 // a fold.
5851 editor.update_in(cx, |editor, window, cx| {
5852 editor.fold_creases(
5853 vec![
5854 Crease::simple(
5855 Point::new(0, 21)..Point::new(0, 24),
5856 FoldPlaceholder::test(),
5857 ),
5858 Crease::simple(
5859 Point::new(3, 20)..Point::new(3, 22),
5860 FoldPlaceholder::test(),
5861 ),
5862 ],
5863 true,
5864 window,
5865 cx,
5866 );
5867 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5868 });
5869 editor.update(cx, |editor, cx| {
5870 assert_text_with_selections(
5871 editor,
5872 indoc! {r#"
5873 use mod1::mod2::«{mod3, mod4}ˇ»;
5874
5875 fn fn_1«ˇ(param1: bool, param2: &str)» {
5876 «let var1 = "text";ˇ»
5877 }
5878 "#},
5879 cx,
5880 );
5881 });
5882}
5883
5884#[gpui::test]
5885async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5886 init_test(cx, |_| {});
5887
5888 let base_text = r#"
5889 impl A {
5890 // this is an uncommitted comment
5891
5892 fn b() {
5893 c();
5894 }
5895
5896 // this is another uncommitted comment
5897
5898 fn d() {
5899 // e
5900 // f
5901 }
5902 }
5903
5904 fn g() {
5905 // h
5906 }
5907 "#
5908 .unindent();
5909
5910 let text = r#"
5911 ˇimpl A {
5912
5913 fn b() {
5914 c();
5915 }
5916
5917 fn d() {
5918 // e
5919 // f
5920 }
5921 }
5922
5923 fn g() {
5924 // h
5925 }
5926 "#
5927 .unindent();
5928
5929 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5930 cx.set_state(&text);
5931 cx.set_head_text(&base_text);
5932 cx.update_editor(|editor, window, cx| {
5933 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5934 });
5935
5936 cx.assert_state_with_diff(
5937 "
5938 ˇimpl A {
5939 - // this is an uncommitted comment
5940
5941 fn b() {
5942 c();
5943 }
5944
5945 - // this is another uncommitted comment
5946 -
5947 fn d() {
5948 // e
5949 // f
5950 }
5951 }
5952
5953 fn g() {
5954 // h
5955 }
5956 "
5957 .unindent(),
5958 );
5959
5960 let expected_display_text = "
5961 impl A {
5962 // this is an uncommitted comment
5963
5964 fn b() {
5965 ⋯
5966 }
5967
5968 // this is another uncommitted comment
5969
5970 fn d() {
5971 ⋯
5972 }
5973 }
5974
5975 fn g() {
5976 ⋯
5977 }
5978 "
5979 .unindent();
5980
5981 cx.update_editor(|editor, window, cx| {
5982 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
5983 assert_eq!(editor.display_text(cx), expected_display_text);
5984 });
5985}
5986
5987#[gpui::test]
5988async fn test_autoindent(cx: &mut TestAppContext) {
5989 init_test(cx, |_| {});
5990
5991 let language = Arc::new(
5992 Language::new(
5993 LanguageConfig {
5994 brackets: BracketPairConfig {
5995 pairs: vec![
5996 BracketPair {
5997 start: "{".to_string(),
5998 end: "}".to_string(),
5999 close: false,
6000 surround: false,
6001 newline: true,
6002 },
6003 BracketPair {
6004 start: "(".to_string(),
6005 end: ")".to_string(),
6006 close: false,
6007 surround: false,
6008 newline: true,
6009 },
6010 ],
6011 ..Default::default()
6012 },
6013 ..Default::default()
6014 },
6015 Some(tree_sitter_rust::LANGUAGE.into()),
6016 )
6017 .with_indents_query(
6018 r#"
6019 (_ "(" ")" @end) @indent
6020 (_ "{" "}" @end) @indent
6021 "#,
6022 )
6023 .unwrap(),
6024 );
6025
6026 let text = "fn a() {}";
6027
6028 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6029 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6030 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6031 editor
6032 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6033 .await;
6034
6035 editor.update_in(cx, |editor, window, cx| {
6036 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6037 editor.newline(&Newline, window, cx);
6038 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6039 assert_eq!(
6040 editor.selections.ranges(cx),
6041 &[
6042 Point::new(1, 4)..Point::new(1, 4),
6043 Point::new(3, 4)..Point::new(3, 4),
6044 Point::new(5, 0)..Point::new(5, 0)
6045 ]
6046 );
6047 });
6048}
6049
6050#[gpui::test]
6051async fn test_autoindent_selections(cx: &mut TestAppContext) {
6052 init_test(cx, |_| {});
6053
6054 {
6055 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6056 cx.set_state(indoc! {"
6057 impl A {
6058
6059 fn b() {}
6060
6061 «fn c() {
6062
6063 }ˇ»
6064 }
6065 "});
6066
6067 cx.update_editor(|editor, window, cx| {
6068 editor.autoindent(&Default::default(), window, cx);
6069 });
6070
6071 cx.assert_editor_state(indoc! {"
6072 impl A {
6073
6074 fn b() {}
6075
6076 «fn c() {
6077
6078 }ˇ»
6079 }
6080 "});
6081 }
6082
6083 {
6084 let mut cx = EditorTestContext::new_multibuffer(
6085 cx,
6086 [indoc! { "
6087 impl A {
6088 «
6089 // a
6090 fn b(){}
6091 »
6092 «
6093 }
6094 fn c(){}
6095 »
6096 "}],
6097 );
6098
6099 let buffer = cx.update_editor(|editor, _, cx| {
6100 let buffer = editor.buffer().update(cx, |buffer, _| {
6101 buffer.all_buffers().iter().next().unwrap().clone()
6102 });
6103 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6104 buffer
6105 });
6106
6107 cx.run_until_parked();
6108 cx.update_editor(|editor, window, cx| {
6109 editor.select_all(&Default::default(), window, cx);
6110 editor.autoindent(&Default::default(), window, cx)
6111 });
6112 cx.run_until_parked();
6113
6114 cx.update(|_, cx| {
6115 pretty_assertions::assert_eq!(
6116 buffer.read(cx).text(),
6117 indoc! { "
6118 impl A {
6119
6120 // a
6121 fn b(){}
6122
6123
6124 }
6125 fn c(){}
6126
6127 " }
6128 )
6129 });
6130 }
6131}
6132
6133#[gpui::test]
6134async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6135 init_test(cx, |_| {});
6136
6137 let mut cx = EditorTestContext::new(cx).await;
6138
6139 let language = Arc::new(Language::new(
6140 LanguageConfig {
6141 brackets: BracketPairConfig {
6142 pairs: vec![
6143 BracketPair {
6144 start: "{".to_string(),
6145 end: "}".to_string(),
6146 close: true,
6147 surround: true,
6148 newline: true,
6149 },
6150 BracketPair {
6151 start: "(".to_string(),
6152 end: ")".to_string(),
6153 close: true,
6154 surround: true,
6155 newline: true,
6156 },
6157 BracketPair {
6158 start: "/*".to_string(),
6159 end: " */".to_string(),
6160 close: true,
6161 surround: true,
6162 newline: true,
6163 },
6164 BracketPair {
6165 start: "[".to_string(),
6166 end: "]".to_string(),
6167 close: false,
6168 surround: false,
6169 newline: true,
6170 },
6171 BracketPair {
6172 start: "\"".to_string(),
6173 end: "\"".to_string(),
6174 close: true,
6175 surround: true,
6176 newline: false,
6177 },
6178 BracketPair {
6179 start: "<".to_string(),
6180 end: ">".to_string(),
6181 close: false,
6182 surround: true,
6183 newline: true,
6184 },
6185 ],
6186 ..Default::default()
6187 },
6188 autoclose_before: "})]".to_string(),
6189 ..Default::default()
6190 },
6191 Some(tree_sitter_rust::LANGUAGE.into()),
6192 ));
6193
6194 cx.language_registry().add(language.clone());
6195 cx.update_buffer(|buffer, cx| {
6196 buffer.set_language(Some(language), cx);
6197 });
6198
6199 cx.set_state(
6200 &r#"
6201 🏀ˇ
6202 εˇ
6203 ❤️ˇ
6204 "#
6205 .unindent(),
6206 );
6207
6208 // autoclose multiple nested brackets at multiple cursors
6209 cx.update_editor(|editor, window, cx| {
6210 editor.handle_input("{", window, cx);
6211 editor.handle_input("{", window, cx);
6212 editor.handle_input("{", window, cx);
6213 });
6214 cx.assert_editor_state(
6215 &"
6216 🏀{{{ˇ}}}
6217 ε{{{ˇ}}}
6218 ❤️{{{ˇ}}}
6219 "
6220 .unindent(),
6221 );
6222
6223 // insert a different closing bracket
6224 cx.update_editor(|editor, window, cx| {
6225 editor.handle_input(")", window, cx);
6226 });
6227 cx.assert_editor_state(
6228 &"
6229 🏀{{{)ˇ}}}
6230 ε{{{)ˇ}}}
6231 ❤️{{{)ˇ}}}
6232 "
6233 .unindent(),
6234 );
6235
6236 // skip over the auto-closed brackets when typing a closing bracket
6237 cx.update_editor(|editor, window, cx| {
6238 editor.move_right(&MoveRight, window, cx);
6239 editor.handle_input("}", window, cx);
6240 editor.handle_input("}", window, cx);
6241 editor.handle_input("}", window, cx);
6242 });
6243 cx.assert_editor_state(
6244 &"
6245 🏀{{{)}}}}ˇ
6246 ε{{{)}}}}ˇ
6247 ❤️{{{)}}}}ˇ
6248 "
6249 .unindent(),
6250 );
6251
6252 // autoclose multi-character pairs
6253 cx.set_state(
6254 &"
6255 ˇ
6256 ˇ
6257 "
6258 .unindent(),
6259 );
6260 cx.update_editor(|editor, window, cx| {
6261 editor.handle_input("/", window, cx);
6262 editor.handle_input("*", window, cx);
6263 });
6264 cx.assert_editor_state(
6265 &"
6266 /*ˇ */
6267 /*ˇ */
6268 "
6269 .unindent(),
6270 );
6271
6272 // one cursor autocloses a multi-character pair, one cursor
6273 // does not autoclose.
6274 cx.set_state(
6275 &"
6276 /ˇ
6277 ˇ
6278 "
6279 .unindent(),
6280 );
6281 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6282 cx.assert_editor_state(
6283 &"
6284 /*ˇ */
6285 *ˇ
6286 "
6287 .unindent(),
6288 );
6289
6290 // Don't autoclose if the next character isn't whitespace and isn't
6291 // listed in the language's "autoclose_before" section.
6292 cx.set_state("ˇa b");
6293 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6294 cx.assert_editor_state("{ˇa b");
6295
6296 // Don't autoclose if `close` is false for the bracket pair
6297 cx.set_state("ˇ");
6298 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6299 cx.assert_editor_state("[ˇ");
6300
6301 // Surround with brackets if text is selected
6302 cx.set_state("«aˇ» b");
6303 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6304 cx.assert_editor_state("{«aˇ»} b");
6305
6306 // Autclose pair where the start and end characters are the same
6307 cx.set_state("aˇ");
6308 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6309 cx.assert_editor_state("a\"ˇ\"");
6310 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6311 cx.assert_editor_state("a\"\"ˇ");
6312
6313 // Don't autoclose pair if autoclose is disabled
6314 cx.set_state("ˇ");
6315 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6316 cx.assert_editor_state("<ˇ");
6317
6318 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6319 cx.set_state("«aˇ» b");
6320 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6321 cx.assert_editor_state("<«aˇ»> b");
6322}
6323
6324#[gpui::test]
6325async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6326 init_test(cx, |settings| {
6327 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6328 });
6329
6330 let mut cx = EditorTestContext::new(cx).await;
6331
6332 let language = Arc::new(Language::new(
6333 LanguageConfig {
6334 brackets: BracketPairConfig {
6335 pairs: vec![
6336 BracketPair {
6337 start: "{".to_string(),
6338 end: "}".to_string(),
6339 close: true,
6340 surround: true,
6341 newline: true,
6342 },
6343 BracketPair {
6344 start: "(".to_string(),
6345 end: ")".to_string(),
6346 close: true,
6347 surround: true,
6348 newline: true,
6349 },
6350 BracketPair {
6351 start: "[".to_string(),
6352 end: "]".to_string(),
6353 close: false,
6354 surround: false,
6355 newline: true,
6356 },
6357 ],
6358 ..Default::default()
6359 },
6360 autoclose_before: "})]".to_string(),
6361 ..Default::default()
6362 },
6363 Some(tree_sitter_rust::LANGUAGE.into()),
6364 ));
6365
6366 cx.language_registry().add(language.clone());
6367 cx.update_buffer(|buffer, cx| {
6368 buffer.set_language(Some(language), cx);
6369 });
6370
6371 cx.set_state(
6372 &"
6373 ˇ
6374 ˇ
6375 ˇ
6376 "
6377 .unindent(),
6378 );
6379
6380 // ensure only matching closing brackets are skipped over
6381 cx.update_editor(|editor, window, cx| {
6382 editor.handle_input("}", window, cx);
6383 editor.move_left(&MoveLeft, window, cx);
6384 editor.handle_input(")", window, cx);
6385 editor.move_left(&MoveLeft, window, cx);
6386 });
6387 cx.assert_editor_state(
6388 &"
6389 ˇ)}
6390 ˇ)}
6391 ˇ)}
6392 "
6393 .unindent(),
6394 );
6395
6396 // skip-over closing brackets at multiple cursors
6397 cx.update_editor(|editor, window, cx| {
6398 editor.handle_input(")", window, cx);
6399 editor.handle_input("}", window, cx);
6400 });
6401 cx.assert_editor_state(
6402 &"
6403 )}ˇ
6404 )}ˇ
6405 )}ˇ
6406 "
6407 .unindent(),
6408 );
6409
6410 // ignore non-close brackets
6411 cx.update_editor(|editor, window, cx| {
6412 editor.handle_input("]", window, cx);
6413 editor.move_left(&MoveLeft, window, cx);
6414 editor.handle_input("]", window, cx);
6415 });
6416 cx.assert_editor_state(
6417 &"
6418 )}]ˇ]
6419 )}]ˇ]
6420 )}]ˇ]
6421 "
6422 .unindent(),
6423 );
6424}
6425
6426#[gpui::test]
6427async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6428 init_test(cx, |_| {});
6429
6430 let mut cx = EditorTestContext::new(cx).await;
6431
6432 let html_language = Arc::new(
6433 Language::new(
6434 LanguageConfig {
6435 name: "HTML".into(),
6436 brackets: BracketPairConfig {
6437 pairs: vec![
6438 BracketPair {
6439 start: "<".into(),
6440 end: ">".into(),
6441 close: true,
6442 ..Default::default()
6443 },
6444 BracketPair {
6445 start: "{".into(),
6446 end: "}".into(),
6447 close: true,
6448 ..Default::default()
6449 },
6450 BracketPair {
6451 start: "(".into(),
6452 end: ")".into(),
6453 close: true,
6454 ..Default::default()
6455 },
6456 ],
6457 ..Default::default()
6458 },
6459 autoclose_before: "})]>".into(),
6460 ..Default::default()
6461 },
6462 Some(tree_sitter_html::LANGUAGE.into()),
6463 )
6464 .with_injection_query(
6465 r#"
6466 (script_element
6467 (raw_text) @injection.content
6468 (#set! injection.language "javascript"))
6469 "#,
6470 )
6471 .unwrap(),
6472 );
6473
6474 let javascript_language = Arc::new(Language::new(
6475 LanguageConfig {
6476 name: "JavaScript".into(),
6477 brackets: BracketPairConfig {
6478 pairs: vec![
6479 BracketPair {
6480 start: "/*".into(),
6481 end: " */".into(),
6482 close: true,
6483 ..Default::default()
6484 },
6485 BracketPair {
6486 start: "{".into(),
6487 end: "}".into(),
6488 close: true,
6489 ..Default::default()
6490 },
6491 BracketPair {
6492 start: "(".into(),
6493 end: ")".into(),
6494 close: true,
6495 ..Default::default()
6496 },
6497 ],
6498 ..Default::default()
6499 },
6500 autoclose_before: "})]>".into(),
6501 ..Default::default()
6502 },
6503 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6504 ));
6505
6506 cx.language_registry().add(html_language.clone());
6507 cx.language_registry().add(javascript_language.clone());
6508
6509 cx.update_buffer(|buffer, cx| {
6510 buffer.set_language(Some(html_language), cx);
6511 });
6512
6513 cx.set_state(
6514 &r#"
6515 <body>ˇ
6516 <script>
6517 var x = 1;ˇ
6518 </script>
6519 </body>ˇ
6520 "#
6521 .unindent(),
6522 );
6523
6524 // Precondition: different languages are active at different locations.
6525 cx.update_editor(|editor, window, cx| {
6526 let snapshot = editor.snapshot(window, cx);
6527 let cursors = editor.selections.ranges::<usize>(cx);
6528 let languages = cursors
6529 .iter()
6530 .map(|c| snapshot.language_at(c.start).unwrap().name())
6531 .collect::<Vec<_>>();
6532 assert_eq!(
6533 languages,
6534 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6535 );
6536 });
6537
6538 // Angle brackets autoclose in HTML, but not JavaScript.
6539 cx.update_editor(|editor, window, cx| {
6540 editor.handle_input("<", window, cx);
6541 editor.handle_input("a", window, cx);
6542 });
6543 cx.assert_editor_state(
6544 &r#"
6545 <body><aˇ>
6546 <script>
6547 var x = 1;<aˇ
6548 </script>
6549 </body><aˇ>
6550 "#
6551 .unindent(),
6552 );
6553
6554 // Curly braces and parens autoclose in both HTML and JavaScript.
6555 cx.update_editor(|editor, window, cx| {
6556 editor.handle_input(" b=", window, cx);
6557 editor.handle_input("{", window, cx);
6558 editor.handle_input("c", window, cx);
6559 editor.handle_input("(", window, cx);
6560 });
6561 cx.assert_editor_state(
6562 &r#"
6563 <body><a b={c(ˇ)}>
6564 <script>
6565 var x = 1;<a b={c(ˇ)}
6566 </script>
6567 </body><a b={c(ˇ)}>
6568 "#
6569 .unindent(),
6570 );
6571
6572 // Brackets that were already autoclosed are skipped.
6573 cx.update_editor(|editor, window, cx| {
6574 editor.handle_input(")", window, cx);
6575 editor.handle_input("d", window, cx);
6576 editor.handle_input("}", window, cx);
6577 });
6578 cx.assert_editor_state(
6579 &r#"
6580 <body><a b={c()d}ˇ>
6581 <script>
6582 var x = 1;<a b={c()d}ˇ
6583 </script>
6584 </body><a b={c()d}ˇ>
6585 "#
6586 .unindent(),
6587 );
6588 cx.update_editor(|editor, window, cx| {
6589 editor.handle_input(">", window, cx);
6590 });
6591 cx.assert_editor_state(
6592 &r#"
6593 <body><a b={c()d}>ˇ
6594 <script>
6595 var x = 1;<a b={c()d}>ˇ
6596 </script>
6597 </body><a b={c()d}>ˇ
6598 "#
6599 .unindent(),
6600 );
6601
6602 // Reset
6603 cx.set_state(
6604 &r#"
6605 <body>ˇ
6606 <script>
6607 var x = 1;ˇ
6608 </script>
6609 </body>ˇ
6610 "#
6611 .unindent(),
6612 );
6613
6614 cx.update_editor(|editor, window, cx| {
6615 editor.handle_input("<", window, cx);
6616 });
6617 cx.assert_editor_state(
6618 &r#"
6619 <body><ˇ>
6620 <script>
6621 var x = 1;<ˇ
6622 </script>
6623 </body><ˇ>
6624 "#
6625 .unindent(),
6626 );
6627
6628 // When backspacing, the closing angle brackets are removed.
6629 cx.update_editor(|editor, window, cx| {
6630 editor.backspace(&Backspace, window, cx);
6631 });
6632 cx.assert_editor_state(
6633 &r#"
6634 <body>ˇ
6635 <script>
6636 var x = 1;ˇ
6637 </script>
6638 </body>ˇ
6639 "#
6640 .unindent(),
6641 );
6642
6643 // Block comments autoclose in JavaScript, but not HTML.
6644 cx.update_editor(|editor, window, cx| {
6645 editor.handle_input("/", window, cx);
6646 editor.handle_input("*", window, cx);
6647 });
6648 cx.assert_editor_state(
6649 &r#"
6650 <body>/*ˇ
6651 <script>
6652 var x = 1;/*ˇ */
6653 </script>
6654 </body>/*ˇ
6655 "#
6656 .unindent(),
6657 );
6658}
6659
6660#[gpui::test]
6661async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6662 init_test(cx, |_| {});
6663
6664 let mut cx = EditorTestContext::new(cx).await;
6665
6666 let rust_language = Arc::new(
6667 Language::new(
6668 LanguageConfig {
6669 name: "Rust".into(),
6670 brackets: serde_json::from_value(json!([
6671 { "start": "{", "end": "}", "close": true, "newline": true },
6672 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6673 ]))
6674 .unwrap(),
6675 autoclose_before: "})]>".into(),
6676 ..Default::default()
6677 },
6678 Some(tree_sitter_rust::LANGUAGE.into()),
6679 )
6680 .with_override_query("(string_literal) @string")
6681 .unwrap(),
6682 );
6683
6684 cx.language_registry().add(rust_language.clone());
6685 cx.update_buffer(|buffer, cx| {
6686 buffer.set_language(Some(rust_language), cx);
6687 });
6688
6689 cx.set_state(
6690 &r#"
6691 let x = ˇ
6692 "#
6693 .unindent(),
6694 );
6695
6696 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6697 cx.update_editor(|editor, window, cx| {
6698 editor.handle_input("\"", window, cx);
6699 });
6700 cx.assert_editor_state(
6701 &r#"
6702 let x = "ˇ"
6703 "#
6704 .unindent(),
6705 );
6706
6707 // Inserting another quotation mark. The cursor moves across the existing
6708 // automatically-inserted quotation mark.
6709 cx.update_editor(|editor, window, cx| {
6710 editor.handle_input("\"", window, cx);
6711 });
6712 cx.assert_editor_state(
6713 &r#"
6714 let x = ""ˇ
6715 "#
6716 .unindent(),
6717 );
6718
6719 // Reset
6720 cx.set_state(
6721 &r#"
6722 let x = ˇ
6723 "#
6724 .unindent(),
6725 );
6726
6727 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6728 cx.update_editor(|editor, window, cx| {
6729 editor.handle_input("\"", window, cx);
6730 editor.handle_input(" ", window, cx);
6731 editor.move_left(&Default::default(), window, cx);
6732 editor.handle_input("\\", window, cx);
6733 editor.handle_input("\"", window, cx);
6734 });
6735 cx.assert_editor_state(
6736 &r#"
6737 let x = "\"ˇ "
6738 "#
6739 .unindent(),
6740 );
6741
6742 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6743 // mark. Nothing is inserted.
6744 cx.update_editor(|editor, window, cx| {
6745 editor.move_right(&Default::default(), window, cx);
6746 editor.handle_input("\"", window, cx);
6747 });
6748 cx.assert_editor_state(
6749 &r#"
6750 let x = "\" "ˇ
6751 "#
6752 .unindent(),
6753 );
6754}
6755
6756#[gpui::test]
6757async fn test_surround_with_pair(cx: &mut TestAppContext) {
6758 init_test(cx, |_| {});
6759
6760 let language = Arc::new(Language::new(
6761 LanguageConfig {
6762 brackets: BracketPairConfig {
6763 pairs: vec![
6764 BracketPair {
6765 start: "{".to_string(),
6766 end: "}".to_string(),
6767 close: true,
6768 surround: true,
6769 newline: true,
6770 },
6771 BracketPair {
6772 start: "/* ".to_string(),
6773 end: "*/".to_string(),
6774 close: true,
6775 surround: true,
6776 ..Default::default()
6777 },
6778 ],
6779 ..Default::default()
6780 },
6781 ..Default::default()
6782 },
6783 Some(tree_sitter_rust::LANGUAGE.into()),
6784 ));
6785
6786 let text = r#"
6787 a
6788 b
6789 c
6790 "#
6791 .unindent();
6792
6793 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6794 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6795 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6796 editor
6797 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6798 .await;
6799
6800 editor.update_in(cx, |editor, window, cx| {
6801 editor.change_selections(None, window, cx, |s| {
6802 s.select_display_ranges([
6803 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6804 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6805 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6806 ])
6807 });
6808
6809 editor.handle_input("{", window, cx);
6810 editor.handle_input("{", window, cx);
6811 editor.handle_input("{", window, cx);
6812 assert_eq!(
6813 editor.text(cx),
6814 "
6815 {{{a}}}
6816 {{{b}}}
6817 {{{c}}}
6818 "
6819 .unindent()
6820 );
6821 assert_eq!(
6822 editor.selections.display_ranges(cx),
6823 [
6824 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6825 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6826 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6827 ]
6828 );
6829
6830 editor.undo(&Undo, window, cx);
6831 editor.undo(&Undo, window, cx);
6832 editor.undo(&Undo, window, cx);
6833 assert_eq!(
6834 editor.text(cx),
6835 "
6836 a
6837 b
6838 c
6839 "
6840 .unindent()
6841 );
6842 assert_eq!(
6843 editor.selections.display_ranges(cx),
6844 [
6845 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6846 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6847 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6848 ]
6849 );
6850
6851 // Ensure inserting the first character of a multi-byte bracket pair
6852 // doesn't surround the selections with the bracket.
6853 editor.handle_input("/", window, cx);
6854 assert_eq!(
6855 editor.text(cx),
6856 "
6857 /
6858 /
6859 /
6860 "
6861 .unindent()
6862 );
6863 assert_eq!(
6864 editor.selections.display_ranges(cx),
6865 [
6866 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6867 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6868 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6869 ]
6870 );
6871
6872 editor.undo(&Undo, window, cx);
6873 assert_eq!(
6874 editor.text(cx),
6875 "
6876 a
6877 b
6878 c
6879 "
6880 .unindent()
6881 );
6882 assert_eq!(
6883 editor.selections.display_ranges(cx),
6884 [
6885 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6886 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6887 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6888 ]
6889 );
6890
6891 // Ensure inserting the last character of a multi-byte bracket pair
6892 // doesn't surround the selections with the bracket.
6893 editor.handle_input("*", window, cx);
6894 assert_eq!(
6895 editor.text(cx),
6896 "
6897 *
6898 *
6899 *
6900 "
6901 .unindent()
6902 );
6903 assert_eq!(
6904 editor.selections.display_ranges(cx),
6905 [
6906 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6907 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6908 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6909 ]
6910 );
6911 });
6912}
6913
6914#[gpui::test]
6915async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6916 init_test(cx, |_| {});
6917
6918 let language = Arc::new(Language::new(
6919 LanguageConfig {
6920 brackets: BracketPairConfig {
6921 pairs: vec![BracketPair {
6922 start: "{".to_string(),
6923 end: "}".to_string(),
6924 close: true,
6925 surround: true,
6926 newline: true,
6927 }],
6928 ..Default::default()
6929 },
6930 autoclose_before: "}".to_string(),
6931 ..Default::default()
6932 },
6933 Some(tree_sitter_rust::LANGUAGE.into()),
6934 ));
6935
6936 let text = r#"
6937 a
6938 b
6939 c
6940 "#
6941 .unindent();
6942
6943 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6944 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6945 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6946 editor
6947 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6948 .await;
6949
6950 editor.update_in(cx, |editor, window, cx| {
6951 editor.change_selections(None, window, cx, |s| {
6952 s.select_ranges([
6953 Point::new(0, 1)..Point::new(0, 1),
6954 Point::new(1, 1)..Point::new(1, 1),
6955 Point::new(2, 1)..Point::new(2, 1),
6956 ])
6957 });
6958
6959 editor.handle_input("{", window, cx);
6960 editor.handle_input("{", window, cx);
6961 editor.handle_input("_", window, cx);
6962 assert_eq!(
6963 editor.text(cx),
6964 "
6965 a{{_}}
6966 b{{_}}
6967 c{{_}}
6968 "
6969 .unindent()
6970 );
6971 assert_eq!(
6972 editor.selections.ranges::<Point>(cx),
6973 [
6974 Point::new(0, 4)..Point::new(0, 4),
6975 Point::new(1, 4)..Point::new(1, 4),
6976 Point::new(2, 4)..Point::new(2, 4)
6977 ]
6978 );
6979
6980 editor.backspace(&Default::default(), window, cx);
6981 editor.backspace(&Default::default(), window, cx);
6982 assert_eq!(
6983 editor.text(cx),
6984 "
6985 a{}
6986 b{}
6987 c{}
6988 "
6989 .unindent()
6990 );
6991 assert_eq!(
6992 editor.selections.ranges::<Point>(cx),
6993 [
6994 Point::new(0, 2)..Point::new(0, 2),
6995 Point::new(1, 2)..Point::new(1, 2),
6996 Point::new(2, 2)..Point::new(2, 2)
6997 ]
6998 );
6999
7000 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7001 assert_eq!(
7002 editor.text(cx),
7003 "
7004 a
7005 b
7006 c
7007 "
7008 .unindent()
7009 );
7010 assert_eq!(
7011 editor.selections.ranges::<Point>(cx),
7012 [
7013 Point::new(0, 1)..Point::new(0, 1),
7014 Point::new(1, 1)..Point::new(1, 1),
7015 Point::new(2, 1)..Point::new(2, 1)
7016 ]
7017 );
7018 });
7019}
7020
7021#[gpui::test]
7022async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7023 init_test(cx, |settings| {
7024 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7025 });
7026
7027 let mut cx = EditorTestContext::new(cx).await;
7028
7029 let language = Arc::new(Language::new(
7030 LanguageConfig {
7031 brackets: BracketPairConfig {
7032 pairs: vec![
7033 BracketPair {
7034 start: "{".to_string(),
7035 end: "}".to_string(),
7036 close: true,
7037 surround: true,
7038 newline: true,
7039 },
7040 BracketPair {
7041 start: "(".to_string(),
7042 end: ")".to_string(),
7043 close: true,
7044 surround: true,
7045 newline: true,
7046 },
7047 BracketPair {
7048 start: "[".to_string(),
7049 end: "]".to_string(),
7050 close: false,
7051 surround: true,
7052 newline: true,
7053 },
7054 ],
7055 ..Default::default()
7056 },
7057 autoclose_before: "})]".to_string(),
7058 ..Default::default()
7059 },
7060 Some(tree_sitter_rust::LANGUAGE.into()),
7061 ));
7062
7063 cx.language_registry().add(language.clone());
7064 cx.update_buffer(|buffer, cx| {
7065 buffer.set_language(Some(language), cx);
7066 });
7067
7068 cx.set_state(
7069 &"
7070 {(ˇ)}
7071 [[ˇ]]
7072 {(ˇ)}
7073 "
7074 .unindent(),
7075 );
7076
7077 cx.update_editor(|editor, window, cx| {
7078 editor.backspace(&Default::default(), window, cx);
7079 editor.backspace(&Default::default(), window, cx);
7080 });
7081
7082 cx.assert_editor_state(
7083 &"
7084 ˇ
7085 ˇ]]
7086 ˇ
7087 "
7088 .unindent(),
7089 );
7090
7091 cx.update_editor(|editor, window, cx| {
7092 editor.handle_input("{", window, cx);
7093 editor.handle_input("{", window, cx);
7094 editor.move_right(&MoveRight, window, cx);
7095 editor.move_right(&MoveRight, window, cx);
7096 editor.move_left(&MoveLeft, window, cx);
7097 editor.move_left(&MoveLeft, window, cx);
7098 editor.backspace(&Default::default(), window, cx);
7099 });
7100
7101 cx.assert_editor_state(
7102 &"
7103 {ˇ}
7104 {ˇ}]]
7105 {ˇ}
7106 "
7107 .unindent(),
7108 );
7109
7110 cx.update_editor(|editor, window, cx| {
7111 editor.backspace(&Default::default(), window, cx);
7112 });
7113
7114 cx.assert_editor_state(
7115 &"
7116 ˇ
7117 ˇ]]
7118 ˇ
7119 "
7120 .unindent(),
7121 );
7122}
7123
7124#[gpui::test]
7125async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7126 init_test(cx, |_| {});
7127
7128 let language = Arc::new(Language::new(
7129 LanguageConfig::default(),
7130 Some(tree_sitter_rust::LANGUAGE.into()),
7131 ));
7132
7133 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7134 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7135 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7136 editor
7137 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7138 .await;
7139
7140 editor.update_in(cx, |editor, window, cx| {
7141 editor.set_auto_replace_emoji_shortcode(true);
7142
7143 editor.handle_input("Hello ", window, cx);
7144 editor.handle_input(":wave", window, cx);
7145 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7146
7147 editor.handle_input(":", window, cx);
7148 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7149
7150 editor.handle_input(" :smile", window, cx);
7151 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7152
7153 editor.handle_input(":", window, cx);
7154 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7155
7156 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7157 editor.handle_input(":wave", window, cx);
7158 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7159
7160 editor.handle_input(":", window, cx);
7161 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7162
7163 editor.handle_input(":1", window, cx);
7164 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7165
7166 editor.handle_input(":", window, cx);
7167 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7168
7169 // Ensure shortcode does not get replaced when it is part of a word
7170 editor.handle_input(" Test:wave", window, cx);
7171 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7172
7173 editor.handle_input(":", window, cx);
7174 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7175
7176 editor.set_auto_replace_emoji_shortcode(false);
7177
7178 // Ensure shortcode does not get replaced when auto replace is off
7179 editor.handle_input(" :wave", window, cx);
7180 assert_eq!(
7181 editor.text(cx),
7182 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7183 );
7184
7185 editor.handle_input(":", window, cx);
7186 assert_eq!(
7187 editor.text(cx),
7188 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7189 );
7190 });
7191}
7192
7193#[gpui::test]
7194async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7195 init_test(cx, |_| {});
7196
7197 let (text, insertion_ranges) = marked_text_ranges(
7198 indoc! {"
7199 ˇ
7200 "},
7201 false,
7202 );
7203
7204 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7205 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7206
7207 _ = editor.update_in(cx, |editor, window, cx| {
7208 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7209
7210 editor
7211 .insert_snippet(&insertion_ranges, snippet, window, cx)
7212 .unwrap();
7213
7214 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7215 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7216 assert_eq!(editor.text(cx), expected_text);
7217 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7218 }
7219
7220 assert(
7221 editor,
7222 cx,
7223 indoc! {"
7224 type «» =•
7225 "},
7226 );
7227
7228 assert!(editor.context_menu_visible(), "There should be a matches");
7229 });
7230}
7231
7232#[gpui::test]
7233async fn test_snippets(cx: &mut TestAppContext) {
7234 init_test(cx, |_| {});
7235
7236 let (text, insertion_ranges) = marked_text_ranges(
7237 indoc! {"
7238 a.ˇ b
7239 a.ˇ b
7240 a.ˇ b
7241 "},
7242 false,
7243 );
7244
7245 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7246 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7247
7248 editor.update_in(cx, |editor, window, cx| {
7249 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7250
7251 editor
7252 .insert_snippet(&insertion_ranges, snippet, window, cx)
7253 .unwrap();
7254
7255 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7256 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7257 assert_eq!(editor.text(cx), expected_text);
7258 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7259 }
7260
7261 assert(
7262 editor,
7263 cx,
7264 indoc! {"
7265 a.f(«one», two, «three») b
7266 a.f(«one», two, «three») b
7267 a.f(«one», two, «three») b
7268 "},
7269 );
7270
7271 // Can't move earlier than the first tab stop
7272 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7273 assert(
7274 editor,
7275 cx,
7276 indoc! {"
7277 a.f(«one», two, «three») b
7278 a.f(«one», two, «three») b
7279 a.f(«one», two, «three») b
7280 "},
7281 );
7282
7283 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7284 assert(
7285 editor,
7286 cx,
7287 indoc! {"
7288 a.f(one, «two», three) b
7289 a.f(one, «two», three) b
7290 a.f(one, «two», three) b
7291 "},
7292 );
7293
7294 editor.move_to_prev_snippet_tabstop(window, cx);
7295 assert(
7296 editor,
7297 cx,
7298 indoc! {"
7299 a.f(«one», two, «three») b
7300 a.f(«one», two, «three») b
7301 a.f(«one», two, «three») b
7302 "},
7303 );
7304
7305 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7306 assert(
7307 editor,
7308 cx,
7309 indoc! {"
7310 a.f(one, «two», three) b
7311 a.f(one, «two», three) b
7312 a.f(one, «two», three) b
7313 "},
7314 );
7315 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7316 assert(
7317 editor,
7318 cx,
7319 indoc! {"
7320 a.f(one, two, three)ˇ b
7321 a.f(one, two, three)ˇ b
7322 a.f(one, two, three)ˇ b
7323 "},
7324 );
7325
7326 // As soon as the last tab stop is reached, snippet state is gone
7327 editor.move_to_prev_snippet_tabstop(window, cx);
7328 assert(
7329 editor,
7330 cx,
7331 indoc! {"
7332 a.f(one, two, three)ˇ b
7333 a.f(one, two, three)ˇ b
7334 a.f(one, two, three)ˇ b
7335 "},
7336 );
7337 });
7338}
7339
7340#[gpui::test]
7341async fn test_document_format_during_save(cx: &mut TestAppContext) {
7342 init_test(cx, |_| {});
7343
7344 let fs = FakeFs::new(cx.executor());
7345 fs.insert_file(path!("/file.rs"), Default::default()).await;
7346
7347 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7348
7349 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7350 language_registry.add(rust_lang());
7351 let mut fake_servers = language_registry.register_fake_lsp(
7352 "Rust",
7353 FakeLspAdapter {
7354 capabilities: lsp::ServerCapabilities {
7355 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7356 ..Default::default()
7357 },
7358 ..Default::default()
7359 },
7360 );
7361
7362 let buffer = project
7363 .update(cx, |project, cx| {
7364 project.open_local_buffer(path!("/file.rs"), cx)
7365 })
7366 .await
7367 .unwrap();
7368
7369 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7370 let (editor, cx) = cx.add_window_view(|window, cx| {
7371 build_editor_with_project(project.clone(), buffer, window, cx)
7372 });
7373 editor.update_in(cx, |editor, window, cx| {
7374 editor.set_text("one\ntwo\nthree\n", window, cx)
7375 });
7376 assert!(cx.read(|cx| editor.is_dirty(cx)));
7377
7378 cx.executor().start_waiting();
7379 let fake_server = fake_servers.next().await.unwrap();
7380
7381 let save = editor
7382 .update_in(cx, |editor, window, cx| {
7383 editor.save(true, project.clone(), window, cx)
7384 })
7385 .unwrap();
7386 fake_server
7387 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7388 assert_eq!(
7389 params.text_document.uri,
7390 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7391 );
7392 assert_eq!(params.options.tab_size, 4);
7393 Ok(Some(vec![lsp::TextEdit::new(
7394 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7395 ", ".to_string(),
7396 )]))
7397 })
7398 .next()
7399 .await;
7400 cx.executor().start_waiting();
7401 save.await;
7402
7403 assert_eq!(
7404 editor.update(cx, |editor, cx| editor.text(cx)),
7405 "one, two\nthree\n"
7406 );
7407 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7408
7409 editor.update_in(cx, |editor, window, cx| {
7410 editor.set_text("one\ntwo\nthree\n", window, cx)
7411 });
7412 assert!(cx.read(|cx| editor.is_dirty(cx)));
7413
7414 // Ensure we can still save even if formatting hangs.
7415 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7416 assert_eq!(
7417 params.text_document.uri,
7418 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7419 );
7420 futures::future::pending::<()>().await;
7421 unreachable!()
7422 });
7423 let save = editor
7424 .update_in(cx, |editor, window, cx| {
7425 editor.save(true, project.clone(), window, cx)
7426 })
7427 .unwrap();
7428 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7429 cx.executor().start_waiting();
7430 save.await;
7431 assert_eq!(
7432 editor.update(cx, |editor, cx| editor.text(cx)),
7433 "one\ntwo\nthree\n"
7434 );
7435 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7436
7437 // For non-dirty buffer, no formatting request should be sent
7438 let save = editor
7439 .update_in(cx, |editor, window, cx| {
7440 editor.save(true, project.clone(), window, cx)
7441 })
7442 .unwrap();
7443 let _pending_format_request = fake_server
7444 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7445 panic!("Should not be invoked on non-dirty buffer");
7446 })
7447 .next();
7448 cx.executor().start_waiting();
7449 save.await;
7450
7451 // Set rust language override and assert overridden tabsize is sent to language server
7452 update_test_language_settings(cx, |settings| {
7453 settings.languages.insert(
7454 "Rust".into(),
7455 LanguageSettingsContent {
7456 tab_size: NonZeroU32::new(8),
7457 ..Default::default()
7458 },
7459 );
7460 });
7461
7462 editor.update_in(cx, |editor, window, cx| {
7463 editor.set_text("somehting_new\n", window, cx)
7464 });
7465 assert!(cx.read(|cx| editor.is_dirty(cx)));
7466 let save = editor
7467 .update_in(cx, |editor, window, cx| {
7468 editor.save(true, project.clone(), window, cx)
7469 })
7470 .unwrap();
7471 fake_server
7472 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7473 assert_eq!(
7474 params.text_document.uri,
7475 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7476 );
7477 assert_eq!(params.options.tab_size, 8);
7478 Ok(Some(vec![]))
7479 })
7480 .next()
7481 .await;
7482 cx.executor().start_waiting();
7483 save.await;
7484}
7485
7486#[gpui::test]
7487async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7488 init_test(cx, |_| {});
7489
7490 let cols = 4;
7491 let rows = 10;
7492 let sample_text_1 = sample_text(rows, cols, 'a');
7493 assert_eq!(
7494 sample_text_1,
7495 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7496 );
7497 let sample_text_2 = sample_text(rows, cols, 'l');
7498 assert_eq!(
7499 sample_text_2,
7500 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7501 );
7502 let sample_text_3 = sample_text(rows, cols, 'v');
7503 assert_eq!(
7504 sample_text_3,
7505 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7506 );
7507
7508 let fs = FakeFs::new(cx.executor());
7509 fs.insert_tree(
7510 path!("/a"),
7511 json!({
7512 "main.rs": sample_text_1,
7513 "other.rs": sample_text_2,
7514 "lib.rs": sample_text_3,
7515 }),
7516 )
7517 .await;
7518
7519 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7520 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7521 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7522
7523 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7524 language_registry.add(rust_lang());
7525 let mut fake_servers = language_registry.register_fake_lsp(
7526 "Rust",
7527 FakeLspAdapter {
7528 capabilities: lsp::ServerCapabilities {
7529 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7530 ..Default::default()
7531 },
7532 ..Default::default()
7533 },
7534 );
7535
7536 let worktree = project.update(cx, |project, cx| {
7537 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7538 assert_eq!(worktrees.len(), 1);
7539 worktrees.pop().unwrap()
7540 });
7541 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7542
7543 let buffer_1 = project
7544 .update(cx, |project, cx| {
7545 project.open_buffer((worktree_id, "main.rs"), cx)
7546 })
7547 .await
7548 .unwrap();
7549 let buffer_2 = project
7550 .update(cx, |project, cx| {
7551 project.open_buffer((worktree_id, "other.rs"), cx)
7552 })
7553 .await
7554 .unwrap();
7555 let buffer_3 = project
7556 .update(cx, |project, cx| {
7557 project.open_buffer((worktree_id, "lib.rs"), cx)
7558 })
7559 .await
7560 .unwrap();
7561
7562 let multi_buffer = cx.new(|cx| {
7563 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7564 multi_buffer.push_excerpts(
7565 buffer_1.clone(),
7566 [
7567 ExcerptRange {
7568 context: Point::new(0, 0)..Point::new(3, 0),
7569 primary: None,
7570 },
7571 ExcerptRange {
7572 context: Point::new(5, 0)..Point::new(7, 0),
7573 primary: None,
7574 },
7575 ExcerptRange {
7576 context: Point::new(9, 0)..Point::new(10, 4),
7577 primary: None,
7578 },
7579 ],
7580 cx,
7581 );
7582 multi_buffer.push_excerpts(
7583 buffer_2.clone(),
7584 [
7585 ExcerptRange {
7586 context: Point::new(0, 0)..Point::new(3, 0),
7587 primary: None,
7588 },
7589 ExcerptRange {
7590 context: Point::new(5, 0)..Point::new(7, 0),
7591 primary: None,
7592 },
7593 ExcerptRange {
7594 context: Point::new(9, 0)..Point::new(10, 4),
7595 primary: None,
7596 },
7597 ],
7598 cx,
7599 );
7600 multi_buffer.push_excerpts(
7601 buffer_3.clone(),
7602 [
7603 ExcerptRange {
7604 context: Point::new(0, 0)..Point::new(3, 0),
7605 primary: None,
7606 },
7607 ExcerptRange {
7608 context: Point::new(5, 0)..Point::new(7, 0),
7609 primary: None,
7610 },
7611 ExcerptRange {
7612 context: Point::new(9, 0)..Point::new(10, 4),
7613 primary: None,
7614 },
7615 ],
7616 cx,
7617 );
7618 multi_buffer
7619 });
7620 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7621 Editor::new(
7622 EditorMode::Full,
7623 multi_buffer,
7624 Some(project.clone()),
7625 true,
7626 window,
7627 cx,
7628 )
7629 });
7630
7631 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7632 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7633 s.select_ranges(Some(1..2))
7634 });
7635 editor.insert("|one|two|three|", window, cx);
7636 });
7637 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7638 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7639 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7640 s.select_ranges(Some(60..70))
7641 });
7642 editor.insert("|four|five|six|", window, cx);
7643 });
7644 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7645
7646 // First two buffers should be edited, but not the third one.
7647 assert_eq!(
7648 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7649 "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}",
7650 );
7651 buffer_1.update(cx, |buffer, _| {
7652 assert!(buffer.is_dirty());
7653 assert_eq!(
7654 buffer.text(),
7655 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7656 )
7657 });
7658 buffer_2.update(cx, |buffer, _| {
7659 assert!(buffer.is_dirty());
7660 assert_eq!(
7661 buffer.text(),
7662 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7663 )
7664 });
7665 buffer_3.update(cx, |buffer, _| {
7666 assert!(!buffer.is_dirty());
7667 assert_eq!(buffer.text(), sample_text_3,)
7668 });
7669 cx.executor().run_until_parked();
7670
7671 cx.executor().start_waiting();
7672 let save = multi_buffer_editor
7673 .update_in(cx, |editor, window, cx| {
7674 editor.save(true, project.clone(), window, cx)
7675 })
7676 .unwrap();
7677
7678 let fake_server = fake_servers.next().await.unwrap();
7679 fake_server
7680 .server
7681 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7682 Ok(Some(vec![lsp::TextEdit::new(
7683 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7684 format!("[{} formatted]", params.text_document.uri),
7685 )]))
7686 })
7687 .detach();
7688 save.await;
7689
7690 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7691 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7692 assert_eq!(
7693 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7694 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}"),
7695 );
7696 buffer_1.update(cx, |buffer, _| {
7697 assert!(!buffer.is_dirty());
7698 assert_eq!(
7699 buffer.text(),
7700 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7701 )
7702 });
7703 buffer_2.update(cx, |buffer, _| {
7704 assert!(!buffer.is_dirty());
7705 assert_eq!(
7706 buffer.text(),
7707 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7708 )
7709 });
7710 buffer_3.update(cx, |buffer, _| {
7711 assert!(!buffer.is_dirty());
7712 assert_eq!(buffer.text(), sample_text_3,)
7713 });
7714}
7715
7716#[gpui::test]
7717async fn test_range_format_during_save(cx: &mut TestAppContext) {
7718 init_test(cx, |_| {});
7719
7720 let fs = FakeFs::new(cx.executor());
7721 fs.insert_file(path!("/file.rs"), Default::default()).await;
7722
7723 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7724
7725 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7726 language_registry.add(rust_lang());
7727 let mut fake_servers = language_registry.register_fake_lsp(
7728 "Rust",
7729 FakeLspAdapter {
7730 capabilities: lsp::ServerCapabilities {
7731 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7732 ..Default::default()
7733 },
7734 ..Default::default()
7735 },
7736 );
7737
7738 let buffer = project
7739 .update(cx, |project, cx| {
7740 project.open_local_buffer(path!("/file.rs"), cx)
7741 })
7742 .await
7743 .unwrap();
7744
7745 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7746 let (editor, cx) = cx.add_window_view(|window, cx| {
7747 build_editor_with_project(project.clone(), buffer, window, cx)
7748 });
7749 editor.update_in(cx, |editor, window, cx| {
7750 editor.set_text("one\ntwo\nthree\n", window, cx)
7751 });
7752 assert!(cx.read(|cx| editor.is_dirty(cx)));
7753
7754 cx.executor().start_waiting();
7755 let fake_server = fake_servers.next().await.unwrap();
7756
7757 let save = editor
7758 .update_in(cx, |editor, window, cx| {
7759 editor.save(true, project.clone(), window, cx)
7760 })
7761 .unwrap();
7762 fake_server
7763 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7764 assert_eq!(
7765 params.text_document.uri,
7766 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7767 );
7768 assert_eq!(params.options.tab_size, 4);
7769 Ok(Some(vec![lsp::TextEdit::new(
7770 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7771 ", ".to_string(),
7772 )]))
7773 })
7774 .next()
7775 .await;
7776 cx.executor().start_waiting();
7777 save.await;
7778 assert_eq!(
7779 editor.update(cx, |editor, cx| editor.text(cx)),
7780 "one, two\nthree\n"
7781 );
7782 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7783
7784 editor.update_in(cx, |editor, window, cx| {
7785 editor.set_text("one\ntwo\nthree\n", window, cx)
7786 });
7787 assert!(cx.read(|cx| editor.is_dirty(cx)));
7788
7789 // Ensure we can still save even if formatting hangs.
7790 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7791 move |params, _| async move {
7792 assert_eq!(
7793 params.text_document.uri,
7794 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7795 );
7796 futures::future::pending::<()>().await;
7797 unreachable!()
7798 },
7799 );
7800 let save = editor
7801 .update_in(cx, |editor, window, cx| {
7802 editor.save(true, project.clone(), window, cx)
7803 })
7804 .unwrap();
7805 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7806 cx.executor().start_waiting();
7807 save.await;
7808 assert_eq!(
7809 editor.update(cx, |editor, cx| editor.text(cx)),
7810 "one\ntwo\nthree\n"
7811 );
7812 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7813
7814 // For non-dirty buffer, no formatting request should be sent
7815 let save = editor
7816 .update_in(cx, |editor, window, cx| {
7817 editor.save(true, project.clone(), window, cx)
7818 })
7819 .unwrap();
7820 let _pending_format_request = fake_server
7821 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7822 panic!("Should not be invoked on non-dirty buffer");
7823 })
7824 .next();
7825 cx.executor().start_waiting();
7826 save.await;
7827
7828 // Set Rust language override and assert overridden tabsize is sent to language server
7829 update_test_language_settings(cx, |settings| {
7830 settings.languages.insert(
7831 "Rust".into(),
7832 LanguageSettingsContent {
7833 tab_size: NonZeroU32::new(8),
7834 ..Default::default()
7835 },
7836 );
7837 });
7838
7839 editor.update_in(cx, |editor, window, cx| {
7840 editor.set_text("somehting_new\n", window, cx)
7841 });
7842 assert!(cx.read(|cx| editor.is_dirty(cx)));
7843 let save = editor
7844 .update_in(cx, |editor, window, cx| {
7845 editor.save(true, project.clone(), window, cx)
7846 })
7847 .unwrap();
7848 fake_server
7849 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7850 assert_eq!(
7851 params.text_document.uri,
7852 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7853 );
7854 assert_eq!(params.options.tab_size, 8);
7855 Ok(Some(vec![]))
7856 })
7857 .next()
7858 .await;
7859 cx.executor().start_waiting();
7860 save.await;
7861}
7862
7863#[gpui::test]
7864async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7865 init_test(cx, |settings| {
7866 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7867 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7868 ))
7869 });
7870
7871 let fs = FakeFs::new(cx.executor());
7872 fs.insert_file(path!("/file.rs"), Default::default()).await;
7873
7874 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7875
7876 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7877 language_registry.add(Arc::new(Language::new(
7878 LanguageConfig {
7879 name: "Rust".into(),
7880 matcher: LanguageMatcher {
7881 path_suffixes: vec!["rs".to_string()],
7882 ..Default::default()
7883 },
7884 ..LanguageConfig::default()
7885 },
7886 Some(tree_sitter_rust::LANGUAGE.into()),
7887 )));
7888 update_test_language_settings(cx, |settings| {
7889 // Enable Prettier formatting for the same buffer, and ensure
7890 // LSP is called instead of Prettier.
7891 settings.defaults.prettier = Some(PrettierSettings {
7892 allowed: true,
7893 ..PrettierSettings::default()
7894 });
7895 });
7896 let mut fake_servers = language_registry.register_fake_lsp(
7897 "Rust",
7898 FakeLspAdapter {
7899 capabilities: lsp::ServerCapabilities {
7900 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7901 ..Default::default()
7902 },
7903 ..Default::default()
7904 },
7905 );
7906
7907 let buffer = project
7908 .update(cx, |project, cx| {
7909 project.open_local_buffer(path!("/file.rs"), cx)
7910 })
7911 .await
7912 .unwrap();
7913
7914 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7915 let (editor, cx) = cx.add_window_view(|window, cx| {
7916 build_editor_with_project(project.clone(), buffer, window, cx)
7917 });
7918 editor.update_in(cx, |editor, window, cx| {
7919 editor.set_text("one\ntwo\nthree\n", window, cx)
7920 });
7921
7922 cx.executor().start_waiting();
7923 let fake_server = fake_servers.next().await.unwrap();
7924
7925 let format = editor
7926 .update_in(cx, |editor, window, cx| {
7927 editor.perform_format(
7928 project.clone(),
7929 FormatTrigger::Manual,
7930 FormatTarget::Buffers,
7931 window,
7932 cx,
7933 )
7934 })
7935 .unwrap();
7936 fake_server
7937 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7938 assert_eq!(
7939 params.text_document.uri,
7940 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7941 );
7942 assert_eq!(params.options.tab_size, 4);
7943 Ok(Some(vec![lsp::TextEdit::new(
7944 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7945 ", ".to_string(),
7946 )]))
7947 })
7948 .next()
7949 .await;
7950 cx.executor().start_waiting();
7951 format.await;
7952 assert_eq!(
7953 editor.update(cx, |editor, cx| editor.text(cx)),
7954 "one, two\nthree\n"
7955 );
7956
7957 editor.update_in(cx, |editor, window, cx| {
7958 editor.set_text("one\ntwo\nthree\n", window, cx)
7959 });
7960 // Ensure we don't lock if formatting hangs.
7961 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7962 assert_eq!(
7963 params.text_document.uri,
7964 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7965 );
7966 futures::future::pending::<()>().await;
7967 unreachable!()
7968 });
7969 let format = editor
7970 .update_in(cx, |editor, window, cx| {
7971 editor.perform_format(
7972 project,
7973 FormatTrigger::Manual,
7974 FormatTarget::Buffers,
7975 window,
7976 cx,
7977 )
7978 })
7979 .unwrap();
7980 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7981 cx.executor().start_waiting();
7982 format.await;
7983 assert_eq!(
7984 editor.update(cx, |editor, cx| editor.text(cx)),
7985 "one\ntwo\nthree\n"
7986 );
7987}
7988
7989#[gpui::test]
7990async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
7991 init_test(cx, |settings| {
7992 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7993 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7994 ))
7995 });
7996
7997 let fs = FakeFs::new(cx.executor());
7998 fs.insert_file(path!("/file.ts"), Default::default()).await;
7999
8000 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8001
8002 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8003 language_registry.add(Arc::new(Language::new(
8004 LanguageConfig {
8005 name: "TypeScript".into(),
8006 matcher: LanguageMatcher {
8007 path_suffixes: vec!["ts".to_string()],
8008 ..Default::default()
8009 },
8010 ..LanguageConfig::default()
8011 },
8012 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8013 )));
8014 update_test_language_settings(cx, |settings| {
8015 settings.defaults.prettier = Some(PrettierSettings {
8016 allowed: true,
8017 ..PrettierSettings::default()
8018 });
8019 });
8020 let mut fake_servers = language_registry.register_fake_lsp(
8021 "TypeScript",
8022 FakeLspAdapter {
8023 capabilities: lsp::ServerCapabilities {
8024 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8025 ..Default::default()
8026 },
8027 ..Default::default()
8028 },
8029 );
8030
8031 let buffer = project
8032 .update(cx, |project, cx| {
8033 project.open_local_buffer(path!("/file.ts"), cx)
8034 })
8035 .await
8036 .unwrap();
8037
8038 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8039 let (editor, cx) = cx.add_window_view(|window, cx| {
8040 build_editor_with_project(project.clone(), buffer, window, cx)
8041 });
8042 editor.update_in(cx, |editor, window, cx| {
8043 editor.set_text(
8044 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8045 window,
8046 cx,
8047 )
8048 });
8049
8050 cx.executor().start_waiting();
8051 let fake_server = fake_servers.next().await.unwrap();
8052
8053 let format = editor
8054 .update_in(cx, |editor, window, cx| {
8055 editor.perform_code_action_kind(
8056 project.clone(),
8057 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8058 window,
8059 cx,
8060 )
8061 })
8062 .unwrap();
8063 fake_server
8064 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8065 assert_eq!(
8066 params.text_document.uri,
8067 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8068 );
8069 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8070 lsp::CodeAction {
8071 title: "Organize Imports".to_string(),
8072 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8073 edit: Some(lsp::WorkspaceEdit {
8074 changes: Some(
8075 [(
8076 params.text_document.uri.clone(),
8077 vec![lsp::TextEdit::new(
8078 lsp::Range::new(
8079 lsp::Position::new(1, 0),
8080 lsp::Position::new(2, 0),
8081 ),
8082 "".to_string(),
8083 )],
8084 )]
8085 .into_iter()
8086 .collect(),
8087 ),
8088 ..Default::default()
8089 }),
8090 ..Default::default()
8091 },
8092 )]))
8093 })
8094 .next()
8095 .await;
8096 cx.executor().start_waiting();
8097 format.await;
8098 assert_eq!(
8099 editor.update(cx, |editor, cx| editor.text(cx)),
8100 "import { a } from 'module';\n\nconst x = a;\n"
8101 );
8102
8103 editor.update_in(cx, |editor, window, cx| {
8104 editor.set_text(
8105 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8106 window,
8107 cx,
8108 )
8109 });
8110 // Ensure we don't lock if code action hangs.
8111 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8112 move |params, _| async move {
8113 assert_eq!(
8114 params.text_document.uri,
8115 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8116 );
8117 futures::future::pending::<()>().await;
8118 unreachable!()
8119 },
8120 );
8121 let format = editor
8122 .update_in(cx, |editor, window, cx| {
8123 editor.perform_code_action_kind(
8124 project,
8125 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8126 window,
8127 cx,
8128 )
8129 })
8130 .unwrap();
8131 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8132 cx.executor().start_waiting();
8133 format.await;
8134 assert_eq!(
8135 editor.update(cx, |editor, cx| editor.text(cx)),
8136 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8137 );
8138}
8139
8140#[gpui::test]
8141async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8142 init_test(cx, |_| {});
8143
8144 let mut cx = EditorLspTestContext::new_rust(
8145 lsp::ServerCapabilities {
8146 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8147 ..Default::default()
8148 },
8149 cx,
8150 )
8151 .await;
8152
8153 cx.set_state(indoc! {"
8154 one.twoˇ
8155 "});
8156
8157 // The format request takes a long time. When it completes, it inserts
8158 // a newline and an indent before the `.`
8159 cx.lsp
8160 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8161 let executor = cx.background_executor().clone();
8162 async move {
8163 executor.timer(Duration::from_millis(100)).await;
8164 Ok(Some(vec![lsp::TextEdit {
8165 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8166 new_text: "\n ".into(),
8167 }]))
8168 }
8169 });
8170
8171 // Submit a format request.
8172 let format_1 = cx
8173 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8174 .unwrap();
8175 cx.executor().run_until_parked();
8176
8177 // Submit a second format request.
8178 let format_2 = cx
8179 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8180 .unwrap();
8181 cx.executor().run_until_parked();
8182
8183 // Wait for both format requests to complete
8184 cx.executor().advance_clock(Duration::from_millis(200));
8185 cx.executor().start_waiting();
8186 format_1.await.unwrap();
8187 cx.executor().start_waiting();
8188 format_2.await.unwrap();
8189
8190 // The formatting edits only happens once.
8191 cx.assert_editor_state(indoc! {"
8192 one
8193 .twoˇ
8194 "});
8195}
8196
8197#[gpui::test]
8198async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8199 init_test(cx, |settings| {
8200 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8201 });
8202
8203 let mut cx = EditorLspTestContext::new_rust(
8204 lsp::ServerCapabilities {
8205 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8206 ..Default::default()
8207 },
8208 cx,
8209 )
8210 .await;
8211
8212 // Set up a buffer white some trailing whitespace and no trailing newline.
8213 cx.set_state(
8214 &[
8215 "one ", //
8216 "twoˇ", //
8217 "three ", //
8218 "four", //
8219 ]
8220 .join("\n"),
8221 );
8222
8223 // Submit a format request.
8224 let format = cx
8225 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8226 .unwrap();
8227
8228 // Record which buffer changes have been sent to the language server
8229 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8230 cx.lsp
8231 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8232 let buffer_changes = buffer_changes.clone();
8233 move |params, _| {
8234 buffer_changes.lock().extend(
8235 params
8236 .content_changes
8237 .into_iter()
8238 .map(|e| (e.range.unwrap(), e.text)),
8239 );
8240 }
8241 });
8242
8243 // Handle formatting requests to the language server.
8244 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8245 let buffer_changes = buffer_changes.clone();
8246 move |_, _| {
8247 // When formatting is requested, trailing whitespace has already been stripped,
8248 // and the trailing newline has already been added.
8249 assert_eq!(
8250 &buffer_changes.lock()[1..],
8251 &[
8252 (
8253 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8254 "".into()
8255 ),
8256 (
8257 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8258 "".into()
8259 ),
8260 (
8261 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8262 "\n".into()
8263 ),
8264 ]
8265 );
8266
8267 // Insert blank lines between each line of the buffer.
8268 async move {
8269 Ok(Some(vec![
8270 lsp::TextEdit {
8271 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8272 new_text: "\n".into(),
8273 },
8274 lsp::TextEdit {
8275 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8276 new_text: "\n".into(),
8277 },
8278 ]))
8279 }
8280 }
8281 });
8282
8283 // After formatting the buffer, the trailing whitespace is stripped,
8284 // a newline is appended, and the edits provided by the language server
8285 // have been applied.
8286 format.await.unwrap();
8287 cx.assert_editor_state(
8288 &[
8289 "one", //
8290 "", //
8291 "twoˇ", //
8292 "", //
8293 "three", //
8294 "four", //
8295 "", //
8296 ]
8297 .join("\n"),
8298 );
8299
8300 // Undoing the formatting undoes the trailing whitespace removal, the
8301 // trailing newline, and the LSP edits.
8302 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8303 cx.assert_editor_state(
8304 &[
8305 "one ", //
8306 "twoˇ", //
8307 "three ", //
8308 "four", //
8309 ]
8310 .join("\n"),
8311 );
8312}
8313
8314#[gpui::test]
8315async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8316 cx: &mut TestAppContext,
8317) {
8318 init_test(cx, |_| {});
8319
8320 cx.update(|cx| {
8321 cx.update_global::<SettingsStore, _>(|settings, cx| {
8322 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8323 settings.auto_signature_help = Some(true);
8324 });
8325 });
8326 });
8327
8328 let mut cx = EditorLspTestContext::new_rust(
8329 lsp::ServerCapabilities {
8330 signature_help_provider: Some(lsp::SignatureHelpOptions {
8331 ..Default::default()
8332 }),
8333 ..Default::default()
8334 },
8335 cx,
8336 )
8337 .await;
8338
8339 let language = Language::new(
8340 LanguageConfig {
8341 name: "Rust".into(),
8342 brackets: BracketPairConfig {
8343 pairs: vec![
8344 BracketPair {
8345 start: "{".to_string(),
8346 end: "}".to_string(),
8347 close: true,
8348 surround: true,
8349 newline: true,
8350 },
8351 BracketPair {
8352 start: "(".to_string(),
8353 end: ")".to_string(),
8354 close: true,
8355 surround: true,
8356 newline: true,
8357 },
8358 BracketPair {
8359 start: "/*".to_string(),
8360 end: " */".to_string(),
8361 close: true,
8362 surround: true,
8363 newline: true,
8364 },
8365 BracketPair {
8366 start: "[".to_string(),
8367 end: "]".to_string(),
8368 close: false,
8369 surround: false,
8370 newline: true,
8371 },
8372 BracketPair {
8373 start: "\"".to_string(),
8374 end: "\"".to_string(),
8375 close: true,
8376 surround: true,
8377 newline: false,
8378 },
8379 BracketPair {
8380 start: "<".to_string(),
8381 end: ">".to_string(),
8382 close: false,
8383 surround: true,
8384 newline: true,
8385 },
8386 ],
8387 ..Default::default()
8388 },
8389 autoclose_before: "})]".to_string(),
8390 ..Default::default()
8391 },
8392 Some(tree_sitter_rust::LANGUAGE.into()),
8393 );
8394 let language = Arc::new(language);
8395
8396 cx.language_registry().add(language.clone());
8397 cx.update_buffer(|buffer, cx| {
8398 buffer.set_language(Some(language), cx);
8399 });
8400
8401 cx.set_state(
8402 &r#"
8403 fn main() {
8404 sampleˇ
8405 }
8406 "#
8407 .unindent(),
8408 );
8409
8410 cx.update_editor(|editor, window, cx| {
8411 editor.handle_input("(", window, cx);
8412 });
8413 cx.assert_editor_state(
8414 &"
8415 fn main() {
8416 sample(ˇ)
8417 }
8418 "
8419 .unindent(),
8420 );
8421
8422 let mocked_response = lsp::SignatureHelp {
8423 signatures: vec![lsp::SignatureInformation {
8424 label: "fn sample(param1: u8, param2: u8)".to_string(),
8425 documentation: None,
8426 parameters: Some(vec![
8427 lsp::ParameterInformation {
8428 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8429 documentation: None,
8430 },
8431 lsp::ParameterInformation {
8432 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8433 documentation: None,
8434 },
8435 ]),
8436 active_parameter: None,
8437 }],
8438 active_signature: Some(0),
8439 active_parameter: Some(0),
8440 };
8441 handle_signature_help_request(&mut cx, mocked_response).await;
8442
8443 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8444 .await;
8445
8446 cx.editor(|editor, _, _| {
8447 let signature_help_state = editor.signature_help_state.popover().cloned();
8448 assert_eq!(
8449 signature_help_state.unwrap().label,
8450 "param1: u8, param2: u8"
8451 );
8452 });
8453}
8454
8455#[gpui::test]
8456async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8457 init_test(cx, |_| {});
8458
8459 cx.update(|cx| {
8460 cx.update_global::<SettingsStore, _>(|settings, cx| {
8461 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8462 settings.auto_signature_help = Some(false);
8463 settings.show_signature_help_after_edits = Some(false);
8464 });
8465 });
8466 });
8467
8468 let mut cx = EditorLspTestContext::new_rust(
8469 lsp::ServerCapabilities {
8470 signature_help_provider: Some(lsp::SignatureHelpOptions {
8471 ..Default::default()
8472 }),
8473 ..Default::default()
8474 },
8475 cx,
8476 )
8477 .await;
8478
8479 let language = Language::new(
8480 LanguageConfig {
8481 name: "Rust".into(),
8482 brackets: BracketPairConfig {
8483 pairs: vec![
8484 BracketPair {
8485 start: "{".to_string(),
8486 end: "}".to_string(),
8487 close: true,
8488 surround: true,
8489 newline: true,
8490 },
8491 BracketPair {
8492 start: "(".to_string(),
8493 end: ")".to_string(),
8494 close: true,
8495 surround: true,
8496 newline: true,
8497 },
8498 BracketPair {
8499 start: "/*".to_string(),
8500 end: " */".to_string(),
8501 close: true,
8502 surround: true,
8503 newline: true,
8504 },
8505 BracketPair {
8506 start: "[".to_string(),
8507 end: "]".to_string(),
8508 close: false,
8509 surround: false,
8510 newline: true,
8511 },
8512 BracketPair {
8513 start: "\"".to_string(),
8514 end: "\"".to_string(),
8515 close: true,
8516 surround: true,
8517 newline: false,
8518 },
8519 BracketPair {
8520 start: "<".to_string(),
8521 end: ">".to_string(),
8522 close: false,
8523 surround: true,
8524 newline: true,
8525 },
8526 ],
8527 ..Default::default()
8528 },
8529 autoclose_before: "})]".to_string(),
8530 ..Default::default()
8531 },
8532 Some(tree_sitter_rust::LANGUAGE.into()),
8533 );
8534 let language = Arc::new(language);
8535
8536 cx.language_registry().add(language.clone());
8537 cx.update_buffer(|buffer, cx| {
8538 buffer.set_language(Some(language), cx);
8539 });
8540
8541 // Ensure that signature_help is not called when no signature help is enabled.
8542 cx.set_state(
8543 &r#"
8544 fn main() {
8545 sampleˇ
8546 }
8547 "#
8548 .unindent(),
8549 );
8550 cx.update_editor(|editor, window, cx| {
8551 editor.handle_input("(", window, cx);
8552 });
8553 cx.assert_editor_state(
8554 &"
8555 fn main() {
8556 sample(ˇ)
8557 }
8558 "
8559 .unindent(),
8560 );
8561 cx.editor(|editor, _, _| {
8562 assert!(editor.signature_help_state.task().is_none());
8563 });
8564
8565 let mocked_response = lsp::SignatureHelp {
8566 signatures: vec![lsp::SignatureInformation {
8567 label: "fn sample(param1: u8, param2: u8)".to_string(),
8568 documentation: None,
8569 parameters: Some(vec![
8570 lsp::ParameterInformation {
8571 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8572 documentation: None,
8573 },
8574 lsp::ParameterInformation {
8575 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8576 documentation: None,
8577 },
8578 ]),
8579 active_parameter: None,
8580 }],
8581 active_signature: Some(0),
8582 active_parameter: Some(0),
8583 };
8584
8585 // Ensure that signature_help is called when enabled afte edits
8586 cx.update(|_, cx| {
8587 cx.update_global::<SettingsStore, _>(|settings, cx| {
8588 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8589 settings.auto_signature_help = Some(false);
8590 settings.show_signature_help_after_edits = Some(true);
8591 });
8592 });
8593 });
8594 cx.set_state(
8595 &r#"
8596 fn main() {
8597 sampleˇ
8598 }
8599 "#
8600 .unindent(),
8601 );
8602 cx.update_editor(|editor, window, cx| {
8603 editor.handle_input("(", window, cx);
8604 });
8605 cx.assert_editor_state(
8606 &"
8607 fn main() {
8608 sample(ˇ)
8609 }
8610 "
8611 .unindent(),
8612 );
8613 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8614 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8615 .await;
8616 cx.update_editor(|editor, _, _| {
8617 let signature_help_state = editor.signature_help_state.popover().cloned();
8618 assert!(signature_help_state.is_some());
8619 assert_eq!(
8620 signature_help_state.unwrap().label,
8621 "param1: u8, param2: u8"
8622 );
8623 editor.signature_help_state = SignatureHelpState::default();
8624 });
8625
8626 // Ensure that signature_help is called when auto signature help override is enabled
8627 cx.update(|_, cx| {
8628 cx.update_global::<SettingsStore, _>(|settings, cx| {
8629 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8630 settings.auto_signature_help = Some(true);
8631 settings.show_signature_help_after_edits = Some(false);
8632 });
8633 });
8634 });
8635 cx.set_state(
8636 &r#"
8637 fn main() {
8638 sampleˇ
8639 }
8640 "#
8641 .unindent(),
8642 );
8643 cx.update_editor(|editor, window, cx| {
8644 editor.handle_input("(", window, cx);
8645 });
8646 cx.assert_editor_state(
8647 &"
8648 fn main() {
8649 sample(ˇ)
8650 }
8651 "
8652 .unindent(),
8653 );
8654 handle_signature_help_request(&mut cx, mocked_response).await;
8655 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8656 .await;
8657 cx.editor(|editor, _, _| {
8658 let signature_help_state = editor.signature_help_state.popover().cloned();
8659 assert!(signature_help_state.is_some());
8660 assert_eq!(
8661 signature_help_state.unwrap().label,
8662 "param1: u8, param2: u8"
8663 );
8664 });
8665}
8666
8667#[gpui::test]
8668async fn test_signature_help(cx: &mut TestAppContext) {
8669 init_test(cx, |_| {});
8670 cx.update(|cx| {
8671 cx.update_global::<SettingsStore, _>(|settings, cx| {
8672 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8673 settings.auto_signature_help = Some(true);
8674 });
8675 });
8676 });
8677
8678 let mut cx = EditorLspTestContext::new_rust(
8679 lsp::ServerCapabilities {
8680 signature_help_provider: Some(lsp::SignatureHelpOptions {
8681 ..Default::default()
8682 }),
8683 ..Default::default()
8684 },
8685 cx,
8686 )
8687 .await;
8688
8689 // A test that directly calls `show_signature_help`
8690 cx.update_editor(|editor, window, cx| {
8691 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8692 });
8693
8694 let mocked_response = lsp::SignatureHelp {
8695 signatures: vec![lsp::SignatureInformation {
8696 label: "fn sample(param1: u8, param2: u8)".to_string(),
8697 documentation: None,
8698 parameters: Some(vec![
8699 lsp::ParameterInformation {
8700 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8701 documentation: None,
8702 },
8703 lsp::ParameterInformation {
8704 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8705 documentation: None,
8706 },
8707 ]),
8708 active_parameter: None,
8709 }],
8710 active_signature: Some(0),
8711 active_parameter: Some(0),
8712 };
8713 handle_signature_help_request(&mut cx, mocked_response).await;
8714
8715 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8716 .await;
8717
8718 cx.editor(|editor, _, _| {
8719 let signature_help_state = editor.signature_help_state.popover().cloned();
8720 assert!(signature_help_state.is_some());
8721 assert_eq!(
8722 signature_help_state.unwrap().label,
8723 "param1: u8, param2: u8"
8724 );
8725 });
8726
8727 // When exiting outside from inside the brackets, `signature_help` is closed.
8728 cx.set_state(indoc! {"
8729 fn main() {
8730 sample(ˇ);
8731 }
8732
8733 fn sample(param1: u8, param2: u8) {}
8734 "});
8735
8736 cx.update_editor(|editor, window, cx| {
8737 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8738 });
8739
8740 let mocked_response = lsp::SignatureHelp {
8741 signatures: Vec::new(),
8742 active_signature: None,
8743 active_parameter: None,
8744 };
8745 handle_signature_help_request(&mut cx, mocked_response).await;
8746
8747 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8748 .await;
8749
8750 cx.editor(|editor, _, _| {
8751 assert!(!editor.signature_help_state.is_shown());
8752 });
8753
8754 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8755 cx.set_state(indoc! {"
8756 fn main() {
8757 sample(ˇ);
8758 }
8759
8760 fn sample(param1: u8, param2: u8) {}
8761 "});
8762
8763 let mocked_response = lsp::SignatureHelp {
8764 signatures: vec![lsp::SignatureInformation {
8765 label: "fn sample(param1: u8, param2: u8)".to_string(),
8766 documentation: None,
8767 parameters: Some(vec![
8768 lsp::ParameterInformation {
8769 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8770 documentation: None,
8771 },
8772 lsp::ParameterInformation {
8773 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8774 documentation: None,
8775 },
8776 ]),
8777 active_parameter: None,
8778 }],
8779 active_signature: Some(0),
8780 active_parameter: Some(0),
8781 };
8782 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8783 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8784 .await;
8785 cx.editor(|editor, _, _| {
8786 assert!(editor.signature_help_state.is_shown());
8787 });
8788
8789 // Restore the popover with more parameter input
8790 cx.set_state(indoc! {"
8791 fn main() {
8792 sample(param1, param2ˇ);
8793 }
8794
8795 fn sample(param1: u8, param2: u8) {}
8796 "});
8797
8798 let mocked_response = lsp::SignatureHelp {
8799 signatures: vec![lsp::SignatureInformation {
8800 label: "fn sample(param1: u8, param2: u8)".to_string(),
8801 documentation: None,
8802 parameters: Some(vec![
8803 lsp::ParameterInformation {
8804 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8805 documentation: None,
8806 },
8807 lsp::ParameterInformation {
8808 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8809 documentation: None,
8810 },
8811 ]),
8812 active_parameter: None,
8813 }],
8814 active_signature: Some(0),
8815 active_parameter: Some(1),
8816 };
8817 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8818 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8819 .await;
8820
8821 // When selecting a range, the popover is gone.
8822 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8823 cx.update_editor(|editor, window, cx| {
8824 editor.change_selections(None, window, cx, |s| {
8825 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8826 })
8827 });
8828 cx.assert_editor_state(indoc! {"
8829 fn main() {
8830 sample(param1, «ˇparam2»);
8831 }
8832
8833 fn sample(param1: u8, param2: u8) {}
8834 "});
8835 cx.editor(|editor, _, _| {
8836 assert!(!editor.signature_help_state.is_shown());
8837 });
8838
8839 // When unselecting again, the popover is back if within the brackets.
8840 cx.update_editor(|editor, window, cx| {
8841 editor.change_selections(None, window, cx, |s| {
8842 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8843 })
8844 });
8845 cx.assert_editor_state(indoc! {"
8846 fn main() {
8847 sample(param1, ˇparam2);
8848 }
8849
8850 fn sample(param1: u8, param2: u8) {}
8851 "});
8852 handle_signature_help_request(&mut cx, mocked_response).await;
8853 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8854 .await;
8855 cx.editor(|editor, _, _| {
8856 assert!(editor.signature_help_state.is_shown());
8857 });
8858
8859 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8860 cx.update_editor(|editor, window, cx| {
8861 editor.change_selections(None, window, cx, |s| {
8862 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8863 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8864 })
8865 });
8866 cx.assert_editor_state(indoc! {"
8867 fn main() {
8868 sample(param1, ˇparam2);
8869 }
8870
8871 fn sample(param1: u8, param2: u8) {}
8872 "});
8873
8874 let mocked_response = lsp::SignatureHelp {
8875 signatures: vec![lsp::SignatureInformation {
8876 label: "fn sample(param1: u8, param2: u8)".to_string(),
8877 documentation: None,
8878 parameters: Some(vec![
8879 lsp::ParameterInformation {
8880 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8881 documentation: None,
8882 },
8883 lsp::ParameterInformation {
8884 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8885 documentation: None,
8886 },
8887 ]),
8888 active_parameter: None,
8889 }],
8890 active_signature: Some(0),
8891 active_parameter: Some(1),
8892 };
8893 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8894 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8895 .await;
8896 cx.update_editor(|editor, _, cx| {
8897 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8898 });
8899 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8900 .await;
8901 cx.update_editor(|editor, window, cx| {
8902 editor.change_selections(None, window, cx, |s| {
8903 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8904 })
8905 });
8906 cx.assert_editor_state(indoc! {"
8907 fn main() {
8908 sample(param1, «ˇparam2»);
8909 }
8910
8911 fn sample(param1: u8, param2: u8) {}
8912 "});
8913 cx.update_editor(|editor, window, cx| {
8914 editor.change_selections(None, window, cx, |s| {
8915 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8916 })
8917 });
8918 cx.assert_editor_state(indoc! {"
8919 fn main() {
8920 sample(param1, ˇparam2);
8921 }
8922
8923 fn sample(param1: u8, param2: u8) {}
8924 "});
8925 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8926 .await;
8927}
8928
8929#[gpui::test]
8930async fn test_completion(cx: &mut TestAppContext) {
8931 init_test(cx, |_| {});
8932
8933 let mut cx = EditorLspTestContext::new_rust(
8934 lsp::ServerCapabilities {
8935 completion_provider: Some(lsp::CompletionOptions {
8936 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8937 resolve_provider: Some(true),
8938 ..Default::default()
8939 }),
8940 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8941 ..Default::default()
8942 },
8943 cx,
8944 )
8945 .await;
8946 let counter = Arc::new(AtomicUsize::new(0));
8947
8948 cx.set_state(indoc! {"
8949 oneˇ
8950 two
8951 three
8952 "});
8953 cx.simulate_keystroke(".");
8954 handle_completion_request(
8955 &mut cx,
8956 indoc! {"
8957 one.|<>
8958 two
8959 three
8960 "},
8961 vec!["first_completion", "second_completion"],
8962 counter.clone(),
8963 )
8964 .await;
8965 cx.condition(|editor, _| editor.context_menu_visible())
8966 .await;
8967 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8968
8969 let _handler = handle_signature_help_request(
8970 &mut cx,
8971 lsp::SignatureHelp {
8972 signatures: vec![lsp::SignatureInformation {
8973 label: "test signature".to_string(),
8974 documentation: None,
8975 parameters: Some(vec![lsp::ParameterInformation {
8976 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8977 documentation: None,
8978 }]),
8979 active_parameter: None,
8980 }],
8981 active_signature: None,
8982 active_parameter: None,
8983 },
8984 );
8985 cx.update_editor(|editor, window, cx| {
8986 assert!(
8987 !editor.signature_help_state.is_shown(),
8988 "No signature help was called for"
8989 );
8990 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8991 });
8992 cx.run_until_parked();
8993 cx.update_editor(|editor, _, _| {
8994 assert!(
8995 !editor.signature_help_state.is_shown(),
8996 "No signature help should be shown when completions menu is open"
8997 );
8998 });
8999
9000 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9001 editor.context_menu_next(&Default::default(), window, cx);
9002 editor
9003 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9004 .unwrap()
9005 });
9006 cx.assert_editor_state(indoc! {"
9007 one.second_completionˇ
9008 two
9009 three
9010 "});
9011
9012 handle_resolve_completion_request(
9013 &mut cx,
9014 Some(vec![
9015 (
9016 //This overlaps with the primary completion edit which is
9017 //misbehavior from the LSP spec, test that we filter it out
9018 indoc! {"
9019 one.second_ˇcompletion
9020 two
9021 threeˇ
9022 "},
9023 "overlapping additional edit",
9024 ),
9025 (
9026 indoc! {"
9027 one.second_completion
9028 two
9029 threeˇ
9030 "},
9031 "\nadditional edit",
9032 ),
9033 ]),
9034 )
9035 .await;
9036 apply_additional_edits.await.unwrap();
9037 cx.assert_editor_state(indoc! {"
9038 one.second_completionˇ
9039 two
9040 three
9041 additional edit
9042 "});
9043
9044 cx.set_state(indoc! {"
9045 one.second_completion
9046 twoˇ
9047 threeˇ
9048 additional edit
9049 "});
9050 cx.simulate_keystroke(" ");
9051 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9052 cx.simulate_keystroke("s");
9053 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9054
9055 cx.assert_editor_state(indoc! {"
9056 one.second_completion
9057 two sˇ
9058 three sˇ
9059 additional edit
9060 "});
9061 handle_completion_request(
9062 &mut cx,
9063 indoc! {"
9064 one.second_completion
9065 two s
9066 three <s|>
9067 additional edit
9068 "},
9069 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9070 counter.clone(),
9071 )
9072 .await;
9073 cx.condition(|editor, _| editor.context_menu_visible())
9074 .await;
9075 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9076
9077 cx.simulate_keystroke("i");
9078
9079 handle_completion_request(
9080 &mut cx,
9081 indoc! {"
9082 one.second_completion
9083 two si
9084 three <si|>
9085 additional edit
9086 "},
9087 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9088 counter.clone(),
9089 )
9090 .await;
9091 cx.condition(|editor, _| editor.context_menu_visible())
9092 .await;
9093 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9094
9095 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9096 editor
9097 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9098 .unwrap()
9099 });
9100 cx.assert_editor_state(indoc! {"
9101 one.second_completion
9102 two sixth_completionˇ
9103 three sixth_completionˇ
9104 additional edit
9105 "});
9106
9107 apply_additional_edits.await.unwrap();
9108
9109 update_test_language_settings(&mut cx, |settings| {
9110 settings.defaults.show_completions_on_input = Some(false);
9111 });
9112 cx.set_state("editorˇ");
9113 cx.simulate_keystroke(".");
9114 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9115 cx.simulate_keystroke("c");
9116 cx.simulate_keystroke("l");
9117 cx.simulate_keystroke("o");
9118 cx.assert_editor_state("editor.cloˇ");
9119 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9120 cx.update_editor(|editor, window, cx| {
9121 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9122 });
9123 handle_completion_request(
9124 &mut cx,
9125 "editor.<clo|>",
9126 vec!["close", "clobber"],
9127 counter.clone(),
9128 )
9129 .await;
9130 cx.condition(|editor, _| editor.context_menu_visible())
9131 .await;
9132 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9133
9134 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9135 editor
9136 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9137 .unwrap()
9138 });
9139 cx.assert_editor_state("editor.closeˇ");
9140 handle_resolve_completion_request(&mut cx, None).await;
9141 apply_additional_edits.await.unwrap();
9142}
9143
9144#[gpui::test]
9145async fn test_multiline_completion(cx: &mut TestAppContext) {
9146 init_test(cx, |_| {});
9147
9148 let fs = FakeFs::new(cx.executor());
9149 fs.insert_tree(
9150 path!("/a"),
9151 json!({
9152 "main.ts": "a",
9153 }),
9154 )
9155 .await;
9156
9157 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9158 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9159 let typescript_language = Arc::new(Language::new(
9160 LanguageConfig {
9161 name: "TypeScript".into(),
9162 matcher: LanguageMatcher {
9163 path_suffixes: vec!["ts".to_string()],
9164 ..LanguageMatcher::default()
9165 },
9166 line_comments: vec!["// ".into()],
9167 ..LanguageConfig::default()
9168 },
9169 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9170 ));
9171 language_registry.add(typescript_language.clone());
9172 let mut fake_servers = language_registry.register_fake_lsp(
9173 "TypeScript",
9174 FakeLspAdapter {
9175 capabilities: lsp::ServerCapabilities {
9176 completion_provider: Some(lsp::CompletionOptions {
9177 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9178 ..lsp::CompletionOptions::default()
9179 }),
9180 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9181 ..lsp::ServerCapabilities::default()
9182 },
9183 // Emulate vtsls label generation
9184 label_for_completion: Some(Box::new(|item, _| {
9185 let text = if let Some(description) = item
9186 .label_details
9187 .as_ref()
9188 .and_then(|label_details| label_details.description.as_ref())
9189 {
9190 format!("{} {}", item.label, description)
9191 } else if let Some(detail) = &item.detail {
9192 format!("{} {}", item.label, detail)
9193 } else {
9194 item.label.clone()
9195 };
9196 let len = text.len();
9197 Some(language::CodeLabel {
9198 text,
9199 runs: Vec::new(),
9200 filter_range: 0..len,
9201 })
9202 })),
9203 ..FakeLspAdapter::default()
9204 },
9205 );
9206 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9207 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9208 let worktree_id = workspace
9209 .update(cx, |workspace, _window, cx| {
9210 workspace.project().update(cx, |project, cx| {
9211 project.worktrees(cx).next().unwrap().read(cx).id()
9212 })
9213 })
9214 .unwrap();
9215 let _buffer = project
9216 .update(cx, |project, cx| {
9217 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9218 })
9219 .await
9220 .unwrap();
9221 let editor = workspace
9222 .update(cx, |workspace, window, cx| {
9223 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9224 })
9225 .unwrap()
9226 .await
9227 .unwrap()
9228 .downcast::<Editor>()
9229 .unwrap();
9230 let fake_server = fake_servers.next().await.unwrap();
9231
9232 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9233 let multiline_label_2 = "a\nb\nc\n";
9234 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9235 let multiline_description = "d\ne\nf\n";
9236 let multiline_detail_2 = "g\nh\ni\n";
9237
9238 let mut completion_handle =
9239 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9240 Ok(Some(lsp::CompletionResponse::Array(vec![
9241 lsp::CompletionItem {
9242 label: multiline_label.to_string(),
9243 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9244 range: lsp::Range {
9245 start: lsp::Position {
9246 line: params.text_document_position.position.line,
9247 character: params.text_document_position.position.character,
9248 },
9249 end: lsp::Position {
9250 line: params.text_document_position.position.line,
9251 character: params.text_document_position.position.character,
9252 },
9253 },
9254 new_text: "new_text_1".to_string(),
9255 })),
9256 ..lsp::CompletionItem::default()
9257 },
9258 lsp::CompletionItem {
9259 label: "single line label 1".to_string(),
9260 detail: Some(multiline_detail.to_string()),
9261 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9262 range: lsp::Range {
9263 start: lsp::Position {
9264 line: params.text_document_position.position.line,
9265 character: params.text_document_position.position.character,
9266 },
9267 end: lsp::Position {
9268 line: params.text_document_position.position.line,
9269 character: params.text_document_position.position.character,
9270 },
9271 },
9272 new_text: "new_text_2".to_string(),
9273 })),
9274 ..lsp::CompletionItem::default()
9275 },
9276 lsp::CompletionItem {
9277 label: "single line label 2".to_string(),
9278 label_details: Some(lsp::CompletionItemLabelDetails {
9279 description: Some(multiline_description.to_string()),
9280 detail: None,
9281 }),
9282 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9283 range: lsp::Range {
9284 start: lsp::Position {
9285 line: params.text_document_position.position.line,
9286 character: params.text_document_position.position.character,
9287 },
9288 end: lsp::Position {
9289 line: params.text_document_position.position.line,
9290 character: params.text_document_position.position.character,
9291 },
9292 },
9293 new_text: "new_text_2".to_string(),
9294 })),
9295 ..lsp::CompletionItem::default()
9296 },
9297 lsp::CompletionItem {
9298 label: multiline_label_2.to_string(),
9299 detail: Some(multiline_detail_2.to_string()),
9300 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9301 range: lsp::Range {
9302 start: lsp::Position {
9303 line: params.text_document_position.position.line,
9304 character: params.text_document_position.position.character,
9305 },
9306 end: lsp::Position {
9307 line: params.text_document_position.position.line,
9308 character: params.text_document_position.position.character,
9309 },
9310 },
9311 new_text: "new_text_3".to_string(),
9312 })),
9313 ..lsp::CompletionItem::default()
9314 },
9315 lsp::CompletionItem {
9316 label: "Label with many spaces and \t but without newlines".to_string(),
9317 detail: Some(
9318 "Details with many spaces and \t but without newlines".to_string(),
9319 ),
9320 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9321 range: lsp::Range {
9322 start: lsp::Position {
9323 line: params.text_document_position.position.line,
9324 character: params.text_document_position.position.character,
9325 },
9326 end: lsp::Position {
9327 line: params.text_document_position.position.line,
9328 character: params.text_document_position.position.character,
9329 },
9330 },
9331 new_text: "new_text_4".to_string(),
9332 })),
9333 ..lsp::CompletionItem::default()
9334 },
9335 ])))
9336 });
9337
9338 editor.update_in(cx, |editor, window, cx| {
9339 cx.focus_self(window);
9340 editor.move_to_end(&MoveToEnd, window, cx);
9341 editor.handle_input(".", window, cx);
9342 });
9343 cx.run_until_parked();
9344 completion_handle.next().await.unwrap();
9345
9346 editor.update(cx, |editor, _| {
9347 assert!(editor.context_menu_visible());
9348 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9349 {
9350 let completion_labels = menu
9351 .completions
9352 .borrow()
9353 .iter()
9354 .map(|c| c.label.text.clone())
9355 .collect::<Vec<_>>();
9356 assert_eq!(
9357 completion_labels,
9358 &[
9359 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9360 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9361 "single line label 2 d e f ",
9362 "a b c g h i ",
9363 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9364 ],
9365 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9366 );
9367
9368 for completion in menu
9369 .completions
9370 .borrow()
9371 .iter() {
9372 assert_eq!(
9373 completion.label.filter_range,
9374 0..completion.label.text.len(),
9375 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9376 );
9377 }
9378
9379 } else {
9380 panic!("expected completion menu to be open");
9381 }
9382 });
9383}
9384
9385#[gpui::test]
9386async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9387 init_test(cx, |_| {});
9388 let mut cx = EditorLspTestContext::new_rust(
9389 lsp::ServerCapabilities {
9390 completion_provider: Some(lsp::CompletionOptions {
9391 trigger_characters: Some(vec![".".to_string()]),
9392 ..Default::default()
9393 }),
9394 ..Default::default()
9395 },
9396 cx,
9397 )
9398 .await;
9399 cx.lsp
9400 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9401 Ok(Some(lsp::CompletionResponse::Array(vec![
9402 lsp::CompletionItem {
9403 label: "first".into(),
9404 ..Default::default()
9405 },
9406 lsp::CompletionItem {
9407 label: "last".into(),
9408 ..Default::default()
9409 },
9410 ])))
9411 });
9412 cx.set_state("variableˇ");
9413 cx.simulate_keystroke(".");
9414 cx.executor().run_until_parked();
9415
9416 cx.update_editor(|editor, _, _| {
9417 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9418 {
9419 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9420 } else {
9421 panic!("expected completion menu to be open");
9422 }
9423 });
9424
9425 cx.update_editor(|editor, window, cx| {
9426 editor.move_page_down(&MovePageDown::default(), window, cx);
9427 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9428 {
9429 assert!(
9430 menu.selected_item == 1,
9431 "expected PageDown to select the last item from the context menu"
9432 );
9433 } else {
9434 panic!("expected completion menu to stay open after PageDown");
9435 }
9436 });
9437
9438 cx.update_editor(|editor, window, cx| {
9439 editor.move_page_up(&MovePageUp::default(), window, cx);
9440 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9441 {
9442 assert!(
9443 menu.selected_item == 0,
9444 "expected PageUp to select the first item from the context menu"
9445 );
9446 } else {
9447 panic!("expected completion menu to stay open after PageUp");
9448 }
9449 });
9450}
9451
9452#[gpui::test]
9453async fn test_completion_sort(cx: &mut TestAppContext) {
9454 init_test(cx, |_| {});
9455 let mut cx = EditorLspTestContext::new_rust(
9456 lsp::ServerCapabilities {
9457 completion_provider: Some(lsp::CompletionOptions {
9458 trigger_characters: Some(vec![".".to_string()]),
9459 ..Default::default()
9460 }),
9461 ..Default::default()
9462 },
9463 cx,
9464 )
9465 .await;
9466 cx.lsp
9467 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9468 Ok(Some(lsp::CompletionResponse::Array(vec![
9469 lsp::CompletionItem {
9470 label: "Range".into(),
9471 sort_text: Some("a".into()),
9472 ..Default::default()
9473 },
9474 lsp::CompletionItem {
9475 label: "r".into(),
9476 sort_text: Some("b".into()),
9477 ..Default::default()
9478 },
9479 lsp::CompletionItem {
9480 label: "ret".into(),
9481 sort_text: Some("c".into()),
9482 ..Default::default()
9483 },
9484 lsp::CompletionItem {
9485 label: "return".into(),
9486 sort_text: Some("d".into()),
9487 ..Default::default()
9488 },
9489 lsp::CompletionItem {
9490 label: "slice".into(),
9491 sort_text: Some("d".into()),
9492 ..Default::default()
9493 },
9494 ])))
9495 });
9496 cx.set_state("rˇ");
9497 cx.executor().run_until_parked();
9498 cx.update_editor(|editor, window, cx| {
9499 editor.show_completions(
9500 &ShowCompletions {
9501 trigger: Some("r".into()),
9502 },
9503 window,
9504 cx,
9505 );
9506 });
9507 cx.executor().run_until_parked();
9508
9509 cx.update_editor(|editor, _, _| {
9510 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9511 {
9512 assert_eq!(
9513 completion_menu_entries(&menu),
9514 &["r", "ret", "Range", "return"]
9515 );
9516 } else {
9517 panic!("expected completion menu to be open");
9518 }
9519 });
9520}
9521
9522#[gpui::test]
9523async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9524 init_test(cx, |_| {});
9525
9526 let mut cx = EditorLspTestContext::new_rust(
9527 lsp::ServerCapabilities {
9528 completion_provider: Some(lsp::CompletionOptions {
9529 trigger_characters: Some(vec![".".to_string()]),
9530 resolve_provider: Some(true),
9531 ..Default::default()
9532 }),
9533 ..Default::default()
9534 },
9535 cx,
9536 )
9537 .await;
9538
9539 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9540 cx.simulate_keystroke(".");
9541 let completion_item = lsp::CompletionItem {
9542 label: "Some".into(),
9543 kind: Some(lsp::CompletionItemKind::SNIPPET),
9544 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9545 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9546 kind: lsp::MarkupKind::Markdown,
9547 value: "```rust\nSome(2)\n```".to_string(),
9548 })),
9549 deprecated: Some(false),
9550 sort_text: Some("Some".to_string()),
9551 filter_text: Some("Some".to_string()),
9552 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9553 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9554 range: lsp::Range {
9555 start: lsp::Position {
9556 line: 0,
9557 character: 22,
9558 },
9559 end: lsp::Position {
9560 line: 0,
9561 character: 22,
9562 },
9563 },
9564 new_text: "Some(2)".to_string(),
9565 })),
9566 additional_text_edits: Some(vec![lsp::TextEdit {
9567 range: lsp::Range {
9568 start: lsp::Position {
9569 line: 0,
9570 character: 20,
9571 },
9572 end: lsp::Position {
9573 line: 0,
9574 character: 22,
9575 },
9576 },
9577 new_text: "".to_string(),
9578 }]),
9579 ..Default::default()
9580 };
9581
9582 let closure_completion_item = completion_item.clone();
9583 let counter = Arc::new(AtomicUsize::new(0));
9584 let counter_clone = counter.clone();
9585 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9586 let task_completion_item = closure_completion_item.clone();
9587 counter_clone.fetch_add(1, atomic::Ordering::Release);
9588 async move {
9589 Ok(Some(lsp::CompletionResponse::Array(vec![
9590 task_completion_item,
9591 ])))
9592 }
9593 });
9594
9595 cx.condition(|editor, _| editor.context_menu_visible())
9596 .await;
9597 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9598 assert!(request.next().await.is_some());
9599 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9600
9601 cx.simulate_keystroke("S");
9602 cx.simulate_keystroke("o");
9603 cx.simulate_keystroke("m");
9604 cx.condition(|editor, _| editor.context_menu_visible())
9605 .await;
9606 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9607 assert!(request.next().await.is_some());
9608 assert!(request.next().await.is_some());
9609 assert!(request.next().await.is_some());
9610 request.close();
9611 assert!(request.next().await.is_none());
9612 assert_eq!(
9613 counter.load(atomic::Ordering::Acquire),
9614 4,
9615 "With the completions menu open, only one LSP request should happen per input"
9616 );
9617}
9618
9619#[gpui::test]
9620async fn test_toggle_comment(cx: &mut TestAppContext) {
9621 init_test(cx, |_| {});
9622 let mut cx = EditorTestContext::new(cx).await;
9623 let language = Arc::new(Language::new(
9624 LanguageConfig {
9625 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9626 ..Default::default()
9627 },
9628 Some(tree_sitter_rust::LANGUAGE.into()),
9629 ));
9630 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9631
9632 // If multiple selections intersect a line, the line is only toggled once.
9633 cx.set_state(indoc! {"
9634 fn a() {
9635 «//b();
9636 ˇ»// «c();
9637 //ˇ» d();
9638 }
9639 "});
9640
9641 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9642
9643 cx.assert_editor_state(indoc! {"
9644 fn a() {
9645 «b();
9646 c();
9647 ˇ» d();
9648 }
9649 "});
9650
9651 // The comment prefix is inserted at the same column for every line in a
9652 // selection.
9653 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9654
9655 cx.assert_editor_state(indoc! {"
9656 fn a() {
9657 // «b();
9658 // c();
9659 ˇ»// d();
9660 }
9661 "});
9662
9663 // If a selection ends at the beginning of a line, that line is not toggled.
9664 cx.set_selections_state(indoc! {"
9665 fn a() {
9666 // b();
9667 «// c();
9668 ˇ» // d();
9669 }
9670 "});
9671
9672 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9673
9674 cx.assert_editor_state(indoc! {"
9675 fn a() {
9676 // b();
9677 «c();
9678 ˇ» // d();
9679 }
9680 "});
9681
9682 // If a selection span a single line and is empty, the line is toggled.
9683 cx.set_state(indoc! {"
9684 fn a() {
9685 a();
9686 b();
9687 ˇ
9688 }
9689 "});
9690
9691 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9692
9693 cx.assert_editor_state(indoc! {"
9694 fn a() {
9695 a();
9696 b();
9697 //•ˇ
9698 }
9699 "});
9700
9701 // If a selection span multiple lines, empty lines are not toggled.
9702 cx.set_state(indoc! {"
9703 fn a() {
9704 «a();
9705
9706 c();ˇ»
9707 }
9708 "});
9709
9710 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9711
9712 cx.assert_editor_state(indoc! {"
9713 fn a() {
9714 // «a();
9715
9716 // c();ˇ»
9717 }
9718 "});
9719
9720 // If a selection includes multiple comment prefixes, all lines are uncommented.
9721 cx.set_state(indoc! {"
9722 fn a() {
9723 «// a();
9724 /// b();
9725 //! c();ˇ»
9726 }
9727 "});
9728
9729 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9730
9731 cx.assert_editor_state(indoc! {"
9732 fn a() {
9733 «a();
9734 b();
9735 c();ˇ»
9736 }
9737 "});
9738}
9739
9740#[gpui::test]
9741async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9742 init_test(cx, |_| {});
9743 let mut cx = EditorTestContext::new(cx).await;
9744 let language = Arc::new(Language::new(
9745 LanguageConfig {
9746 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9747 ..Default::default()
9748 },
9749 Some(tree_sitter_rust::LANGUAGE.into()),
9750 ));
9751 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9752
9753 let toggle_comments = &ToggleComments {
9754 advance_downwards: false,
9755 ignore_indent: true,
9756 };
9757
9758 // If multiple selections intersect a line, the line is only toggled once.
9759 cx.set_state(indoc! {"
9760 fn a() {
9761 // «b();
9762 // c();
9763 // ˇ» d();
9764 }
9765 "});
9766
9767 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9768
9769 cx.assert_editor_state(indoc! {"
9770 fn a() {
9771 «b();
9772 c();
9773 ˇ» d();
9774 }
9775 "});
9776
9777 // The comment prefix is inserted at the beginning of each line
9778 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9779
9780 cx.assert_editor_state(indoc! {"
9781 fn a() {
9782 // «b();
9783 // c();
9784 // ˇ» d();
9785 }
9786 "});
9787
9788 // If a selection ends at the beginning of a line, that line is not toggled.
9789 cx.set_selections_state(indoc! {"
9790 fn a() {
9791 // b();
9792 // «c();
9793 ˇ»// d();
9794 }
9795 "});
9796
9797 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9798
9799 cx.assert_editor_state(indoc! {"
9800 fn a() {
9801 // b();
9802 «c();
9803 ˇ»// d();
9804 }
9805 "});
9806
9807 // If a selection span a single line and is empty, the line is toggled.
9808 cx.set_state(indoc! {"
9809 fn a() {
9810 a();
9811 b();
9812 ˇ
9813 }
9814 "});
9815
9816 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9817
9818 cx.assert_editor_state(indoc! {"
9819 fn a() {
9820 a();
9821 b();
9822 //ˇ
9823 }
9824 "});
9825
9826 // If a selection span multiple lines, empty lines are not toggled.
9827 cx.set_state(indoc! {"
9828 fn a() {
9829 «a();
9830
9831 c();ˇ»
9832 }
9833 "});
9834
9835 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9836
9837 cx.assert_editor_state(indoc! {"
9838 fn a() {
9839 // «a();
9840
9841 // c();ˇ»
9842 }
9843 "});
9844
9845 // If a selection includes multiple comment prefixes, all lines are uncommented.
9846 cx.set_state(indoc! {"
9847 fn a() {
9848 // «a();
9849 /// b();
9850 //! c();ˇ»
9851 }
9852 "});
9853
9854 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9855
9856 cx.assert_editor_state(indoc! {"
9857 fn a() {
9858 «a();
9859 b();
9860 c();ˇ»
9861 }
9862 "});
9863}
9864
9865#[gpui::test]
9866async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9867 init_test(cx, |_| {});
9868
9869 let language = Arc::new(Language::new(
9870 LanguageConfig {
9871 line_comments: vec!["// ".into()],
9872 ..Default::default()
9873 },
9874 Some(tree_sitter_rust::LANGUAGE.into()),
9875 ));
9876
9877 let mut cx = EditorTestContext::new(cx).await;
9878
9879 cx.language_registry().add(language.clone());
9880 cx.update_buffer(|buffer, cx| {
9881 buffer.set_language(Some(language), cx);
9882 });
9883
9884 let toggle_comments = &ToggleComments {
9885 advance_downwards: true,
9886 ignore_indent: false,
9887 };
9888
9889 // Single cursor on one line -> advance
9890 // Cursor moves horizontally 3 characters as well on non-blank line
9891 cx.set_state(indoc!(
9892 "fn a() {
9893 ˇdog();
9894 cat();
9895 }"
9896 ));
9897 cx.update_editor(|editor, window, cx| {
9898 editor.toggle_comments(toggle_comments, window, cx);
9899 });
9900 cx.assert_editor_state(indoc!(
9901 "fn a() {
9902 // dog();
9903 catˇ();
9904 }"
9905 ));
9906
9907 // Single selection on one line -> don't advance
9908 cx.set_state(indoc!(
9909 "fn a() {
9910 «dog()ˇ»;
9911 cat();
9912 }"
9913 ));
9914 cx.update_editor(|editor, window, cx| {
9915 editor.toggle_comments(toggle_comments, window, cx);
9916 });
9917 cx.assert_editor_state(indoc!(
9918 "fn a() {
9919 // «dog()ˇ»;
9920 cat();
9921 }"
9922 ));
9923
9924 // Multiple cursors on one line -> advance
9925 cx.set_state(indoc!(
9926 "fn a() {
9927 ˇdˇog();
9928 cat();
9929 }"
9930 ));
9931 cx.update_editor(|editor, window, cx| {
9932 editor.toggle_comments(toggle_comments, window, cx);
9933 });
9934 cx.assert_editor_state(indoc!(
9935 "fn a() {
9936 // dog();
9937 catˇ(ˇ);
9938 }"
9939 ));
9940
9941 // Multiple cursors on one line, with selection -> don't advance
9942 cx.set_state(indoc!(
9943 "fn a() {
9944 ˇdˇog«()ˇ»;
9945 cat();
9946 }"
9947 ));
9948 cx.update_editor(|editor, window, cx| {
9949 editor.toggle_comments(toggle_comments, window, cx);
9950 });
9951 cx.assert_editor_state(indoc!(
9952 "fn a() {
9953 // ˇdˇog«()ˇ»;
9954 cat();
9955 }"
9956 ));
9957
9958 // Single cursor on one line -> advance
9959 // Cursor moves to column 0 on blank line
9960 cx.set_state(indoc!(
9961 "fn a() {
9962 ˇdog();
9963
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 ˇ
9974 cat();
9975 }"
9976 ));
9977
9978 // Single cursor on one line -> advance
9979 // Cursor starts and ends at column 0
9980 cx.set_state(indoc!(
9981 "fn a() {
9982 ˇ dog();
9983 cat();
9984 }"
9985 ));
9986 cx.update_editor(|editor, window, cx| {
9987 editor.toggle_comments(toggle_comments, window, cx);
9988 });
9989 cx.assert_editor_state(indoc!(
9990 "fn a() {
9991 // dog();
9992 ˇ cat();
9993 }"
9994 ));
9995}
9996
9997#[gpui::test]
9998async fn test_toggle_block_comment(cx: &mut TestAppContext) {
9999 init_test(cx, |_| {});
10000
10001 let mut cx = EditorTestContext::new(cx).await;
10002
10003 let html_language = Arc::new(
10004 Language::new(
10005 LanguageConfig {
10006 name: "HTML".into(),
10007 block_comment: Some(("<!-- ".into(), " -->".into())),
10008 ..Default::default()
10009 },
10010 Some(tree_sitter_html::LANGUAGE.into()),
10011 )
10012 .with_injection_query(
10013 r#"
10014 (script_element
10015 (raw_text) @injection.content
10016 (#set! injection.language "javascript"))
10017 "#,
10018 )
10019 .unwrap(),
10020 );
10021
10022 let javascript_language = Arc::new(Language::new(
10023 LanguageConfig {
10024 name: "JavaScript".into(),
10025 line_comments: vec!["// ".into()],
10026 ..Default::default()
10027 },
10028 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10029 ));
10030
10031 cx.language_registry().add(html_language.clone());
10032 cx.language_registry().add(javascript_language.clone());
10033 cx.update_buffer(|buffer, cx| {
10034 buffer.set_language(Some(html_language), cx);
10035 });
10036
10037 // Toggle comments for empty selections
10038 cx.set_state(
10039 &r#"
10040 <p>A</p>ˇ
10041 <p>B</p>ˇ
10042 <p>C</p>ˇ
10043 "#
10044 .unindent(),
10045 );
10046 cx.update_editor(|editor, window, cx| {
10047 editor.toggle_comments(&ToggleComments::default(), window, cx)
10048 });
10049 cx.assert_editor_state(
10050 &r#"
10051 <!-- <p>A</p>ˇ -->
10052 <!-- <p>B</p>ˇ -->
10053 <!-- <p>C</p>ˇ -->
10054 "#
10055 .unindent(),
10056 );
10057 cx.update_editor(|editor, window, cx| {
10058 editor.toggle_comments(&ToggleComments::default(), window, cx)
10059 });
10060 cx.assert_editor_state(
10061 &r#"
10062 <p>A</p>ˇ
10063 <p>B</p>ˇ
10064 <p>C</p>ˇ
10065 "#
10066 .unindent(),
10067 );
10068
10069 // Toggle comments for mixture of empty and non-empty selections, where
10070 // multiple selections occupy a given line.
10071 cx.set_state(
10072 &r#"
10073 <p>A«</p>
10074 <p>ˇ»B</p>ˇ
10075 <p>C«</p>
10076 <p>ˇ»D</p>ˇ
10077 "#
10078 .unindent(),
10079 );
10080
10081 cx.update_editor(|editor, window, cx| {
10082 editor.toggle_comments(&ToggleComments::default(), window, cx)
10083 });
10084 cx.assert_editor_state(
10085 &r#"
10086 <!-- <p>A«</p>
10087 <p>ˇ»B</p>ˇ -->
10088 <!-- <p>C«</p>
10089 <p>ˇ»D</p>ˇ -->
10090 "#
10091 .unindent(),
10092 );
10093 cx.update_editor(|editor, window, cx| {
10094 editor.toggle_comments(&ToggleComments::default(), window, cx)
10095 });
10096 cx.assert_editor_state(
10097 &r#"
10098 <p>A«</p>
10099 <p>ˇ»B</p>ˇ
10100 <p>C«</p>
10101 <p>ˇ»D</p>ˇ
10102 "#
10103 .unindent(),
10104 );
10105
10106 // Toggle comments when different languages are active for different
10107 // selections.
10108 cx.set_state(
10109 &r#"
10110 ˇ<script>
10111 ˇvar x = new Y();
10112 ˇ</script>
10113 "#
10114 .unindent(),
10115 );
10116 cx.executor().run_until_parked();
10117 cx.update_editor(|editor, window, cx| {
10118 editor.toggle_comments(&ToggleComments::default(), window, cx)
10119 });
10120 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10121 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10122 cx.assert_editor_state(
10123 &r#"
10124 <!-- ˇ<script> -->
10125 // ˇvar x = new Y();
10126 <!-- ˇ</script> -->
10127 "#
10128 .unindent(),
10129 );
10130}
10131
10132#[gpui::test]
10133fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10134 init_test(cx, |_| {});
10135
10136 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10137 let multibuffer = cx.new(|cx| {
10138 let mut multibuffer = MultiBuffer::new(ReadWrite);
10139 multibuffer.push_excerpts(
10140 buffer.clone(),
10141 [
10142 ExcerptRange {
10143 context: Point::new(0, 0)..Point::new(0, 4),
10144 primary: None,
10145 },
10146 ExcerptRange {
10147 context: Point::new(1, 0)..Point::new(1, 4),
10148 primary: None,
10149 },
10150 ],
10151 cx,
10152 );
10153 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10154 multibuffer
10155 });
10156
10157 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10158 editor.update_in(cx, |editor, window, cx| {
10159 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10160 editor.change_selections(None, window, cx, |s| {
10161 s.select_ranges([
10162 Point::new(0, 0)..Point::new(0, 0),
10163 Point::new(1, 0)..Point::new(1, 0),
10164 ])
10165 });
10166
10167 editor.handle_input("X", window, cx);
10168 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10169 assert_eq!(
10170 editor.selections.ranges(cx),
10171 [
10172 Point::new(0, 1)..Point::new(0, 1),
10173 Point::new(1, 1)..Point::new(1, 1),
10174 ]
10175 );
10176
10177 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10178 editor.change_selections(None, window, cx, |s| {
10179 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10180 });
10181 editor.backspace(&Default::default(), window, cx);
10182 assert_eq!(editor.text(cx), "Xa\nbbb");
10183 assert_eq!(
10184 editor.selections.ranges(cx),
10185 [Point::new(1, 0)..Point::new(1, 0)]
10186 );
10187
10188 editor.change_selections(None, window, cx, |s| {
10189 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10190 });
10191 editor.backspace(&Default::default(), window, cx);
10192 assert_eq!(editor.text(cx), "X\nbb");
10193 assert_eq!(
10194 editor.selections.ranges(cx),
10195 [Point::new(0, 1)..Point::new(0, 1)]
10196 );
10197 });
10198}
10199
10200#[gpui::test]
10201fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10202 init_test(cx, |_| {});
10203
10204 let markers = vec![('[', ']').into(), ('(', ')').into()];
10205 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10206 indoc! {"
10207 [aaaa
10208 (bbbb]
10209 cccc)",
10210 },
10211 markers.clone(),
10212 );
10213 let excerpt_ranges = markers.into_iter().map(|marker| {
10214 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10215 ExcerptRange {
10216 context,
10217 primary: None,
10218 }
10219 });
10220 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10221 let multibuffer = cx.new(|cx| {
10222 let mut multibuffer = MultiBuffer::new(ReadWrite);
10223 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10224 multibuffer
10225 });
10226
10227 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10228 editor.update_in(cx, |editor, window, cx| {
10229 let (expected_text, selection_ranges) = marked_text_ranges(
10230 indoc! {"
10231 aaaa
10232 bˇbbb
10233 bˇbbˇb
10234 cccc"
10235 },
10236 true,
10237 );
10238 assert_eq!(editor.text(cx), expected_text);
10239 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10240
10241 editor.handle_input("X", window, cx);
10242
10243 let (expected_text, expected_selections) = marked_text_ranges(
10244 indoc! {"
10245 aaaa
10246 bXˇbbXb
10247 bXˇbbXˇb
10248 cccc"
10249 },
10250 false,
10251 );
10252 assert_eq!(editor.text(cx), expected_text);
10253 assert_eq!(editor.selections.ranges(cx), expected_selections);
10254
10255 editor.newline(&Newline, window, cx);
10256 let (expected_text, expected_selections) = marked_text_ranges(
10257 indoc! {"
10258 aaaa
10259 bX
10260 ˇbbX
10261 b
10262 bX
10263 ˇbbX
10264 ˇb
10265 cccc"
10266 },
10267 false,
10268 );
10269 assert_eq!(editor.text(cx), expected_text);
10270 assert_eq!(editor.selections.ranges(cx), expected_selections);
10271 });
10272}
10273
10274#[gpui::test]
10275fn test_refresh_selections(cx: &mut TestAppContext) {
10276 init_test(cx, |_| {});
10277
10278 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10279 let mut excerpt1_id = None;
10280 let multibuffer = cx.new(|cx| {
10281 let mut multibuffer = MultiBuffer::new(ReadWrite);
10282 excerpt1_id = multibuffer
10283 .push_excerpts(
10284 buffer.clone(),
10285 [
10286 ExcerptRange {
10287 context: Point::new(0, 0)..Point::new(1, 4),
10288 primary: None,
10289 },
10290 ExcerptRange {
10291 context: Point::new(1, 0)..Point::new(2, 4),
10292 primary: None,
10293 },
10294 ],
10295 cx,
10296 )
10297 .into_iter()
10298 .next();
10299 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10300 multibuffer
10301 });
10302
10303 let editor = cx.add_window(|window, cx| {
10304 let mut editor = build_editor(multibuffer.clone(), window, cx);
10305 let snapshot = editor.snapshot(window, cx);
10306 editor.change_selections(None, window, cx, |s| {
10307 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10308 });
10309 editor.begin_selection(
10310 Point::new(2, 1).to_display_point(&snapshot),
10311 true,
10312 1,
10313 window,
10314 cx,
10315 );
10316 assert_eq!(
10317 editor.selections.ranges(cx),
10318 [
10319 Point::new(1, 3)..Point::new(1, 3),
10320 Point::new(2, 1)..Point::new(2, 1),
10321 ]
10322 );
10323 editor
10324 });
10325
10326 // Refreshing selections is a no-op when excerpts haven't changed.
10327 _ = editor.update(cx, |editor, window, cx| {
10328 editor.change_selections(None, window, cx, |s| s.refresh());
10329 assert_eq!(
10330 editor.selections.ranges(cx),
10331 [
10332 Point::new(1, 3)..Point::new(1, 3),
10333 Point::new(2, 1)..Point::new(2, 1),
10334 ]
10335 );
10336 });
10337
10338 multibuffer.update(cx, |multibuffer, cx| {
10339 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10340 });
10341 _ = editor.update(cx, |editor, window, cx| {
10342 // Removing an excerpt causes the first selection to become degenerate.
10343 assert_eq!(
10344 editor.selections.ranges(cx),
10345 [
10346 Point::new(0, 0)..Point::new(0, 0),
10347 Point::new(0, 1)..Point::new(0, 1)
10348 ]
10349 );
10350
10351 // Refreshing selections will relocate the first selection to the original buffer
10352 // location.
10353 editor.change_selections(None, window, cx, |s| s.refresh());
10354 assert_eq!(
10355 editor.selections.ranges(cx),
10356 [
10357 Point::new(0, 1)..Point::new(0, 1),
10358 Point::new(0, 3)..Point::new(0, 3)
10359 ]
10360 );
10361 assert!(editor.selections.pending_anchor().is_some());
10362 });
10363}
10364
10365#[gpui::test]
10366fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10367 init_test(cx, |_| {});
10368
10369 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10370 let mut excerpt1_id = None;
10371 let multibuffer = cx.new(|cx| {
10372 let mut multibuffer = MultiBuffer::new(ReadWrite);
10373 excerpt1_id = multibuffer
10374 .push_excerpts(
10375 buffer.clone(),
10376 [
10377 ExcerptRange {
10378 context: Point::new(0, 0)..Point::new(1, 4),
10379 primary: None,
10380 },
10381 ExcerptRange {
10382 context: Point::new(1, 0)..Point::new(2, 4),
10383 primary: None,
10384 },
10385 ],
10386 cx,
10387 )
10388 .into_iter()
10389 .next();
10390 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10391 multibuffer
10392 });
10393
10394 let editor = cx.add_window(|window, cx| {
10395 let mut editor = build_editor(multibuffer.clone(), window, cx);
10396 let snapshot = editor.snapshot(window, cx);
10397 editor.begin_selection(
10398 Point::new(1, 3).to_display_point(&snapshot),
10399 false,
10400 1,
10401 window,
10402 cx,
10403 );
10404 assert_eq!(
10405 editor.selections.ranges(cx),
10406 [Point::new(1, 3)..Point::new(1, 3)]
10407 );
10408 editor
10409 });
10410
10411 multibuffer.update(cx, |multibuffer, cx| {
10412 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10413 });
10414 _ = editor.update(cx, |editor, window, cx| {
10415 assert_eq!(
10416 editor.selections.ranges(cx),
10417 [Point::new(0, 0)..Point::new(0, 0)]
10418 );
10419
10420 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10421 editor.change_selections(None, window, cx, |s| s.refresh());
10422 assert_eq!(
10423 editor.selections.ranges(cx),
10424 [Point::new(0, 3)..Point::new(0, 3)]
10425 );
10426 assert!(editor.selections.pending_anchor().is_some());
10427 });
10428}
10429
10430#[gpui::test]
10431async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10432 init_test(cx, |_| {});
10433
10434 let language = Arc::new(
10435 Language::new(
10436 LanguageConfig {
10437 brackets: BracketPairConfig {
10438 pairs: vec![
10439 BracketPair {
10440 start: "{".to_string(),
10441 end: "}".to_string(),
10442 close: true,
10443 surround: true,
10444 newline: true,
10445 },
10446 BracketPair {
10447 start: "/* ".to_string(),
10448 end: " */".to_string(),
10449 close: true,
10450 surround: true,
10451 newline: true,
10452 },
10453 ],
10454 ..Default::default()
10455 },
10456 ..Default::default()
10457 },
10458 Some(tree_sitter_rust::LANGUAGE.into()),
10459 )
10460 .with_indents_query("")
10461 .unwrap(),
10462 );
10463
10464 let text = concat!(
10465 "{ }\n", //
10466 " x\n", //
10467 " /* */\n", //
10468 "x\n", //
10469 "{{} }\n", //
10470 );
10471
10472 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10473 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10474 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10475 editor
10476 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10477 .await;
10478
10479 editor.update_in(cx, |editor, window, cx| {
10480 editor.change_selections(None, window, cx, |s| {
10481 s.select_display_ranges([
10482 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10483 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10484 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10485 ])
10486 });
10487 editor.newline(&Newline, window, cx);
10488
10489 assert_eq!(
10490 editor.buffer().read(cx).read(cx).text(),
10491 concat!(
10492 "{ \n", // Suppress rustfmt
10493 "\n", //
10494 "}\n", //
10495 " x\n", //
10496 " /* \n", //
10497 " \n", //
10498 " */\n", //
10499 "x\n", //
10500 "{{} \n", //
10501 "}\n", //
10502 )
10503 );
10504 });
10505}
10506
10507#[gpui::test]
10508fn test_highlighted_ranges(cx: &mut TestAppContext) {
10509 init_test(cx, |_| {});
10510
10511 let editor = cx.add_window(|window, cx| {
10512 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10513 build_editor(buffer.clone(), window, cx)
10514 });
10515
10516 _ = editor.update(cx, |editor, window, cx| {
10517 struct Type1;
10518 struct Type2;
10519
10520 let buffer = editor.buffer.read(cx).snapshot(cx);
10521
10522 let anchor_range =
10523 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10524
10525 editor.highlight_background::<Type1>(
10526 &[
10527 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10528 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10529 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10530 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10531 ],
10532 |_| Hsla::red(),
10533 cx,
10534 );
10535 editor.highlight_background::<Type2>(
10536 &[
10537 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10538 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10539 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10540 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10541 ],
10542 |_| Hsla::green(),
10543 cx,
10544 );
10545
10546 let snapshot = editor.snapshot(window, cx);
10547 let mut highlighted_ranges = editor.background_highlights_in_range(
10548 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10549 &snapshot,
10550 cx.theme().colors(),
10551 );
10552 // Enforce a consistent ordering based on color without relying on the ordering of the
10553 // highlight's `TypeId` which is non-executor.
10554 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10555 assert_eq!(
10556 highlighted_ranges,
10557 &[
10558 (
10559 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10560 Hsla::red(),
10561 ),
10562 (
10563 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10564 Hsla::red(),
10565 ),
10566 (
10567 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10568 Hsla::green(),
10569 ),
10570 (
10571 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10572 Hsla::green(),
10573 ),
10574 ]
10575 );
10576 assert_eq!(
10577 editor.background_highlights_in_range(
10578 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10579 &snapshot,
10580 cx.theme().colors(),
10581 ),
10582 &[(
10583 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10584 Hsla::red(),
10585 )]
10586 );
10587 });
10588}
10589
10590#[gpui::test]
10591async fn test_following(cx: &mut TestAppContext) {
10592 init_test(cx, |_| {});
10593
10594 let fs = FakeFs::new(cx.executor());
10595 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10596
10597 let buffer = project.update(cx, |project, cx| {
10598 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10599 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10600 });
10601 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10602 let follower = cx.update(|cx| {
10603 cx.open_window(
10604 WindowOptions {
10605 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10606 gpui::Point::new(px(0.), px(0.)),
10607 gpui::Point::new(px(10.), px(80.)),
10608 ))),
10609 ..Default::default()
10610 },
10611 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10612 )
10613 .unwrap()
10614 });
10615
10616 let is_still_following = Rc::new(RefCell::new(true));
10617 let follower_edit_event_count = Rc::new(RefCell::new(0));
10618 let pending_update = Rc::new(RefCell::new(None));
10619 let leader_entity = leader.root(cx).unwrap();
10620 let follower_entity = follower.root(cx).unwrap();
10621 _ = follower.update(cx, {
10622 let update = pending_update.clone();
10623 let is_still_following = is_still_following.clone();
10624 let follower_edit_event_count = follower_edit_event_count.clone();
10625 |_, window, cx| {
10626 cx.subscribe_in(
10627 &leader_entity,
10628 window,
10629 move |_, leader, event, window, cx| {
10630 leader.read(cx).add_event_to_update_proto(
10631 event,
10632 &mut update.borrow_mut(),
10633 window,
10634 cx,
10635 );
10636 },
10637 )
10638 .detach();
10639
10640 cx.subscribe_in(
10641 &follower_entity,
10642 window,
10643 move |_, _, event: &EditorEvent, _window, _cx| {
10644 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10645 *is_still_following.borrow_mut() = false;
10646 }
10647
10648 if let EditorEvent::BufferEdited = event {
10649 *follower_edit_event_count.borrow_mut() += 1;
10650 }
10651 },
10652 )
10653 .detach();
10654 }
10655 });
10656
10657 // Update the selections only
10658 _ = leader.update(cx, |leader, window, cx| {
10659 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10660 });
10661 follower
10662 .update(cx, |follower, window, cx| {
10663 follower.apply_update_proto(
10664 &project,
10665 pending_update.borrow_mut().take().unwrap(),
10666 window,
10667 cx,
10668 )
10669 })
10670 .unwrap()
10671 .await
10672 .unwrap();
10673 _ = follower.update(cx, |follower, _, cx| {
10674 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10675 });
10676 assert!(*is_still_following.borrow());
10677 assert_eq!(*follower_edit_event_count.borrow(), 0);
10678
10679 // Update the scroll position only
10680 _ = leader.update(cx, |leader, window, cx| {
10681 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10682 });
10683 follower
10684 .update(cx, |follower, window, cx| {
10685 follower.apply_update_proto(
10686 &project,
10687 pending_update.borrow_mut().take().unwrap(),
10688 window,
10689 cx,
10690 )
10691 })
10692 .unwrap()
10693 .await
10694 .unwrap();
10695 assert_eq!(
10696 follower
10697 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10698 .unwrap(),
10699 gpui::Point::new(1.5, 3.5)
10700 );
10701 assert!(*is_still_following.borrow());
10702 assert_eq!(*follower_edit_event_count.borrow(), 0);
10703
10704 // Update the selections and scroll position. The follower's scroll position is updated
10705 // via autoscroll, not via the leader's exact scroll position.
10706 _ = leader.update(cx, |leader, window, cx| {
10707 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10708 leader.request_autoscroll(Autoscroll::newest(), cx);
10709 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10710 });
10711 follower
10712 .update(cx, |follower, window, cx| {
10713 follower.apply_update_proto(
10714 &project,
10715 pending_update.borrow_mut().take().unwrap(),
10716 window,
10717 cx,
10718 )
10719 })
10720 .unwrap()
10721 .await
10722 .unwrap();
10723 _ = follower.update(cx, |follower, _, cx| {
10724 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10725 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10726 });
10727 assert!(*is_still_following.borrow());
10728
10729 // Creating a pending selection that precedes another selection
10730 _ = leader.update(cx, |leader, window, cx| {
10731 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10732 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10733 });
10734 follower
10735 .update(cx, |follower, window, cx| {
10736 follower.apply_update_proto(
10737 &project,
10738 pending_update.borrow_mut().take().unwrap(),
10739 window,
10740 cx,
10741 )
10742 })
10743 .unwrap()
10744 .await
10745 .unwrap();
10746 _ = follower.update(cx, |follower, _, cx| {
10747 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10748 });
10749 assert!(*is_still_following.borrow());
10750
10751 // Extend the pending selection so that it surrounds another selection
10752 _ = leader.update(cx, |leader, window, cx| {
10753 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10754 });
10755 follower
10756 .update(cx, |follower, window, cx| {
10757 follower.apply_update_proto(
10758 &project,
10759 pending_update.borrow_mut().take().unwrap(),
10760 window,
10761 cx,
10762 )
10763 })
10764 .unwrap()
10765 .await
10766 .unwrap();
10767 _ = follower.update(cx, |follower, _, cx| {
10768 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10769 });
10770
10771 // Scrolling locally breaks the follow
10772 _ = follower.update(cx, |follower, window, cx| {
10773 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10774 follower.set_scroll_anchor(
10775 ScrollAnchor {
10776 anchor: top_anchor,
10777 offset: gpui::Point::new(0.0, 0.5),
10778 },
10779 window,
10780 cx,
10781 );
10782 });
10783 assert!(!(*is_still_following.borrow()));
10784}
10785
10786#[gpui::test]
10787async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10788 init_test(cx, |_| {});
10789
10790 let fs = FakeFs::new(cx.executor());
10791 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10792 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10793 let pane = workspace
10794 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10795 .unwrap();
10796
10797 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10798
10799 let leader = pane.update_in(cx, |_, window, cx| {
10800 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10801 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10802 });
10803
10804 // Start following the editor when it has no excerpts.
10805 let mut state_message =
10806 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10807 let workspace_entity = workspace.root(cx).unwrap();
10808 let follower_1 = cx
10809 .update_window(*workspace.deref(), |_, window, cx| {
10810 Editor::from_state_proto(
10811 workspace_entity,
10812 ViewId {
10813 creator: Default::default(),
10814 id: 0,
10815 },
10816 &mut state_message,
10817 window,
10818 cx,
10819 )
10820 })
10821 .unwrap()
10822 .unwrap()
10823 .await
10824 .unwrap();
10825
10826 let update_message = Rc::new(RefCell::new(None));
10827 follower_1.update_in(cx, {
10828 let update = update_message.clone();
10829 |_, window, cx| {
10830 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10831 leader.read(cx).add_event_to_update_proto(
10832 event,
10833 &mut update.borrow_mut(),
10834 window,
10835 cx,
10836 );
10837 })
10838 .detach();
10839 }
10840 });
10841
10842 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10843 (
10844 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10845 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10846 )
10847 });
10848
10849 // Insert some excerpts.
10850 leader.update(cx, |leader, cx| {
10851 leader.buffer.update(cx, |multibuffer, cx| {
10852 let excerpt_ids = multibuffer.push_excerpts(
10853 buffer_1.clone(),
10854 [
10855 ExcerptRange {
10856 context: 1..6,
10857 primary: None,
10858 },
10859 ExcerptRange {
10860 context: 12..15,
10861 primary: None,
10862 },
10863 ExcerptRange {
10864 context: 0..3,
10865 primary: None,
10866 },
10867 ],
10868 cx,
10869 );
10870 multibuffer.insert_excerpts_after(
10871 excerpt_ids[0],
10872 buffer_2.clone(),
10873 [
10874 ExcerptRange {
10875 context: 8..12,
10876 primary: None,
10877 },
10878 ExcerptRange {
10879 context: 0..6,
10880 primary: None,
10881 },
10882 ],
10883 cx,
10884 );
10885 });
10886 });
10887
10888 // Apply the update of adding the excerpts.
10889 follower_1
10890 .update_in(cx, |follower, window, cx| {
10891 follower.apply_update_proto(
10892 &project,
10893 update_message.borrow().clone().unwrap(),
10894 window,
10895 cx,
10896 )
10897 })
10898 .await
10899 .unwrap();
10900 assert_eq!(
10901 follower_1.update(cx, |editor, cx| editor.text(cx)),
10902 leader.update(cx, |editor, cx| editor.text(cx))
10903 );
10904 update_message.borrow_mut().take();
10905
10906 // Start following separately after it already has excerpts.
10907 let mut state_message =
10908 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10909 let workspace_entity = workspace.root(cx).unwrap();
10910 let follower_2 = cx
10911 .update_window(*workspace.deref(), |_, window, cx| {
10912 Editor::from_state_proto(
10913 workspace_entity,
10914 ViewId {
10915 creator: Default::default(),
10916 id: 0,
10917 },
10918 &mut state_message,
10919 window,
10920 cx,
10921 )
10922 })
10923 .unwrap()
10924 .unwrap()
10925 .await
10926 .unwrap();
10927 assert_eq!(
10928 follower_2.update(cx, |editor, cx| editor.text(cx)),
10929 leader.update(cx, |editor, cx| editor.text(cx))
10930 );
10931
10932 // Remove some excerpts.
10933 leader.update(cx, |leader, cx| {
10934 leader.buffer.update(cx, |multibuffer, cx| {
10935 let excerpt_ids = multibuffer.excerpt_ids();
10936 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10937 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10938 });
10939 });
10940
10941 // Apply the update of removing 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 follower_2
10954 .update_in(cx, |follower, window, cx| {
10955 follower.apply_update_proto(
10956 &project,
10957 update_message.borrow().clone().unwrap(),
10958 window,
10959 cx,
10960 )
10961 })
10962 .await
10963 .unwrap();
10964 update_message.borrow_mut().take();
10965 assert_eq!(
10966 follower_1.update(cx, |editor, cx| editor.text(cx)),
10967 leader.update(cx, |editor, cx| editor.text(cx))
10968 );
10969}
10970
10971#[gpui::test]
10972async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
10973 init_test(cx, |_| {});
10974
10975 let mut cx = EditorTestContext::new(cx).await;
10976 let lsp_store =
10977 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10978
10979 cx.set_state(indoc! {"
10980 ˇfn func(abc def: i32) -> u32 {
10981 }
10982 "});
10983
10984 cx.update(|_, cx| {
10985 lsp_store.update(cx, |lsp_store, cx| {
10986 lsp_store
10987 .update_diagnostics(
10988 LanguageServerId(0),
10989 lsp::PublishDiagnosticsParams {
10990 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
10991 version: None,
10992 diagnostics: vec![
10993 lsp::Diagnostic {
10994 range: lsp::Range::new(
10995 lsp::Position::new(0, 11),
10996 lsp::Position::new(0, 12),
10997 ),
10998 severity: Some(lsp::DiagnosticSeverity::ERROR),
10999 ..Default::default()
11000 },
11001 lsp::Diagnostic {
11002 range: lsp::Range::new(
11003 lsp::Position::new(0, 12),
11004 lsp::Position::new(0, 15),
11005 ),
11006 severity: Some(lsp::DiagnosticSeverity::ERROR),
11007 ..Default::default()
11008 },
11009 lsp::Diagnostic {
11010 range: lsp::Range::new(
11011 lsp::Position::new(0, 25),
11012 lsp::Position::new(0, 28),
11013 ),
11014 severity: Some(lsp::DiagnosticSeverity::ERROR),
11015 ..Default::default()
11016 },
11017 ],
11018 },
11019 &[],
11020 cx,
11021 )
11022 .unwrap()
11023 });
11024 });
11025
11026 executor.run_until_parked();
11027
11028 cx.update_editor(|editor, window, cx| {
11029 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11030 });
11031
11032 cx.assert_editor_state(indoc! {"
11033 fn func(abc def: i32) -> ˇu32 {
11034 }
11035 "});
11036
11037 cx.update_editor(|editor, window, cx| {
11038 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11039 });
11040
11041 cx.assert_editor_state(indoc! {"
11042 fn func(abc ˇdef: i32) -> u32 {
11043 }
11044 "});
11045
11046 cx.update_editor(|editor, window, cx| {
11047 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11048 });
11049
11050 cx.assert_editor_state(indoc! {"
11051 fn func(abcˇ def: i32) -> u32 {
11052 }
11053 "});
11054
11055 cx.update_editor(|editor, window, cx| {
11056 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11057 });
11058
11059 cx.assert_editor_state(indoc! {"
11060 fn func(abc def: i32) -> ˇu32 {
11061 }
11062 "});
11063}
11064
11065#[gpui::test]
11066async fn cycle_through_same_place_diagnostics(
11067 executor: BackgroundExecutor,
11068 cx: &mut TestAppContext,
11069) {
11070 init_test(cx, |_| {});
11071
11072 let mut cx = EditorTestContext::new(cx).await;
11073 let lsp_store =
11074 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11075
11076 cx.set_state(indoc! {"
11077 ˇfn func(abc def: i32) -> u32 {
11078 }
11079 "});
11080
11081 cx.update(|_, cx| {
11082 lsp_store.update(cx, |lsp_store, cx| {
11083 lsp_store
11084 .update_diagnostics(
11085 LanguageServerId(0),
11086 lsp::PublishDiagnosticsParams {
11087 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11088 version: None,
11089 diagnostics: vec![
11090 lsp::Diagnostic {
11091 range: lsp::Range::new(
11092 lsp::Position::new(0, 11),
11093 lsp::Position::new(0, 12),
11094 ),
11095 severity: Some(lsp::DiagnosticSeverity::ERROR),
11096 ..Default::default()
11097 },
11098 lsp::Diagnostic {
11099 range: lsp::Range::new(
11100 lsp::Position::new(0, 12),
11101 lsp::Position::new(0, 15),
11102 ),
11103 severity: Some(lsp::DiagnosticSeverity::ERROR),
11104 ..Default::default()
11105 },
11106 lsp::Diagnostic {
11107 range: lsp::Range::new(
11108 lsp::Position::new(0, 12),
11109 lsp::Position::new(0, 15),
11110 ),
11111 severity: Some(lsp::DiagnosticSeverity::ERROR),
11112 ..Default::default()
11113 },
11114 lsp::Diagnostic {
11115 range: lsp::Range::new(
11116 lsp::Position::new(0, 25),
11117 lsp::Position::new(0, 28),
11118 ),
11119 severity: Some(lsp::DiagnosticSeverity::ERROR),
11120 ..Default::default()
11121 },
11122 ],
11123 },
11124 &[],
11125 cx,
11126 )
11127 .unwrap()
11128 });
11129 });
11130 executor.run_until_parked();
11131
11132 //// Backward
11133
11134 // Fourth diagnostic
11135 cx.update_editor(|editor, window, cx| {
11136 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11137 });
11138 cx.assert_editor_state(indoc! {"
11139 fn func(abc def: i32) -> ˇu32 {
11140 }
11141 "});
11142
11143 // Third diagnostic
11144 cx.update_editor(|editor, window, cx| {
11145 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11146 });
11147 cx.assert_editor_state(indoc! {"
11148 fn func(abc ˇdef: i32) -> u32 {
11149 }
11150 "});
11151
11152 // Second diagnostic, same place
11153 cx.update_editor(|editor, window, cx| {
11154 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11155 });
11156 cx.assert_editor_state(indoc! {"
11157 fn func(abc ˇdef: i32) -> u32 {
11158 }
11159 "});
11160
11161 // First diagnostic
11162 cx.update_editor(|editor, window, cx| {
11163 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11164 });
11165 cx.assert_editor_state(indoc! {"
11166 fn func(abcˇ def: i32) -> u32 {
11167 }
11168 "});
11169
11170 // Wrapped over, fourth diagnostic
11171 cx.update_editor(|editor, window, cx| {
11172 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11173 });
11174 cx.assert_editor_state(indoc! {"
11175 fn func(abc def: i32) -> ˇu32 {
11176 }
11177 "});
11178
11179 cx.update_editor(|editor, window, cx| {
11180 editor.move_to_beginning(&MoveToBeginning, window, cx);
11181 });
11182 cx.assert_editor_state(indoc! {"
11183 ˇfn func(abc def: i32) -> u32 {
11184 }
11185 "});
11186
11187 //// Forward
11188
11189 // First diagnostic
11190 cx.update_editor(|editor, window, cx| {
11191 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11192 });
11193 cx.assert_editor_state(indoc! {"
11194 fn func(abcˇ def: i32) -> u32 {
11195 }
11196 "});
11197
11198 // Second diagnostic
11199 cx.update_editor(|editor, window, cx| {
11200 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11201 });
11202 cx.assert_editor_state(indoc! {"
11203 fn func(abc ˇdef: i32) -> u32 {
11204 }
11205 "});
11206
11207 // Third diagnostic, same place
11208 cx.update_editor(|editor, window, cx| {
11209 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11210 });
11211 cx.assert_editor_state(indoc! {"
11212 fn func(abc ˇdef: i32) -> u32 {
11213 }
11214 "});
11215
11216 // Fourth diagnostic
11217 cx.update_editor(|editor, window, cx| {
11218 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11219 });
11220 cx.assert_editor_state(indoc! {"
11221 fn func(abc def: i32) -> ˇu32 {
11222 }
11223 "});
11224
11225 // Wrapped around, first diagnostic
11226 cx.update_editor(|editor, window, cx| {
11227 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11228 });
11229 cx.assert_editor_state(indoc! {"
11230 fn func(abcˇ def: i32) -> u32 {
11231 }
11232 "});
11233}
11234
11235#[gpui::test]
11236async fn active_diagnostics_dismiss_after_invalidation(
11237 executor: BackgroundExecutor,
11238 cx: &mut TestAppContext,
11239) {
11240 init_test(cx, |_| {});
11241
11242 let mut cx = EditorTestContext::new(cx).await;
11243 let lsp_store =
11244 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11245
11246 cx.set_state(indoc! {"
11247 ˇfn func(abc def: i32) -> u32 {
11248 }
11249 "});
11250
11251 let message = "Something's wrong!";
11252 cx.update(|_, cx| {
11253 lsp_store.update(cx, |lsp_store, cx| {
11254 lsp_store
11255 .update_diagnostics(
11256 LanguageServerId(0),
11257 lsp::PublishDiagnosticsParams {
11258 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11259 version: None,
11260 diagnostics: vec![lsp::Diagnostic {
11261 range: lsp::Range::new(
11262 lsp::Position::new(0, 11),
11263 lsp::Position::new(0, 12),
11264 ),
11265 severity: Some(lsp::DiagnosticSeverity::ERROR),
11266 message: message.to_string(),
11267 ..Default::default()
11268 }],
11269 },
11270 &[],
11271 cx,
11272 )
11273 .unwrap()
11274 });
11275 });
11276 executor.run_until_parked();
11277
11278 cx.update_editor(|editor, window, cx| {
11279 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11280 assert_eq!(
11281 editor
11282 .active_diagnostics
11283 .as_ref()
11284 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11285 Some(message),
11286 "Should have a diagnostics group activated"
11287 );
11288 });
11289 cx.assert_editor_state(indoc! {"
11290 fn func(abcˇ def: i32) -> u32 {
11291 }
11292 "});
11293
11294 cx.update(|_, cx| {
11295 lsp_store.update(cx, |lsp_store, cx| {
11296 lsp_store
11297 .update_diagnostics(
11298 LanguageServerId(0),
11299 lsp::PublishDiagnosticsParams {
11300 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11301 version: None,
11302 diagnostics: Vec::new(),
11303 },
11304 &[],
11305 cx,
11306 )
11307 .unwrap()
11308 });
11309 });
11310 executor.run_until_parked();
11311 cx.update_editor(|editor, _, _| {
11312 assert_eq!(
11313 editor.active_diagnostics, None,
11314 "After no diagnostics set to the editor, no diagnostics should be active"
11315 );
11316 });
11317 cx.assert_editor_state(indoc! {"
11318 fn func(abcˇ def: i32) -> u32 {
11319 }
11320 "});
11321
11322 cx.update_editor(|editor, window, cx| {
11323 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11324 assert_eq!(
11325 editor.active_diagnostics, None,
11326 "Should be no diagnostics to go to and activate"
11327 );
11328 });
11329 cx.assert_editor_state(indoc! {"
11330 fn func(abcˇ def: i32) -> u32 {
11331 }
11332 "});
11333}
11334
11335#[gpui::test]
11336async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11337 init_test(cx, |_| {});
11338
11339 let mut cx = EditorTestContext::new(cx).await;
11340
11341 cx.set_state(indoc! {"
11342 fn func(abˇc def: i32) -> u32 {
11343 }
11344 "});
11345 let lsp_store =
11346 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11347
11348 cx.update(|_, cx| {
11349 lsp_store.update(cx, |lsp_store, cx| {
11350 lsp_store.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![lsp::Diagnostic {
11356 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11357 severity: Some(lsp::DiagnosticSeverity::ERROR),
11358 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11359 ..Default::default()
11360 }],
11361 },
11362 &[],
11363 cx,
11364 )
11365 })
11366 }).unwrap();
11367 cx.run_until_parked();
11368 cx.update_editor(|editor, window, cx| {
11369 hover_popover::hover(editor, &Default::default(), window, cx)
11370 });
11371 cx.run_until_parked();
11372 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11373}
11374
11375#[gpui::test]
11376async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11377 init_test(cx, |_| {});
11378
11379 let mut cx = EditorTestContext::new(cx).await;
11380
11381 let diff_base = r#"
11382 use some::mod;
11383
11384 const A: u32 = 42;
11385
11386 fn main() {
11387 println!("hello");
11388
11389 println!("world");
11390 }
11391 "#
11392 .unindent();
11393
11394 // Edits are modified, removed, modified, added
11395 cx.set_state(
11396 &r#"
11397 use some::modified;
11398
11399 ˇ
11400 fn main() {
11401 println!("hello there");
11402
11403 println!("around the");
11404 println!("world");
11405 }
11406 "#
11407 .unindent(),
11408 );
11409
11410 cx.set_head_text(&diff_base);
11411 executor.run_until_parked();
11412
11413 cx.update_editor(|editor, window, cx| {
11414 //Wrap around the bottom of the buffer
11415 for _ in 0..3 {
11416 editor.go_to_next_hunk(&GoToHunk, window, cx);
11417 }
11418 });
11419
11420 cx.assert_editor_state(
11421 &r#"
11422 ˇuse some::modified;
11423
11424
11425 fn main() {
11426 println!("hello there");
11427
11428 println!("around the");
11429 println!("world");
11430 }
11431 "#
11432 .unindent(),
11433 );
11434
11435 cx.update_editor(|editor, window, cx| {
11436 //Wrap around the top of the buffer
11437 for _ in 0..2 {
11438 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11439 }
11440 });
11441
11442 cx.assert_editor_state(
11443 &r#"
11444 use some::modified;
11445
11446
11447 fn main() {
11448 ˇ println!("hello there");
11449
11450 println!("around the");
11451 println!("world");
11452 }
11453 "#
11454 .unindent(),
11455 );
11456
11457 cx.update_editor(|editor, window, cx| {
11458 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11459 });
11460
11461 cx.assert_editor_state(
11462 &r#"
11463 use some::modified;
11464
11465 ˇ
11466 fn main() {
11467 println!("hello there");
11468
11469 println!("around the");
11470 println!("world");
11471 }
11472 "#
11473 .unindent(),
11474 );
11475
11476 cx.update_editor(|editor, window, cx| {
11477 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11478 });
11479
11480 cx.assert_editor_state(
11481 &r#"
11482 ˇuse some::modified;
11483
11484
11485 fn main() {
11486 println!("hello there");
11487
11488 println!("around the");
11489 println!("world");
11490 }
11491 "#
11492 .unindent(),
11493 );
11494
11495 cx.update_editor(|editor, window, cx| {
11496 for _ in 0..2 {
11497 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11498 }
11499 });
11500
11501 cx.assert_editor_state(
11502 &r#"
11503 use some::modified;
11504
11505
11506 fn main() {
11507 ˇ println!("hello there");
11508
11509 println!("around the");
11510 println!("world");
11511 }
11512 "#
11513 .unindent(),
11514 );
11515
11516 cx.update_editor(|editor, window, cx| {
11517 editor.fold(&Fold, window, cx);
11518 });
11519
11520 cx.update_editor(|editor, window, cx| {
11521 editor.go_to_next_hunk(&GoToHunk, window, cx);
11522 });
11523
11524 cx.assert_editor_state(
11525 &r#"
11526 ˇuse some::modified;
11527
11528
11529 fn main() {
11530 println!("hello there");
11531
11532 println!("around the");
11533 println!("world");
11534 }
11535 "#
11536 .unindent(),
11537 );
11538}
11539
11540#[test]
11541fn test_split_words() {
11542 fn split(text: &str) -> Vec<&str> {
11543 split_words(text).collect()
11544 }
11545
11546 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11547 assert_eq!(split("hello_world"), &["hello_", "world"]);
11548 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11549 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11550 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11551 assert_eq!(split("helloworld"), &["helloworld"]);
11552
11553 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11554}
11555
11556#[gpui::test]
11557async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11558 init_test(cx, |_| {});
11559
11560 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11561 let mut assert = |before, after| {
11562 let _state_context = cx.set_state(before);
11563 cx.run_until_parked();
11564 cx.update_editor(|editor, window, cx| {
11565 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11566 });
11567 cx.assert_editor_state(after);
11568 };
11569
11570 // Outside bracket jumps to outside of matching bracket
11571 assert("console.logˇ(var);", "console.log(var)ˇ;");
11572 assert("console.log(var)ˇ;", "console.logˇ(var);");
11573
11574 // Inside bracket jumps to inside of matching bracket
11575 assert("console.log(ˇvar);", "console.log(varˇ);");
11576 assert("console.log(varˇ);", "console.log(ˇvar);");
11577
11578 // When outside a bracket and inside, favor jumping to the inside bracket
11579 assert(
11580 "console.log('foo', [1, 2, 3]ˇ);",
11581 "console.log(ˇ'foo', [1, 2, 3]);",
11582 );
11583 assert(
11584 "console.log(ˇ'foo', [1, 2, 3]);",
11585 "console.log('foo', [1, 2, 3]ˇ);",
11586 );
11587
11588 // Bias forward if two options are equally likely
11589 assert(
11590 "let result = curried_fun()ˇ();",
11591 "let result = curried_fun()()ˇ;",
11592 );
11593
11594 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11595 assert(
11596 indoc! {"
11597 function test() {
11598 console.log('test')ˇ
11599 }"},
11600 indoc! {"
11601 function test() {
11602 console.logˇ('test')
11603 }"},
11604 );
11605}
11606
11607#[gpui::test]
11608async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11609 init_test(cx, |_| {});
11610
11611 let fs = FakeFs::new(cx.executor());
11612 fs.insert_tree(
11613 path!("/a"),
11614 json!({
11615 "main.rs": "fn main() { let a = 5; }",
11616 "other.rs": "// Test file",
11617 }),
11618 )
11619 .await;
11620 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11621
11622 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11623 language_registry.add(Arc::new(Language::new(
11624 LanguageConfig {
11625 name: "Rust".into(),
11626 matcher: LanguageMatcher {
11627 path_suffixes: vec!["rs".to_string()],
11628 ..Default::default()
11629 },
11630 brackets: BracketPairConfig {
11631 pairs: vec![BracketPair {
11632 start: "{".to_string(),
11633 end: "}".to_string(),
11634 close: true,
11635 surround: true,
11636 newline: true,
11637 }],
11638 disabled_scopes_by_bracket_ix: Vec::new(),
11639 },
11640 ..Default::default()
11641 },
11642 Some(tree_sitter_rust::LANGUAGE.into()),
11643 )));
11644 let mut fake_servers = language_registry.register_fake_lsp(
11645 "Rust",
11646 FakeLspAdapter {
11647 capabilities: lsp::ServerCapabilities {
11648 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11649 first_trigger_character: "{".to_string(),
11650 more_trigger_character: None,
11651 }),
11652 ..Default::default()
11653 },
11654 ..Default::default()
11655 },
11656 );
11657
11658 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11659
11660 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11661
11662 let worktree_id = workspace
11663 .update(cx, |workspace, _, cx| {
11664 workspace.project().update(cx, |project, cx| {
11665 project.worktrees(cx).next().unwrap().read(cx).id()
11666 })
11667 })
11668 .unwrap();
11669
11670 let buffer = project
11671 .update(cx, |project, cx| {
11672 project.open_local_buffer(path!("/a/main.rs"), cx)
11673 })
11674 .await
11675 .unwrap();
11676 let editor_handle = workspace
11677 .update(cx, |workspace, window, cx| {
11678 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11679 })
11680 .unwrap()
11681 .await
11682 .unwrap()
11683 .downcast::<Editor>()
11684 .unwrap();
11685
11686 cx.executor().start_waiting();
11687 let fake_server = fake_servers.next().await.unwrap();
11688
11689 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11690 assert_eq!(
11691 params.text_document_position.text_document.uri,
11692 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11693 );
11694 assert_eq!(
11695 params.text_document_position.position,
11696 lsp::Position::new(0, 21),
11697 );
11698
11699 Ok(Some(vec![lsp::TextEdit {
11700 new_text: "]".to_string(),
11701 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11702 }]))
11703 });
11704
11705 editor_handle.update_in(cx, |editor, window, cx| {
11706 window.focus(&editor.focus_handle(cx));
11707 editor.change_selections(None, window, cx, |s| {
11708 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11709 });
11710 editor.handle_input("{", window, cx);
11711 });
11712
11713 cx.executor().run_until_parked();
11714
11715 buffer.update(cx, |buffer, _| {
11716 assert_eq!(
11717 buffer.text(),
11718 "fn main() { let a = {5}; }",
11719 "No extra braces from on type formatting should appear in the buffer"
11720 )
11721 });
11722}
11723
11724#[gpui::test]
11725async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11726 init_test(cx, |_| {});
11727
11728 let fs = FakeFs::new(cx.executor());
11729 fs.insert_tree(
11730 path!("/a"),
11731 json!({
11732 "main.rs": "fn main() { let a = 5; }",
11733 "other.rs": "// Test file",
11734 }),
11735 )
11736 .await;
11737
11738 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11739
11740 let server_restarts = Arc::new(AtomicUsize::new(0));
11741 let closure_restarts = Arc::clone(&server_restarts);
11742 let language_server_name = "test language server";
11743 let language_name: LanguageName = "Rust".into();
11744
11745 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11746 language_registry.add(Arc::new(Language::new(
11747 LanguageConfig {
11748 name: language_name.clone(),
11749 matcher: LanguageMatcher {
11750 path_suffixes: vec!["rs".to_string()],
11751 ..Default::default()
11752 },
11753 ..Default::default()
11754 },
11755 Some(tree_sitter_rust::LANGUAGE.into()),
11756 )));
11757 let mut fake_servers = language_registry.register_fake_lsp(
11758 "Rust",
11759 FakeLspAdapter {
11760 name: language_server_name,
11761 initialization_options: Some(json!({
11762 "testOptionValue": true
11763 })),
11764 initializer: Some(Box::new(move |fake_server| {
11765 let task_restarts = Arc::clone(&closure_restarts);
11766 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11767 task_restarts.fetch_add(1, atomic::Ordering::Release);
11768 futures::future::ready(Ok(()))
11769 });
11770 })),
11771 ..Default::default()
11772 },
11773 );
11774
11775 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11776 let _buffer = project
11777 .update(cx, |project, cx| {
11778 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11779 })
11780 .await
11781 .unwrap();
11782 let _fake_server = fake_servers.next().await.unwrap();
11783 update_test_language_settings(cx, |language_settings| {
11784 language_settings.languages.insert(
11785 language_name.clone(),
11786 LanguageSettingsContent {
11787 tab_size: NonZeroU32::new(8),
11788 ..Default::default()
11789 },
11790 );
11791 });
11792 cx.executor().run_until_parked();
11793 assert_eq!(
11794 server_restarts.load(atomic::Ordering::Acquire),
11795 0,
11796 "Should not restart LSP server on an unrelated change"
11797 );
11798
11799 update_test_project_settings(cx, |project_settings| {
11800 project_settings.lsp.insert(
11801 "Some other server name".into(),
11802 LspSettings {
11803 binary: None,
11804 settings: None,
11805 initialization_options: Some(json!({
11806 "some other init value": false
11807 })),
11808 },
11809 );
11810 });
11811 cx.executor().run_until_parked();
11812 assert_eq!(
11813 server_restarts.load(atomic::Ordering::Acquire),
11814 0,
11815 "Should not restart LSP server on an unrelated LSP settings change"
11816 );
11817
11818 update_test_project_settings(cx, |project_settings| {
11819 project_settings.lsp.insert(
11820 language_server_name.into(),
11821 LspSettings {
11822 binary: None,
11823 settings: None,
11824 initialization_options: Some(json!({
11825 "anotherInitValue": false
11826 })),
11827 },
11828 );
11829 });
11830 cx.executor().run_until_parked();
11831 assert_eq!(
11832 server_restarts.load(atomic::Ordering::Acquire),
11833 1,
11834 "Should restart LSP server on a related LSP settings change"
11835 );
11836
11837 update_test_project_settings(cx, |project_settings| {
11838 project_settings.lsp.insert(
11839 language_server_name.into(),
11840 LspSettings {
11841 binary: None,
11842 settings: None,
11843 initialization_options: Some(json!({
11844 "anotherInitValue": false
11845 })),
11846 },
11847 );
11848 });
11849 cx.executor().run_until_parked();
11850 assert_eq!(
11851 server_restarts.load(atomic::Ordering::Acquire),
11852 1,
11853 "Should not restart LSP server on a related LSP settings change that is the same"
11854 );
11855
11856 update_test_project_settings(cx, |project_settings| {
11857 project_settings.lsp.insert(
11858 language_server_name.into(),
11859 LspSettings {
11860 binary: None,
11861 settings: None,
11862 initialization_options: None,
11863 },
11864 );
11865 });
11866 cx.executor().run_until_parked();
11867 assert_eq!(
11868 server_restarts.load(atomic::Ordering::Acquire),
11869 2,
11870 "Should restart LSP server on another related LSP settings change"
11871 );
11872}
11873
11874#[gpui::test]
11875async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11876 init_test(cx, |_| {});
11877
11878 let mut cx = EditorLspTestContext::new_rust(
11879 lsp::ServerCapabilities {
11880 completion_provider: Some(lsp::CompletionOptions {
11881 trigger_characters: Some(vec![".".to_string()]),
11882 resolve_provider: Some(true),
11883 ..Default::default()
11884 }),
11885 ..Default::default()
11886 },
11887 cx,
11888 )
11889 .await;
11890
11891 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11892 cx.simulate_keystroke(".");
11893 let completion_item = lsp::CompletionItem {
11894 label: "some".into(),
11895 kind: Some(lsp::CompletionItemKind::SNIPPET),
11896 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11897 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11898 kind: lsp::MarkupKind::Markdown,
11899 value: "```rust\nSome(2)\n```".to_string(),
11900 })),
11901 deprecated: Some(false),
11902 sort_text: Some("fffffff2".to_string()),
11903 filter_text: Some("some".to_string()),
11904 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11905 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11906 range: lsp::Range {
11907 start: lsp::Position {
11908 line: 0,
11909 character: 22,
11910 },
11911 end: lsp::Position {
11912 line: 0,
11913 character: 22,
11914 },
11915 },
11916 new_text: "Some(2)".to_string(),
11917 })),
11918 additional_text_edits: Some(vec![lsp::TextEdit {
11919 range: lsp::Range {
11920 start: lsp::Position {
11921 line: 0,
11922 character: 20,
11923 },
11924 end: lsp::Position {
11925 line: 0,
11926 character: 22,
11927 },
11928 },
11929 new_text: "".to_string(),
11930 }]),
11931 ..Default::default()
11932 };
11933
11934 let closure_completion_item = completion_item.clone();
11935 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11936 let task_completion_item = closure_completion_item.clone();
11937 async move {
11938 Ok(Some(lsp::CompletionResponse::Array(vec![
11939 task_completion_item,
11940 ])))
11941 }
11942 });
11943
11944 request.next().await;
11945
11946 cx.condition(|editor, _| editor.context_menu_visible())
11947 .await;
11948 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11949 editor
11950 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11951 .unwrap()
11952 });
11953 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11954
11955 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11956 let task_completion_item = completion_item.clone();
11957 async move { Ok(task_completion_item) }
11958 })
11959 .next()
11960 .await
11961 .unwrap();
11962 apply_additional_edits.await.unwrap();
11963 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11964}
11965
11966#[gpui::test]
11967async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11968 init_test(cx, |_| {});
11969
11970 let mut cx = EditorLspTestContext::new_rust(
11971 lsp::ServerCapabilities {
11972 completion_provider: Some(lsp::CompletionOptions {
11973 trigger_characters: Some(vec![".".to_string()]),
11974 resolve_provider: Some(true),
11975 ..Default::default()
11976 }),
11977 ..Default::default()
11978 },
11979 cx,
11980 )
11981 .await;
11982
11983 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11984 cx.simulate_keystroke(".");
11985
11986 let item1 = lsp::CompletionItem {
11987 label: "method id()".to_string(),
11988 filter_text: Some("id".to_string()),
11989 detail: None,
11990 documentation: None,
11991 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11992 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11993 new_text: ".id".to_string(),
11994 })),
11995 ..lsp::CompletionItem::default()
11996 };
11997
11998 let item2 = lsp::CompletionItem {
11999 label: "other".to_string(),
12000 filter_text: Some("other".to_string()),
12001 detail: None,
12002 documentation: None,
12003 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12004 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12005 new_text: ".other".to_string(),
12006 })),
12007 ..lsp::CompletionItem::default()
12008 };
12009
12010 let item1 = item1.clone();
12011 cx.handle_request::<lsp::request::Completion, _, _>({
12012 let item1 = item1.clone();
12013 move |_, _, _| {
12014 let item1 = item1.clone();
12015 let item2 = item2.clone();
12016 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12017 }
12018 })
12019 .next()
12020 .await;
12021
12022 cx.condition(|editor, _| editor.context_menu_visible())
12023 .await;
12024 cx.update_editor(|editor, _, _| {
12025 let context_menu = editor.context_menu.borrow_mut();
12026 let context_menu = context_menu
12027 .as_ref()
12028 .expect("Should have the context menu deployed");
12029 match context_menu {
12030 CodeContextMenu::Completions(completions_menu) => {
12031 let completions = completions_menu.completions.borrow_mut();
12032 assert_eq!(
12033 completions
12034 .iter()
12035 .map(|completion| &completion.label.text)
12036 .collect::<Vec<_>>(),
12037 vec!["method id()", "other"]
12038 )
12039 }
12040 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12041 }
12042 });
12043
12044 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12045 let item1 = item1.clone();
12046 move |_, item_to_resolve, _| {
12047 let item1 = item1.clone();
12048 async move {
12049 if item1 == item_to_resolve {
12050 Ok(lsp::CompletionItem {
12051 label: "method id()".to_string(),
12052 filter_text: Some("id".to_string()),
12053 detail: Some("Now resolved!".to_string()),
12054 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12055 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12056 range: lsp::Range::new(
12057 lsp::Position::new(0, 22),
12058 lsp::Position::new(0, 22),
12059 ),
12060 new_text: ".id".to_string(),
12061 })),
12062 ..lsp::CompletionItem::default()
12063 })
12064 } else {
12065 Ok(item_to_resolve)
12066 }
12067 }
12068 }
12069 })
12070 .next()
12071 .await
12072 .unwrap();
12073 cx.run_until_parked();
12074
12075 cx.update_editor(|editor, window, cx| {
12076 editor.context_menu_next(&Default::default(), window, cx);
12077 });
12078
12079 cx.update_editor(|editor, _, _| {
12080 let context_menu = editor.context_menu.borrow_mut();
12081 let context_menu = context_menu
12082 .as_ref()
12083 .expect("Should have the context menu deployed");
12084 match context_menu {
12085 CodeContextMenu::Completions(completions_menu) => {
12086 let completions = completions_menu.completions.borrow_mut();
12087 assert_eq!(
12088 completions
12089 .iter()
12090 .map(|completion| &completion.label.text)
12091 .collect::<Vec<_>>(),
12092 vec!["method id() Now resolved!", "other"],
12093 "Should update first completion label, but not second as the filter text did not match."
12094 );
12095 }
12096 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12097 }
12098 });
12099}
12100
12101#[gpui::test]
12102async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12103 init_test(cx, |_| {});
12104
12105 let mut cx = EditorLspTestContext::new_rust(
12106 lsp::ServerCapabilities {
12107 completion_provider: Some(lsp::CompletionOptions {
12108 trigger_characters: Some(vec![".".to_string()]),
12109 resolve_provider: Some(true),
12110 ..Default::default()
12111 }),
12112 ..Default::default()
12113 },
12114 cx,
12115 )
12116 .await;
12117
12118 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12119 cx.simulate_keystroke(".");
12120
12121 let unresolved_item_1 = lsp::CompletionItem {
12122 label: "id".to_string(),
12123 filter_text: Some("id".to_string()),
12124 detail: None,
12125 documentation: None,
12126 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12127 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12128 new_text: ".id".to_string(),
12129 })),
12130 ..lsp::CompletionItem::default()
12131 };
12132 let resolved_item_1 = lsp::CompletionItem {
12133 additional_text_edits: Some(vec![lsp::TextEdit {
12134 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12135 new_text: "!!".to_string(),
12136 }]),
12137 ..unresolved_item_1.clone()
12138 };
12139 let unresolved_item_2 = lsp::CompletionItem {
12140 label: "other".to_string(),
12141 filter_text: Some("other".to_string()),
12142 detail: None,
12143 documentation: None,
12144 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12145 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12146 new_text: ".other".to_string(),
12147 })),
12148 ..lsp::CompletionItem::default()
12149 };
12150 let resolved_item_2 = lsp::CompletionItem {
12151 additional_text_edits: Some(vec![lsp::TextEdit {
12152 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12153 new_text: "??".to_string(),
12154 }]),
12155 ..unresolved_item_2.clone()
12156 };
12157
12158 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12159 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12160 cx.lsp
12161 .server
12162 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12163 let unresolved_item_1 = unresolved_item_1.clone();
12164 let resolved_item_1 = resolved_item_1.clone();
12165 let unresolved_item_2 = unresolved_item_2.clone();
12166 let resolved_item_2 = resolved_item_2.clone();
12167 let resolve_requests_1 = resolve_requests_1.clone();
12168 let resolve_requests_2 = resolve_requests_2.clone();
12169 move |unresolved_request, _| {
12170 let unresolved_item_1 = unresolved_item_1.clone();
12171 let resolved_item_1 = resolved_item_1.clone();
12172 let unresolved_item_2 = unresolved_item_2.clone();
12173 let resolved_item_2 = resolved_item_2.clone();
12174 let resolve_requests_1 = resolve_requests_1.clone();
12175 let resolve_requests_2 = resolve_requests_2.clone();
12176 async move {
12177 if unresolved_request == unresolved_item_1 {
12178 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12179 Ok(resolved_item_1.clone())
12180 } else if unresolved_request == unresolved_item_2 {
12181 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12182 Ok(resolved_item_2.clone())
12183 } else {
12184 panic!("Unexpected completion item {unresolved_request:?}")
12185 }
12186 }
12187 }
12188 })
12189 .detach();
12190
12191 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12192 let unresolved_item_1 = unresolved_item_1.clone();
12193 let unresolved_item_2 = unresolved_item_2.clone();
12194 async move {
12195 Ok(Some(lsp::CompletionResponse::Array(vec![
12196 unresolved_item_1,
12197 unresolved_item_2,
12198 ])))
12199 }
12200 })
12201 .next()
12202 .await;
12203
12204 cx.condition(|editor, _| editor.context_menu_visible())
12205 .await;
12206 cx.update_editor(|editor, _, _| {
12207 let context_menu = editor.context_menu.borrow_mut();
12208 let context_menu = context_menu
12209 .as_ref()
12210 .expect("Should have the context menu deployed");
12211 match context_menu {
12212 CodeContextMenu::Completions(completions_menu) => {
12213 let completions = completions_menu.completions.borrow_mut();
12214 assert_eq!(
12215 completions
12216 .iter()
12217 .map(|completion| &completion.label.text)
12218 .collect::<Vec<_>>(),
12219 vec!["id", "other"]
12220 )
12221 }
12222 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12223 }
12224 });
12225 cx.run_until_parked();
12226
12227 cx.update_editor(|editor, window, cx| {
12228 editor.context_menu_next(&ContextMenuNext, window, cx);
12229 });
12230 cx.run_until_parked();
12231 cx.update_editor(|editor, window, cx| {
12232 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12233 });
12234 cx.run_until_parked();
12235 cx.update_editor(|editor, window, cx| {
12236 editor.context_menu_next(&ContextMenuNext, window, cx);
12237 });
12238 cx.run_until_parked();
12239 cx.update_editor(|editor, window, cx| {
12240 editor
12241 .compose_completion(&ComposeCompletion::default(), window, cx)
12242 .expect("No task returned")
12243 })
12244 .await
12245 .expect("Completion failed");
12246 cx.run_until_parked();
12247
12248 cx.update_editor(|editor, _, cx| {
12249 assert_eq!(
12250 resolve_requests_1.load(atomic::Ordering::Acquire),
12251 1,
12252 "Should always resolve once despite multiple selections"
12253 );
12254 assert_eq!(
12255 resolve_requests_2.load(atomic::Ordering::Acquire),
12256 1,
12257 "Should always resolve once after multiple selections and applying the completion"
12258 );
12259 assert_eq!(
12260 editor.text(cx),
12261 "fn main() { let a = ??.other; }",
12262 "Should use resolved data when applying the completion"
12263 );
12264 });
12265}
12266
12267#[gpui::test]
12268async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12269 init_test(cx, |_| {});
12270
12271 let item_0 = lsp::CompletionItem {
12272 label: "abs".into(),
12273 insert_text: Some("abs".into()),
12274 data: Some(json!({ "very": "special"})),
12275 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12276 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12277 lsp::InsertReplaceEdit {
12278 new_text: "abs".to_string(),
12279 insert: lsp::Range::default(),
12280 replace: lsp::Range::default(),
12281 },
12282 )),
12283 ..lsp::CompletionItem::default()
12284 };
12285 let items = iter::once(item_0.clone())
12286 .chain((11..51).map(|i| lsp::CompletionItem {
12287 label: format!("item_{}", i),
12288 insert_text: Some(format!("item_{}", i)),
12289 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12290 ..lsp::CompletionItem::default()
12291 }))
12292 .collect::<Vec<_>>();
12293
12294 let default_commit_characters = vec!["?".to_string()];
12295 let default_data = json!({ "default": "data"});
12296 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12297 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12298 let default_edit_range = lsp::Range {
12299 start: lsp::Position {
12300 line: 0,
12301 character: 5,
12302 },
12303 end: lsp::Position {
12304 line: 0,
12305 character: 5,
12306 },
12307 };
12308
12309 let item_0_out = lsp::CompletionItem {
12310 commit_characters: Some(default_commit_characters.clone()),
12311 insert_text_format: Some(default_insert_text_format),
12312 ..item_0
12313 };
12314 let items_out = iter::once(item_0_out)
12315 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
12316 commit_characters: Some(default_commit_characters.clone()),
12317 data: Some(default_data.clone()),
12318 insert_text_mode: Some(default_insert_text_mode),
12319 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12320 range: default_edit_range,
12321 new_text: item.label.clone(),
12322 })),
12323 ..item.clone()
12324 }))
12325 .collect::<Vec<lsp::CompletionItem>>();
12326
12327 let mut cx = EditorLspTestContext::new_rust(
12328 lsp::ServerCapabilities {
12329 completion_provider: Some(lsp::CompletionOptions {
12330 trigger_characters: Some(vec![".".to_string()]),
12331 resolve_provider: Some(true),
12332 ..Default::default()
12333 }),
12334 ..Default::default()
12335 },
12336 cx,
12337 )
12338 .await;
12339
12340 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12341 cx.simulate_keystroke(".");
12342
12343 let completion_data = default_data.clone();
12344 let completion_characters = default_commit_characters.clone();
12345 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12346 let default_data = completion_data.clone();
12347 let default_commit_characters = completion_characters.clone();
12348 let items = items.clone();
12349 async move {
12350 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12351 items,
12352 item_defaults: Some(lsp::CompletionListItemDefaults {
12353 data: Some(default_data.clone()),
12354 commit_characters: Some(default_commit_characters.clone()),
12355 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12356 default_edit_range,
12357 )),
12358 insert_text_format: Some(default_insert_text_format),
12359 insert_text_mode: Some(default_insert_text_mode),
12360 }),
12361 ..lsp::CompletionList::default()
12362 })))
12363 }
12364 })
12365 .next()
12366 .await;
12367
12368 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12369 cx.lsp
12370 .server
12371 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12372 let closure_resolved_items = resolved_items.clone();
12373 move |item_to_resolve, _| {
12374 let closure_resolved_items = closure_resolved_items.clone();
12375 async move {
12376 closure_resolved_items.lock().push(item_to_resolve.clone());
12377 Ok(item_to_resolve)
12378 }
12379 }
12380 })
12381 .detach();
12382
12383 cx.condition(|editor, _| editor.context_menu_visible())
12384 .await;
12385 cx.run_until_parked();
12386 cx.update_editor(|editor, _, _| {
12387 let menu = editor.context_menu.borrow_mut();
12388 match menu.as_ref().expect("should have the completions menu") {
12389 CodeContextMenu::Completions(completions_menu) => {
12390 assert_eq!(
12391 completions_menu
12392 .entries
12393 .borrow()
12394 .iter()
12395 .map(|mat| mat.string.clone())
12396 .collect::<Vec<String>>(),
12397 items_out
12398 .iter()
12399 .map(|completion| completion.label.clone())
12400 .collect::<Vec<String>>()
12401 );
12402 }
12403 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12404 }
12405 });
12406 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12407 // with 4 from the end.
12408 assert_eq!(
12409 *resolved_items.lock(),
12410 [
12411 &items_out[0..16],
12412 &items_out[items_out.len() - 4..items_out.len()]
12413 ]
12414 .concat()
12415 .iter()
12416 .cloned()
12417 .collect::<Vec<lsp::CompletionItem>>()
12418 );
12419 resolved_items.lock().clear();
12420
12421 cx.update_editor(|editor, window, cx| {
12422 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12423 });
12424 cx.run_until_parked();
12425 // Completions that have already been resolved are skipped.
12426 assert_eq!(
12427 *resolved_items.lock(),
12428 items_out[items_out.len() - 16..items_out.len() - 4]
12429 .iter()
12430 .cloned()
12431 .collect::<Vec<lsp::CompletionItem>>()
12432 );
12433 resolved_items.lock().clear();
12434}
12435
12436#[gpui::test]
12437async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12438 init_test(cx, |_| {});
12439
12440 let mut cx = EditorLspTestContext::new(
12441 Language::new(
12442 LanguageConfig {
12443 matcher: LanguageMatcher {
12444 path_suffixes: vec!["jsx".into()],
12445 ..Default::default()
12446 },
12447 overrides: [(
12448 "element".into(),
12449 LanguageConfigOverride {
12450 word_characters: Override::Set(['-'].into_iter().collect()),
12451 ..Default::default()
12452 },
12453 )]
12454 .into_iter()
12455 .collect(),
12456 ..Default::default()
12457 },
12458 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12459 )
12460 .with_override_query("(jsx_self_closing_element) @element")
12461 .unwrap(),
12462 lsp::ServerCapabilities {
12463 completion_provider: Some(lsp::CompletionOptions {
12464 trigger_characters: Some(vec![":".to_string()]),
12465 ..Default::default()
12466 }),
12467 ..Default::default()
12468 },
12469 cx,
12470 )
12471 .await;
12472
12473 cx.lsp
12474 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12475 Ok(Some(lsp::CompletionResponse::Array(vec![
12476 lsp::CompletionItem {
12477 label: "bg-blue".into(),
12478 ..Default::default()
12479 },
12480 lsp::CompletionItem {
12481 label: "bg-red".into(),
12482 ..Default::default()
12483 },
12484 lsp::CompletionItem {
12485 label: "bg-yellow".into(),
12486 ..Default::default()
12487 },
12488 ])))
12489 });
12490
12491 cx.set_state(r#"<p class="bgˇ" />"#);
12492
12493 // Trigger completion when typing a dash, because the dash is an extra
12494 // word character in the 'element' scope, which contains the cursor.
12495 cx.simulate_keystroke("-");
12496 cx.executor().run_until_parked();
12497 cx.update_editor(|editor, _, _| {
12498 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12499 {
12500 assert_eq!(
12501 completion_menu_entries(&menu),
12502 &["bg-red", "bg-blue", "bg-yellow"]
12503 );
12504 } else {
12505 panic!("expected completion menu to be open");
12506 }
12507 });
12508
12509 cx.simulate_keystroke("l");
12510 cx.executor().run_until_parked();
12511 cx.update_editor(|editor, _, _| {
12512 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12513 {
12514 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12515 } else {
12516 panic!("expected completion menu to be open");
12517 }
12518 });
12519
12520 // When filtering completions, consider the character after the '-' to
12521 // be the start of a subword.
12522 cx.set_state(r#"<p class="yelˇ" />"#);
12523 cx.simulate_keystroke("l");
12524 cx.executor().run_until_parked();
12525 cx.update_editor(|editor, _, _| {
12526 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12527 {
12528 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12529 } else {
12530 panic!("expected completion menu to be open");
12531 }
12532 });
12533}
12534
12535fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12536 let entries = menu.entries.borrow();
12537 entries.iter().map(|mat| mat.string.clone()).collect()
12538}
12539
12540#[gpui::test]
12541async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12542 init_test(cx, |settings| {
12543 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12544 FormatterList(vec![Formatter::Prettier].into()),
12545 ))
12546 });
12547
12548 let fs = FakeFs::new(cx.executor());
12549 fs.insert_file(path!("/file.ts"), Default::default()).await;
12550
12551 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12552 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12553
12554 language_registry.add(Arc::new(Language::new(
12555 LanguageConfig {
12556 name: "TypeScript".into(),
12557 matcher: LanguageMatcher {
12558 path_suffixes: vec!["ts".to_string()],
12559 ..Default::default()
12560 },
12561 ..Default::default()
12562 },
12563 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12564 )));
12565 update_test_language_settings(cx, |settings| {
12566 settings.defaults.prettier = Some(PrettierSettings {
12567 allowed: true,
12568 ..PrettierSettings::default()
12569 });
12570 });
12571
12572 let test_plugin = "test_plugin";
12573 let _ = language_registry.register_fake_lsp(
12574 "TypeScript",
12575 FakeLspAdapter {
12576 prettier_plugins: vec![test_plugin],
12577 ..Default::default()
12578 },
12579 );
12580
12581 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12582 let buffer = project
12583 .update(cx, |project, cx| {
12584 project.open_local_buffer(path!("/file.ts"), cx)
12585 })
12586 .await
12587 .unwrap();
12588
12589 let buffer_text = "one\ntwo\nthree\n";
12590 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12591 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12592 editor.update_in(cx, |editor, window, cx| {
12593 editor.set_text(buffer_text, window, cx)
12594 });
12595
12596 editor
12597 .update_in(cx, |editor, window, cx| {
12598 editor.perform_format(
12599 project.clone(),
12600 FormatTrigger::Manual,
12601 FormatTarget::Buffers,
12602 window,
12603 cx,
12604 )
12605 })
12606 .unwrap()
12607 .await;
12608 assert_eq!(
12609 editor.update(cx, |editor, cx| editor.text(cx)),
12610 buffer_text.to_string() + prettier_format_suffix,
12611 "Test prettier formatting was not applied to the original buffer text",
12612 );
12613
12614 update_test_language_settings(cx, |settings| {
12615 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12616 });
12617 let format = editor.update_in(cx, |editor, window, cx| {
12618 editor.perform_format(
12619 project.clone(),
12620 FormatTrigger::Manual,
12621 FormatTarget::Buffers,
12622 window,
12623 cx,
12624 )
12625 });
12626 format.await.unwrap();
12627 assert_eq!(
12628 editor.update(cx, |editor, cx| editor.text(cx)),
12629 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12630 "Autoformatting (via test prettier) was not applied to the original buffer text",
12631 );
12632}
12633
12634#[gpui::test]
12635async fn test_addition_reverts(cx: &mut TestAppContext) {
12636 init_test(cx, |_| {});
12637 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12638 let base_text = indoc! {r#"
12639 struct Row;
12640 struct Row1;
12641 struct Row2;
12642
12643 struct Row4;
12644 struct Row5;
12645 struct Row6;
12646
12647 struct Row8;
12648 struct Row9;
12649 struct Row10;"#};
12650
12651 // When addition hunks are not adjacent to carets, no hunk revert is performed
12652 assert_hunk_revert(
12653 indoc! {r#"struct Row;
12654 struct Row1;
12655 struct Row1.1;
12656 struct Row1.2;
12657 struct Row2;ˇ
12658
12659 struct Row4;
12660 struct Row5;
12661 struct Row6;
12662
12663 struct Row8;
12664 ˇstruct Row9;
12665 struct Row9.1;
12666 struct Row9.2;
12667 struct Row9.3;
12668 struct Row10;"#},
12669 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12670 indoc! {r#"struct Row;
12671 struct Row1;
12672 struct Row1.1;
12673 struct Row1.2;
12674 struct Row2;ˇ
12675
12676 struct Row4;
12677 struct Row5;
12678 struct Row6;
12679
12680 struct Row8;
12681 ˇstruct Row9;
12682 struct Row9.1;
12683 struct Row9.2;
12684 struct Row9.3;
12685 struct Row10;"#},
12686 base_text,
12687 &mut cx,
12688 );
12689 // Same for selections
12690 assert_hunk_revert(
12691 indoc! {r#"struct Row;
12692 struct Row1;
12693 struct Row2;
12694 struct Row2.1;
12695 struct Row2.2;
12696 «ˇ
12697 struct Row4;
12698 struct» Row5;
12699 «struct Row6;
12700 ˇ»
12701 struct Row9.1;
12702 struct Row9.2;
12703 struct Row9.3;
12704 struct Row8;
12705 struct Row9;
12706 struct Row10;"#},
12707 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12708 indoc! {r#"struct Row;
12709 struct Row1;
12710 struct Row2;
12711 struct Row2.1;
12712 struct Row2.2;
12713 «ˇ
12714 struct Row4;
12715 struct» Row5;
12716 «struct Row6;
12717 ˇ»
12718 struct Row9.1;
12719 struct Row9.2;
12720 struct Row9.3;
12721 struct Row8;
12722 struct Row9;
12723 struct Row10;"#},
12724 base_text,
12725 &mut cx,
12726 );
12727
12728 // When carets and selections intersect the addition hunks, those are reverted.
12729 // Adjacent carets got merged.
12730 assert_hunk_revert(
12731 indoc! {r#"struct Row;
12732 ˇ// something on the top
12733 struct Row1;
12734 struct Row2;
12735 struct Roˇw3.1;
12736 struct Row2.2;
12737 struct Row2.3;ˇ
12738
12739 struct Row4;
12740 struct ˇRow5.1;
12741 struct Row5.2;
12742 struct «Rowˇ»5.3;
12743 struct Row5;
12744 struct Row6;
12745 ˇ
12746 struct Row9.1;
12747 struct «Rowˇ»9.2;
12748 struct «ˇRow»9.3;
12749 struct Row8;
12750 struct Row9;
12751 «ˇ// something on bottom»
12752 struct Row10;"#},
12753 vec![
12754 DiffHunkStatusKind::Added,
12755 DiffHunkStatusKind::Added,
12756 DiffHunkStatusKind::Added,
12757 DiffHunkStatusKind::Added,
12758 DiffHunkStatusKind::Added,
12759 ],
12760 indoc! {r#"struct Row;
12761 ˇstruct Row1;
12762 struct Row2;
12763 ˇ
12764 struct Row4;
12765 ˇstruct Row5;
12766 struct Row6;
12767 ˇ
12768 ˇstruct Row8;
12769 struct Row9;
12770 ˇstruct Row10;"#},
12771 base_text,
12772 &mut cx,
12773 );
12774}
12775
12776#[gpui::test]
12777async fn test_modification_reverts(cx: &mut TestAppContext) {
12778 init_test(cx, |_| {});
12779 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12780 let base_text = indoc! {r#"
12781 struct Row;
12782 struct Row1;
12783 struct Row2;
12784
12785 struct Row4;
12786 struct Row5;
12787 struct Row6;
12788
12789 struct Row8;
12790 struct Row9;
12791 struct Row10;"#};
12792
12793 // Modification hunks behave the same as the addition ones.
12794 assert_hunk_revert(
12795 indoc! {r#"struct Row;
12796 struct Row1;
12797 struct Row33;
12798 ˇ
12799 struct Row4;
12800 struct Row5;
12801 struct Row6;
12802 ˇ
12803 struct Row99;
12804 struct Row9;
12805 struct Row10;"#},
12806 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12807 indoc! {r#"struct Row;
12808 struct Row1;
12809 struct Row33;
12810 ˇ
12811 struct Row4;
12812 struct Row5;
12813 struct Row6;
12814 ˇ
12815 struct Row99;
12816 struct Row9;
12817 struct Row10;"#},
12818 base_text,
12819 &mut cx,
12820 );
12821 assert_hunk_revert(
12822 indoc! {r#"struct Row;
12823 struct Row1;
12824 struct Row33;
12825 «ˇ
12826 struct Row4;
12827 struct» Row5;
12828 «struct Row6;
12829 ˇ»
12830 struct Row99;
12831 struct Row9;
12832 struct Row10;"#},
12833 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12834 indoc! {r#"struct Row;
12835 struct Row1;
12836 struct Row33;
12837 «ˇ
12838 struct Row4;
12839 struct» Row5;
12840 «struct Row6;
12841 ˇ»
12842 struct Row99;
12843 struct Row9;
12844 struct Row10;"#},
12845 base_text,
12846 &mut cx,
12847 );
12848
12849 assert_hunk_revert(
12850 indoc! {r#"ˇstruct Row1.1;
12851 struct Row1;
12852 «ˇstr»uct Row22;
12853
12854 struct ˇRow44;
12855 struct Row5;
12856 struct «Rˇ»ow66;ˇ
12857
12858 «struˇ»ct Row88;
12859 struct Row9;
12860 struct Row1011;ˇ"#},
12861 vec![
12862 DiffHunkStatusKind::Modified,
12863 DiffHunkStatusKind::Modified,
12864 DiffHunkStatusKind::Modified,
12865 DiffHunkStatusKind::Modified,
12866 DiffHunkStatusKind::Modified,
12867 DiffHunkStatusKind::Modified,
12868 ],
12869 indoc! {r#"struct Row;
12870 ˇstruct Row1;
12871 struct Row2;
12872 ˇ
12873 struct Row4;
12874 ˇstruct Row5;
12875 struct Row6;
12876 ˇ
12877 struct Row8;
12878 ˇstruct Row9;
12879 struct Row10;ˇ"#},
12880 base_text,
12881 &mut cx,
12882 );
12883}
12884
12885#[gpui::test]
12886async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12887 init_test(cx, |_| {});
12888 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12889 let base_text = indoc! {r#"
12890 one
12891
12892 two
12893 three
12894 "#};
12895
12896 cx.set_head_text(base_text);
12897 cx.set_state("\nˇ\n");
12898 cx.executor().run_until_parked();
12899 cx.update_editor(|editor, _window, cx| {
12900 editor.expand_selected_diff_hunks(cx);
12901 });
12902 cx.executor().run_until_parked();
12903 cx.update_editor(|editor, window, cx| {
12904 editor.backspace(&Default::default(), window, cx);
12905 });
12906 cx.run_until_parked();
12907 cx.assert_state_with_diff(
12908 indoc! {r#"
12909
12910 - two
12911 - threeˇ
12912 +
12913 "#}
12914 .to_string(),
12915 );
12916}
12917
12918#[gpui::test]
12919async fn test_deletion_reverts(cx: &mut TestAppContext) {
12920 init_test(cx, |_| {});
12921 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12922 let base_text = indoc! {r#"struct Row;
12923struct Row1;
12924struct Row2;
12925
12926struct Row4;
12927struct Row5;
12928struct Row6;
12929
12930struct Row8;
12931struct Row9;
12932struct Row10;"#};
12933
12934 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12935 assert_hunk_revert(
12936 indoc! {r#"struct Row;
12937 struct Row2;
12938
12939 ˇstruct Row4;
12940 struct Row5;
12941 struct Row6;
12942 ˇ
12943 struct Row8;
12944 struct Row10;"#},
12945 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12946 indoc! {r#"struct Row;
12947 struct Row2;
12948
12949 ˇstruct Row4;
12950 struct Row5;
12951 struct Row6;
12952 ˇ
12953 struct Row8;
12954 struct Row10;"#},
12955 base_text,
12956 &mut cx,
12957 );
12958 assert_hunk_revert(
12959 indoc! {r#"struct Row;
12960 struct Row2;
12961
12962 «ˇstruct Row4;
12963 struct» Row5;
12964 «struct Row6;
12965 ˇ»
12966 struct Row8;
12967 struct Row10;"#},
12968 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12969 indoc! {r#"struct Row;
12970 struct Row2;
12971
12972 «ˇstruct Row4;
12973 struct» Row5;
12974 «struct Row6;
12975 ˇ»
12976 struct Row8;
12977 struct Row10;"#},
12978 base_text,
12979 &mut cx,
12980 );
12981
12982 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12983 assert_hunk_revert(
12984 indoc! {r#"struct Row;
12985 ˇstruct Row2;
12986
12987 struct Row4;
12988 struct Row5;
12989 struct Row6;
12990
12991 struct Row8;ˇ
12992 struct Row10;"#},
12993 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12994 indoc! {r#"struct Row;
12995 struct Row1;
12996 ˇstruct Row2;
12997
12998 struct Row4;
12999 struct Row5;
13000 struct Row6;
13001
13002 struct Row8;ˇ
13003 struct Row9;
13004 struct Row10;"#},
13005 base_text,
13006 &mut cx,
13007 );
13008 assert_hunk_revert(
13009 indoc! {r#"struct Row;
13010 struct Row2«ˇ;
13011 struct Row4;
13012 struct» Row5;
13013 «struct Row6;
13014
13015 struct Row8;ˇ»
13016 struct Row10;"#},
13017 vec![
13018 DiffHunkStatusKind::Deleted,
13019 DiffHunkStatusKind::Deleted,
13020 DiffHunkStatusKind::Deleted,
13021 ],
13022 indoc! {r#"struct Row;
13023 struct Row1;
13024 struct Row2«ˇ;
13025
13026 struct Row4;
13027 struct» Row5;
13028 «struct Row6;
13029
13030 struct Row8;ˇ»
13031 struct Row9;
13032 struct Row10;"#},
13033 base_text,
13034 &mut cx,
13035 );
13036}
13037
13038#[gpui::test]
13039async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13040 init_test(cx, |_| {});
13041
13042 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13043 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13044 let base_text_3 =
13045 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13046
13047 let text_1 = edit_first_char_of_every_line(base_text_1);
13048 let text_2 = edit_first_char_of_every_line(base_text_2);
13049 let text_3 = edit_first_char_of_every_line(base_text_3);
13050
13051 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13052 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13053 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13054
13055 let multibuffer = cx.new(|cx| {
13056 let mut multibuffer = MultiBuffer::new(ReadWrite);
13057 multibuffer.push_excerpts(
13058 buffer_1.clone(),
13059 [
13060 ExcerptRange {
13061 context: Point::new(0, 0)..Point::new(3, 0),
13062 primary: None,
13063 },
13064 ExcerptRange {
13065 context: Point::new(5, 0)..Point::new(7, 0),
13066 primary: None,
13067 },
13068 ExcerptRange {
13069 context: Point::new(9, 0)..Point::new(10, 4),
13070 primary: None,
13071 },
13072 ],
13073 cx,
13074 );
13075 multibuffer.push_excerpts(
13076 buffer_2.clone(),
13077 [
13078 ExcerptRange {
13079 context: Point::new(0, 0)..Point::new(3, 0),
13080 primary: None,
13081 },
13082 ExcerptRange {
13083 context: Point::new(5, 0)..Point::new(7, 0),
13084 primary: None,
13085 },
13086 ExcerptRange {
13087 context: Point::new(9, 0)..Point::new(10, 4),
13088 primary: None,
13089 },
13090 ],
13091 cx,
13092 );
13093 multibuffer.push_excerpts(
13094 buffer_3.clone(),
13095 [
13096 ExcerptRange {
13097 context: Point::new(0, 0)..Point::new(3, 0),
13098 primary: None,
13099 },
13100 ExcerptRange {
13101 context: Point::new(5, 0)..Point::new(7, 0),
13102 primary: None,
13103 },
13104 ExcerptRange {
13105 context: Point::new(9, 0)..Point::new(10, 4),
13106 primary: None,
13107 },
13108 ],
13109 cx,
13110 );
13111 multibuffer
13112 });
13113
13114 let fs = FakeFs::new(cx.executor());
13115 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13116 let (editor, cx) = cx
13117 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13118 editor.update_in(cx, |editor, _window, cx| {
13119 for (buffer, diff_base) in [
13120 (buffer_1.clone(), base_text_1),
13121 (buffer_2.clone(), base_text_2),
13122 (buffer_3.clone(), base_text_3),
13123 ] {
13124 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13125 editor
13126 .buffer
13127 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13128 }
13129 });
13130 cx.executor().run_until_parked();
13131
13132 editor.update_in(cx, |editor, window, cx| {
13133 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}");
13134 editor.select_all(&SelectAll, window, cx);
13135 editor.git_restore(&Default::default(), window, cx);
13136 });
13137 cx.executor().run_until_parked();
13138
13139 // When all ranges are selected, all buffer hunks are reverted.
13140 editor.update(cx, |editor, cx| {
13141 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");
13142 });
13143 buffer_1.update(cx, |buffer, _| {
13144 assert_eq!(buffer.text(), base_text_1);
13145 });
13146 buffer_2.update(cx, |buffer, _| {
13147 assert_eq!(buffer.text(), base_text_2);
13148 });
13149 buffer_3.update(cx, |buffer, _| {
13150 assert_eq!(buffer.text(), base_text_3);
13151 });
13152
13153 editor.update_in(cx, |editor, window, cx| {
13154 editor.undo(&Default::default(), window, cx);
13155 });
13156
13157 editor.update_in(cx, |editor, window, cx| {
13158 editor.change_selections(None, window, cx, |s| {
13159 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13160 });
13161 editor.git_restore(&Default::default(), window, cx);
13162 });
13163
13164 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13165 // but not affect buffer_2 and its related excerpts.
13166 editor.update(cx, |editor, cx| {
13167 assert_eq!(
13168 editor.text(cx),
13169 "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}"
13170 );
13171 });
13172 buffer_1.update(cx, |buffer, _| {
13173 assert_eq!(buffer.text(), base_text_1);
13174 });
13175 buffer_2.update(cx, |buffer, _| {
13176 assert_eq!(
13177 buffer.text(),
13178 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13179 );
13180 });
13181 buffer_3.update(cx, |buffer, _| {
13182 assert_eq!(
13183 buffer.text(),
13184 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13185 );
13186 });
13187
13188 fn edit_first_char_of_every_line(text: &str) -> String {
13189 text.split('\n')
13190 .map(|line| format!("X{}", &line[1..]))
13191 .collect::<Vec<_>>()
13192 .join("\n")
13193 }
13194}
13195
13196#[gpui::test]
13197async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13198 init_test(cx, |_| {});
13199
13200 let cols = 4;
13201 let rows = 10;
13202 let sample_text_1 = sample_text(rows, cols, 'a');
13203 assert_eq!(
13204 sample_text_1,
13205 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13206 );
13207 let sample_text_2 = sample_text(rows, cols, 'l');
13208 assert_eq!(
13209 sample_text_2,
13210 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13211 );
13212 let sample_text_3 = sample_text(rows, cols, 'v');
13213 assert_eq!(
13214 sample_text_3,
13215 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13216 );
13217
13218 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13219 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13220 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13221
13222 let multi_buffer = cx.new(|cx| {
13223 let mut multibuffer = MultiBuffer::new(ReadWrite);
13224 multibuffer.push_excerpts(
13225 buffer_1.clone(),
13226 [
13227 ExcerptRange {
13228 context: Point::new(0, 0)..Point::new(3, 0),
13229 primary: None,
13230 },
13231 ExcerptRange {
13232 context: Point::new(5, 0)..Point::new(7, 0),
13233 primary: None,
13234 },
13235 ExcerptRange {
13236 context: Point::new(9, 0)..Point::new(10, 4),
13237 primary: None,
13238 },
13239 ],
13240 cx,
13241 );
13242 multibuffer.push_excerpts(
13243 buffer_2.clone(),
13244 [
13245 ExcerptRange {
13246 context: Point::new(0, 0)..Point::new(3, 0),
13247 primary: None,
13248 },
13249 ExcerptRange {
13250 context: Point::new(5, 0)..Point::new(7, 0),
13251 primary: None,
13252 },
13253 ExcerptRange {
13254 context: Point::new(9, 0)..Point::new(10, 4),
13255 primary: None,
13256 },
13257 ],
13258 cx,
13259 );
13260 multibuffer.push_excerpts(
13261 buffer_3.clone(),
13262 [
13263 ExcerptRange {
13264 context: Point::new(0, 0)..Point::new(3, 0),
13265 primary: None,
13266 },
13267 ExcerptRange {
13268 context: Point::new(5, 0)..Point::new(7, 0),
13269 primary: None,
13270 },
13271 ExcerptRange {
13272 context: Point::new(9, 0)..Point::new(10, 4),
13273 primary: None,
13274 },
13275 ],
13276 cx,
13277 );
13278 multibuffer
13279 });
13280
13281 let fs = FakeFs::new(cx.executor());
13282 fs.insert_tree(
13283 "/a",
13284 json!({
13285 "main.rs": sample_text_1,
13286 "other.rs": sample_text_2,
13287 "lib.rs": sample_text_3,
13288 }),
13289 )
13290 .await;
13291 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13292 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13293 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13294 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13295 Editor::new(
13296 EditorMode::Full,
13297 multi_buffer,
13298 Some(project.clone()),
13299 true,
13300 window,
13301 cx,
13302 )
13303 });
13304 let multibuffer_item_id = workspace
13305 .update(cx, |workspace, window, cx| {
13306 assert!(
13307 workspace.active_item(cx).is_none(),
13308 "active item should be None before the first item is added"
13309 );
13310 workspace.add_item_to_active_pane(
13311 Box::new(multi_buffer_editor.clone()),
13312 None,
13313 true,
13314 window,
13315 cx,
13316 );
13317 let active_item = workspace
13318 .active_item(cx)
13319 .expect("should have an active item after adding the multi buffer");
13320 assert!(
13321 !active_item.is_singleton(cx),
13322 "A multi buffer was expected to active after adding"
13323 );
13324 active_item.item_id()
13325 })
13326 .unwrap();
13327 cx.executor().run_until_parked();
13328
13329 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13330 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13331 s.select_ranges(Some(1..2))
13332 });
13333 editor.open_excerpts(&OpenExcerpts, window, cx);
13334 });
13335 cx.executor().run_until_parked();
13336 let first_item_id = workspace
13337 .update(cx, |workspace, window, cx| {
13338 let active_item = workspace
13339 .active_item(cx)
13340 .expect("should have an active item after navigating into the 1st buffer");
13341 let first_item_id = active_item.item_id();
13342 assert_ne!(
13343 first_item_id, multibuffer_item_id,
13344 "Should navigate into the 1st buffer and activate it"
13345 );
13346 assert!(
13347 active_item.is_singleton(cx),
13348 "New active item should be a singleton buffer"
13349 );
13350 assert_eq!(
13351 active_item
13352 .act_as::<Editor>(cx)
13353 .expect("should have navigated into an editor for the 1st buffer")
13354 .read(cx)
13355 .text(cx),
13356 sample_text_1
13357 );
13358
13359 workspace
13360 .go_back(workspace.active_pane().downgrade(), window, cx)
13361 .detach_and_log_err(cx);
13362
13363 first_item_id
13364 })
13365 .unwrap();
13366 cx.executor().run_until_parked();
13367 workspace
13368 .update(cx, |workspace, _, cx| {
13369 let active_item = workspace
13370 .active_item(cx)
13371 .expect("should have an active item after navigating back");
13372 assert_eq!(
13373 active_item.item_id(),
13374 multibuffer_item_id,
13375 "Should navigate back to the multi buffer"
13376 );
13377 assert!(!active_item.is_singleton(cx));
13378 })
13379 .unwrap();
13380
13381 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13382 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13383 s.select_ranges(Some(39..40))
13384 });
13385 editor.open_excerpts(&OpenExcerpts, window, cx);
13386 });
13387 cx.executor().run_until_parked();
13388 let second_item_id = workspace
13389 .update(cx, |workspace, window, cx| {
13390 let active_item = workspace
13391 .active_item(cx)
13392 .expect("should have an active item after navigating into the 2nd buffer");
13393 let second_item_id = active_item.item_id();
13394 assert_ne!(
13395 second_item_id, multibuffer_item_id,
13396 "Should navigate away from the multibuffer"
13397 );
13398 assert_ne!(
13399 second_item_id, first_item_id,
13400 "Should navigate into the 2nd buffer and activate it"
13401 );
13402 assert!(
13403 active_item.is_singleton(cx),
13404 "New active item should be a singleton buffer"
13405 );
13406 assert_eq!(
13407 active_item
13408 .act_as::<Editor>(cx)
13409 .expect("should have navigated into an editor")
13410 .read(cx)
13411 .text(cx),
13412 sample_text_2
13413 );
13414
13415 workspace
13416 .go_back(workspace.active_pane().downgrade(), window, cx)
13417 .detach_and_log_err(cx);
13418
13419 second_item_id
13420 })
13421 .unwrap();
13422 cx.executor().run_until_parked();
13423 workspace
13424 .update(cx, |workspace, _, cx| {
13425 let active_item = workspace
13426 .active_item(cx)
13427 .expect("should have an active item after navigating back from the 2nd buffer");
13428 assert_eq!(
13429 active_item.item_id(),
13430 multibuffer_item_id,
13431 "Should navigate back from the 2nd buffer to the multi buffer"
13432 );
13433 assert!(!active_item.is_singleton(cx));
13434 })
13435 .unwrap();
13436
13437 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13438 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13439 s.select_ranges(Some(70..70))
13440 });
13441 editor.open_excerpts(&OpenExcerpts, window, cx);
13442 });
13443 cx.executor().run_until_parked();
13444 workspace
13445 .update(cx, |workspace, window, cx| {
13446 let active_item = workspace
13447 .active_item(cx)
13448 .expect("should have an active item after navigating into the 3rd buffer");
13449 let third_item_id = active_item.item_id();
13450 assert_ne!(
13451 third_item_id, multibuffer_item_id,
13452 "Should navigate into the 3rd buffer and activate it"
13453 );
13454 assert_ne!(third_item_id, first_item_id);
13455 assert_ne!(third_item_id, second_item_id);
13456 assert!(
13457 active_item.is_singleton(cx),
13458 "New active item should be a singleton buffer"
13459 );
13460 assert_eq!(
13461 active_item
13462 .act_as::<Editor>(cx)
13463 .expect("should have navigated into an editor")
13464 .read(cx)
13465 .text(cx),
13466 sample_text_3
13467 );
13468
13469 workspace
13470 .go_back(workspace.active_pane().downgrade(), window, cx)
13471 .detach_and_log_err(cx);
13472 })
13473 .unwrap();
13474 cx.executor().run_until_parked();
13475 workspace
13476 .update(cx, |workspace, _, cx| {
13477 let active_item = workspace
13478 .active_item(cx)
13479 .expect("should have an active item after navigating back from the 3rd buffer");
13480 assert_eq!(
13481 active_item.item_id(),
13482 multibuffer_item_id,
13483 "Should navigate back from the 3rd buffer to the multi buffer"
13484 );
13485 assert!(!active_item.is_singleton(cx));
13486 })
13487 .unwrap();
13488}
13489
13490#[gpui::test]
13491async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13492 init_test(cx, |_| {});
13493
13494 let mut cx = EditorTestContext::new(cx).await;
13495
13496 let diff_base = r#"
13497 use some::mod;
13498
13499 const A: u32 = 42;
13500
13501 fn main() {
13502 println!("hello");
13503
13504 println!("world");
13505 }
13506 "#
13507 .unindent();
13508
13509 cx.set_state(
13510 &r#"
13511 use some::modified;
13512
13513 ˇ
13514 fn main() {
13515 println!("hello there");
13516
13517 println!("around the");
13518 println!("world");
13519 }
13520 "#
13521 .unindent(),
13522 );
13523
13524 cx.set_head_text(&diff_base);
13525 executor.run_until_parked();
13526
13527 cx.update_editor(|editor, window, cx| {
13528 editor.go_to_next_hunk(&GoToHunk, window, cx);
13529 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13530 });
13531 executor.run_until_parked();
13532 cx.assert_state_with_diff(
13533 r#"
13534 use some::modified;
13535
13536
13537 fn main() {
13538 - println!("hello");
13539 + ˇ println!("hello there");
13540
13541 println!("around the");
13542 println!("world");
13543 }
13544 "#
13545 .unindent(),
13546 );
13547
13548 cx.update_editor(|editor, window, cx| {
13549 for _ in 0..2 {
13550 editor.go_to_next_hunk(&GoToHunk, window, cx);
13551 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13552 }
13553 });
13554 executor.run_until_parked();
13555 cx.assert_state_with_diff(
13556 r#"
13557 - use some::mod;
13558 + ˇuse some::modified;
13559
13560
13561 fn main() {
13562 - println!("hello");
13563 + println!("hello there");
13564
13565 + println!("around the");
13566 println!("world");
13567 }
13568 "#
13569 .unindent(),
13570 );
13571
13572 cx.update_editor(|editor, window, cx| {
13573 editor.go_to_next_hunk(&GoToHunk, window, cx);
13574 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13575 });
13576 executor.run_until_parked();
13577 cx.assert_state_with_diff(
13578 r#"
13579 - use some::mod;
13580 + use some::modified;
13581
13582 - const A: u32 = 42;
13583 ˇ
13584 fn main() {
13585 - println!("hello");
13586 + println!("hello there");
13587
13588 + println!("around the");
13589 println!("world");
13590 }
13591 "#
13592 .unindent(),
13593 );
13594
13595 cx.update_editor(|editor, window, cx| {
13596 editor.cancel(&Cancel, window, cx);
13597 });
13598
13599 cx.assert_state_with_diff(
13600 r#"
13601 use some::modified;
13602
13603 ˇ
13604 fn main() {
13605 println!("hello there");
13606
13607 println!("around the");
13608 println!("world");
13609 }
13610 "#
13611 .unindent(),
13612 );
13613}
13614
13615#[gpui::test]
13616async fn test_diff_base_change_with_expanded_diff_hunks(
13617 executor: BackgroundExecutor,
13618 cx: &mut TestAppContext,
13619) {
13620 init_test(cx, |_| {});
13621
13622 let mut cx = EditorTestContext::new(cx).await;
13623
13624 let diff_base = r#"
13625 use some::mod1;
13626 use some::mod2;
13627
13628 const A: u32 = 42;
13629 const B: u32 = 42;
13630 const C: u32 = 42;
13631
13632 fn main() {
13633 println!("hello");
13634
13635 println!("world");
13636 }
13637 "#
13638 .unindent();
13639
13640 cx.set_state(
13641 &r#"
13642 use some::mod2;
13643
13644 const A: u32 = 42;
13645 const C: u32 = 42;
13646
13647 fn main(ˇ) {
13648 //println!("hello");
13649
13650 println!("world");
13651 //
13652 //
13653 }
13654 "#
13655 .unindent(),
13656 );
13657
13658 cx.set_head_text(&diff_base);
13659 executor.run_until_parked();
13660
13661 cx.update_editor(|editor, window, cx| {
13662 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13663 });
13664 executor.run_until_parked();
13665 cx.assert_state_with_diff(
13666 r#"
13667 - use some::mod1;
13668 use some::mod2;
13669
13670 const A: u32 = 42;
13671 - const B: u32 = 42;
13672 const C: u32 = 42;
13673
13674 fn main(ˇ) {
13675 - println!("hello");
13676 + //println!("hello");
13677
13678 println!("world");
13679 + //
13680 + //
13681 }
13682 "#
13683 .unindent(),
13684 );
13685
13686 cx.set_head_text("new diff base!");
13687 executor.run_until_parked();
13688 cx.assert_state_with_diff(
13689 r#"
13690 - new diff base!
13691 + use some::mod2;
13692 +
13693 + const A: u32 = 42;
13694 + const C: u32 = 42;
13695 +
13696 + fn main(ˇ) {
13697 + //println!("hello");
13698 +
13699 + println!("world");
13700 + //
13701 + //
13702 + }
13703 "#
13704 .unindent(),
13705 );
13706}
13707
13708#[gpui::test]
13709async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13710 init_test(cx, |_| {});
13711
13712 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13713 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13714 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13715 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13716 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13717 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13718
13719 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13720 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13721 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13722
13723 let multi_buffer = cx.new(|cx| {
13724 let mut multibuffer = MultiBuffer::new(ReadWrite);
13725 multibuffer.push_excerpts(
13726 buffer_1.clone(),
13727 [
13728 ExcerptRange {
13729 context: Point::new(0, 0)..Point::new(3, 0),
13730 primary: None,
13731 },
13732 ExcerptRange {
13733 context: Point::new(5, 0)..Point::new(7, 0),
13734 primary: None,
13735 },
13736 ExcerptRange {
13737 context: Point::new(9, 0)..Point::new(10, 3),
13738 primary: None,
13739 },
13740 ],
13741 cx,
13742 );
13743 multibuffer.push_excerpts(
13744 buffer_2.clone(),
13745 [
13746 ExcerptRange {
13747 context: Point::new(0, 0)..Point::new(3, 0),
13748 primary: None,
13749 },
13750 ExcerptRange {
13751 context: Point::new(5, 0)..Point::new(7, 0),
13752 primary: None,
13753 },
13754 ExcerptRange {
13755 context: Point::new(9, 0)..Point::new(10, 3),
13756 primary: None,
13757 },
13758 ],
13759 cx,
13760 );
13761 multibuffer.push_excerpts(
13762 buffer_3.clone(),
13763 [
13764 ExcerptRange {
13765 context: Point::new(0, 0)..Point::new(3, 0),
13766 primary: None,
13767 },
13768 ExcerptRange {
13769 context: Point::new(5, 0)..Point::new(7, 0),
13770 primary: None,
13771 },
13772 ExcerptRange {
13773 context: Point::new(9, 0)..Point::new(10, 3),
13774 primary: None,
13775 },
13776 ],
13777 cx,
13778 );
13779 multibuffer
13780 });
13781
13782 let editor = cx.add_window(|window, cx| {
13783 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13784 });
13785 editor
13786 .update(cx, |editor, _window, cx| {
13787 for (buffer, diff_base) in [
13788 (buffer_1.clone(), file_1_old),
13789 (buffer_2.clone(), file_2_old),
13790 (buffer_3.clone(), file_3_old),
13791 ] {
13792 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13793 editor
13794 .buffer
13795 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13796 }
13797 })
13798 .unwrap();
13799
13800 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13801 cx.run_until_parked();
13802
13803 cx.assert_editor_state(
13804 &"
13805 ˇaaa
13806 ccc
13807 ddd
13808
13809 ggg
13810 hhh
13811
13812
13813 lll
13814 mmm
13815 NNN
13816
13817 qqq
13818 rrr
13819
13820 uuu
13821 111
13822 222
13823 333
13824
13825 666
13826 777
13827
13828 000
13829 !!!"
13830 .unindent(),
13831 );
13832
13833 cx.update_editor(|editor, window, cx| {
13834 editor.select_all(&SelectAll, window, cx);
13835 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13836 });
13837 cx.executor().run_until_parked();
13838
13839 cx.assert_state_with_diff(
13840 "
13841 «aaa
13842 - bbb
13843 ccc
13844 ddd
13845
13846 ggg
13847 hhh
13848
13849
13850 lll
13851 mmm
13852 - nnn
13853 + NNN
13854
13855 qqq
13856 rrr
13857
13858 uuu
13859 111
13860 222
13861 333
13862
13863 + 666
13864 777
13865
13866 000
13867 !!!ˇ»"
13868 .unindent(),
13869 );
13870}
13871
13872#[gpui::test]
13873async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13874 init_test(cx, |_| {});
13875
13876 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13877 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13878
13879 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13880 let multi_buffer = cx.new(|cx| {
13881 let mut multibuffer = MultiBuffer::new(ReadWrite);
13882 multibuffer.push_excerpts(
13883 buffer.clone(),
13884 [
13885 ExcerptRange {
13886 context: Point::new(0, 0)..Point::new(2, 0),
13887 primary: None,
13888 },
13889 ExcerptRange {
13890 context: Point::new(4, 0)..Point::new(7, 0),
13891 primary: None,
13892 },
13893 ExcerptRange {
13894 context: Point::new(9, 0)..Point::new(10, 0),
13895 primary: None,
13896 },
13897 ],
13898 cx,
13899 );
13900 multibuffer
13901 });
13902
13903 let editor = cx.add_window(|window, cx| {
13904 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13905 });
13906 editor
13907 .update(cx, |editor, _window, cx| {
13908 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13909 editor
13910 .buffer
13911 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13912 })
13913 .unwrap();
13914
13915 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13916 cx.run_until_parked();
13917
13918 cx.update_editor(|editor, window, cx| {
13919 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13920 });
13921 cx.executor().run_until_parked();
13922
13923 // When the start of a hunk coincides with the start of its excerpt,
13924 // the hunk is expanded. When the start of a a hunk is earlier than
13925 // the start of its excerpt, the hunk is not expanded.
13926 cx.assert_state_with_diff(
13927 "
13928 ˇaaa
13929 - bbb
13930 + BBB
13931
13932 - ddd
13933 - eee
13934 + DDD
13935 + EEE
13936 fff
13937
13938 iii
13939 "
13940 .unindent(),
13941 );
13942}
13943
13944#[gpui::test]
13945async fn test_edits_around_expanded_insertion_hunks(
13946 executor: BackgroundExecutor,
13947 cx: &mut TestAppContext,
13948) {
13949 init_test(cx, |_| {});
13950
13951 let mut cx = EditorTestContext::new(cx).await;
13952
13953 let diff_base = r#"
13954 use some::mod1;
13955 use some::mod2;
13956
13957 const A: u32 = 42;
13958
13959 fn main() {
13960 println!("hello");
13961
13962 println!("world");
13963 }
13964 "#
13965 .unindent();
13966 executor.run_until_parked();
13967 cx.set_state(
13968 &r#"
13969 use some::mod1;
13970 use some::mod2;
13971
13972 const A: u32 = 42;
13973 const B: u32 = 42;
13974 const C: u32 = 42;
13975 ˇ
13976
13977 fn main() {
13978 println!("hello");
13979
13980 println!("world");
13981 }
13982 "#
13983 .unindent(),
13984 );
13985
13986 cx.set_head_text(&diff_base);
13987 executor.run_until_parked();
13988
13989 cx.update_editor(|editor, window, cx| {
13990 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13991 });
13992 executor.run_until_parked();
13993
13994 cx.assert_state_with_diff(
13995 r#"
13996 use some::mod1;
13997 use some::mod2;
13998
13999 const A: u32 = 42;
14000 + const B: u32 = 42;
14001 + const C: u32 = 42;
14002 + ˇ
14003
14004 fn main() {
14005 println!("hello");
14006
14007 println!("world");
14008 }
14009 "#
14010 .unindent(),
14011 );
14012
14013 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14014 executor.run_until_parked();
14015
14016 cx.assert_state_with_diff(
14017 r#"
14018 use some::mod1;
14019 use some::mod2;
14020
14021 const A: u32 = 42;
14022 + const B: u32 = 42;
14023 + const C: u32 = 42;
14024 + const D: u32 = 42;
14025 + ˇ
14026
14027 fn main() {
14028 println!("hello");
14029
14030 println!("world");
14031 }
14032 "#
14033 .unindent(),
14034 );
14035
14036 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14037 executor.run_until_parked();
14038
14039 cx.assert_state_with_diff(
14040 r#"
14041 use some::mod1;
14042 use some::mod2;
14043
14044 const A: u32 = 42;
14045 + const B: u32 = 42;
14046 + const C: u32 = 42;
14047 + const D: u32 = 42;
14048 + const E: u32 = 42;
14049 + ˇ
14050
14051 fn main() {
14052 println!("hello");
14053
14054 println!("world");
14055 }
14056 "#
14057 .unindent(),
14058 );
14059
14060 cx.update_editor(|editor, window, cx| {
14061 editor.delete_line(&DeleteLine, window, cx);
14062 });
14063 executor.run_until_parked();
14064
14065 cx.assert_state_with_diff(
14066 r#"
14067 use some::mod1;
14068 use some::mod2;
14069
14070 const A: u32 = 42;
14071 + const B: u32 = 42;
14072 + const C: u32 = 42;
14073 + const D: u32 = 42;
14074 + const E: u32 = 42;
14075 ˇ
14076 fn main() {
14077 println!("hello");
14078
14079 println!("world");
14080 }
14081 "#
14082 .unindent(),
14083 );
14084
14085 cx.update_editor(|editor, window, cx| {
14086 editor.move_up(&MoveUp, window, cx);
14087 editor.delete_line(&DeleteLine, window, cx);
14088 editor.move_up(&MoveUp, window, cx);
14089 editor.delete_line(&DeleteLine, window, cx);
14090 editor.move_up(&MoveUp, window, cx);
14091 editor.delete_line(&DeleteLine, window, cx);
14092 });
14093 executor.run_until_parked();
14094 cx.assert_state_with_diff(
14095 r#"
14096 use some::mod1;
14097 use some::mod2;
14098
14099 const A: u32 = 42;
14100 + const B: u32 = 42;
14101 ˇ
14102 fn main() {
14103 println!("hello");
14104
14105 println!("world");
14106 }
14107 "#
14108 .unindent(),
14109 );
14110
14111 cx.update_editor(|editor, window, cx| {
14112 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14113 editor.delete_line(&DeleteLine, window, cx);
14114 });
14115 executor.run_until_parked();
14116 cx.assert_state_with_diff(
14117 r#"
14118 ˇ
14119 fn main() {
14120 println!("hello");
14121
14122 println!("world");
14123 }
14124 "#
14125 .unindent(),
14126 );
14127}
14128
14129#[gpui::test]
14130async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14131 init_test(cx, |_| {});
14132
14133 let mut cx = EditorTestContext::new(cx).await;
14134 cx.set_head_text(indoc! { "
14135 one
14136 two
14137 three
14138 four
14139 five
14140 "
14141 });
14142 cx.set_state(indoc! { "
14143 one
14144 ˇthree
14145 five
14146 "});
14147 cx.run_until_parked();
14148 cx.update_editor(|editor, window, cx| {
14149 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14150 });
14151 cx.assert_state_with_diff(
14152 indoc! { "
14153 one
14154 - two
14155 ˇthree
14156 - four
14157 five
14158 "}
14159 .to_string(),
14160 );
14161 cx.update_editor(|editor, window, cx| {
14162 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14163 });
14164
14165 cx.assert_state_with_diff(
14166 indoc! { "
14167 one
14168 ˇthree
14169 five
14170 "}
14171 .to_string(),
14172 );
14173
14174 cx.set_state(indoc! { "
14175 one
14176 ˇTWO
14177 three
14178 four
14179 five
14180 "});
14181 cx.run_until_parked();
14182 cx.update_editor(|editor, window, cx| {
14183 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14184 });
14185
14186 cx.assert_state_with_diff(
14187 indoc! { "
14188 one
14189 - two
14190 + ˇTWO
14191 three
14192 four
14193 five
14194 "}
14195 .to_string(),
14196 );
14197 cx.update_editor(|editor, window, cx| {
14198 editor.move_up(&Default::default(), window, cx);
14199 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14200 });
14201 cx.assert_state_with_diff(
14202 indoc! { "
14203 one
14204 ˇTWO
14205 three
14206 four
14207 five
14208 "}
14209 .to_string(),
14210 );
14211}
14212
14213#[gpui::test]
14214async fn test_edits_around_expanded_deletion_hunks(
14215 executor: BackgroundExecutor,
14216 cx: &mut TestAppContext,
14217) {
14218 init_test(cx, |_| {});
14219
14220 let mut cx = EditorTestContext::new(cx).await;
14221
14222 let diff_base = r#"
14223 use some::mod1;
14224 use some::mod2;
14225
14226 const A: u32 = 42;
14227 const B: u32 = 42;
14228 const C: u32 = 42;
14229
14230
14231 fn main() {
14232 println!("hello");
14233
14234 println!("world");
14235 }
14236 "#
14237 .unindent();
14238 executor.run_until_parked();
14239 cx.set_state(
14240 &r#"
14241 use some::mod1;
14242 use some::mod2;
14243
14244 ˇconst B: u32 = 42;
14245 const C: u32 = 42;
14246
14247
14248 fn main() {
14249 println!("hello");
14250
14251 println!("world");
14252 }
14253 "#
14254 .unindent(),
14255 );
14256
14257 cx.set_head_text(&diff_base);
14258 executor.run_until_parked();
14259
14260 cx.update_editor(|editor, window, cx| {
14261 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14262 });
14263 executor.run_until_parked();
14264
14265 cx.assert_state_with_diff(
14266 r#"
14267 use some::mod1;
14268 use some::mod2;
14269
14270 - const A: u32 = 42;
14271 ˇconst B: u32 = 42;
14272 const C: u32 = 42;
14273
14274
14275 fn main() {
14276 println!("hello");
14277
14278 println!("world");
14279 }
14280 "#
14281 .unindent(),
14282 );
14283
14284 cx.update_editor(|editor, window, cx| {
14285 editor.delete_line(&DeleteLine, window, cx);
14286 });
14287 executor.run_until_parked();
14288 cx.assert_state_with_diff(
14289 r#"
14290 use some::mod1;
14291 use some::mod2;
14292
14293 - const A: u32 = 42;
14294 - const B: u32 = 42;
14295 ˇconst C: u32 = 42;
14296
14297
14298 fn main() {
14299 println!("hello");
14300
14301 println!("world");
14302 }
14303 "#
14304 .unindent(),
14305 );
14306
14307 cx.update_editor(|editor, window, cx| {
14308 editor.delete_line(&DeleteLine, window, cx);
14309 });
14310 executor.run_until_parked();
14311 cx.assert_state_with_diff(
14312 r#"
14313 use some::mod1;
14314 use some::mod2;
14315
14316 - const A: u32 = 42;
14317 - const B: u32 = 42;
14318 - const C: u32 = 42;
14319 ˇ
14320
14321 fn main() {
14322 println!("hello");
14323
14324 println!("world");
14325 }
14326 "#
14327 .unindent(),
14328 );
14329
14330 cx.update_editor(|editor, window, cx| {
14331 editor.handle_input("replacement", window, cx);
14332 });
14333 executor.run_until_parked();
14334 cx.assert_state_with_diff(
14335 r#"
14336 use some::mod1;
14337 use some::mod2;
14338
14339 - const A: u32 = 42;
14340 - const B: u32 = 42;
14341 - const C: u32 = 42;
14342 -
14343 + replacementˇ
14344
14345 fn main() {
14346 println!("hello");
14347
14348 println!("world");
14349 }
14350 "#
14351 .unindent(),
14352 );
14353}
14354
14355#[gpui::test]
14356async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14357 init_test(cx, |_| {});
14358
14359 let mut cx = EditorTestContext::new(cx).await;
14360
14361 let base_text = r#"
14362 one
14363 two
14364 three
14365 four
14366 five
14367 "#
14368 .unindent();
14369 executor.run_until_parked();
14370 cx.set_state(
14371 &r#"
14372 one
14373 two
14374 fˇour
14375 five
14376 "#
14377 .unindent(),
14378 );
14379
14380 cx.set_head_text(&base_text);
14381 executor.run_until_parked();
14382
14383 cx.update_editor(|editor, window, cx| {
14384 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14385 });
14386 executor.run_until_parked();
14387
14388 cx.assert_state_with_diff(
14389 r#"
14390 one
14391 two
14392 - three
14393 fˇour
14394 five
14395 "#
14396 .unindent(),
14397 );
14398
14399 cx.update_editor(|editor, window, cx| {
14400 editor.backspace(&Backspace, window, cx);
14401 editor.backspace(&Backspace, window, cx);
14402 });
14403 executor.run_until_parked();
14404 cx.assert_state_with_diff(
14405 r#"
14406 one
14407 two
14408 - threeˇ
14409 - four
14410 + our
14411 five
14412 "#
14413 .unindent(),
14414 );
14415}
14416
14417#[gpui::test]
14418async fn test_edit_after_expanded_modification_hunk(
14419 executor: BackgroundExecutor,
14420 cx: &mut TestAppContext,
14421) {
14422 init_test(cx, |_| {});
14423
14424 let mut cx = EditorTestContext::new(cx).await;
14425
14426 let diff_base = r#"
14427 use some::mod1;
14428 use some::mod2;
14429
14430 const A: u32 = 42;
14431 const B: u32 = 42;
14432 const C: u32 = 42;
14433 const D: u32 = 42;
14434
14435
14436 fn main() {
14437 println!("hello");
14438
14439 println!("world");
14440 }"#
14441 .unindent();
14442
14443 cx.set_state(
14444 &r#"
14445 use some::mod1;
14446 use some::mod2;
14447
14448 const A: u32 = 42;
14449 const B: u32 = 42;
14450 const C: u32 = 43ˇ
14451 const D: u32 = 42;
14452
14453
14454 fn main() {
14455 println!("hello");
14456
14457 println!("world");
14458 }"#
14459 .unindent(),
14460 );
14461
14462 cx.set_head_text(&diff_base);
14463 executor.run_until_parked();
14464 cx.update_editor(|editor, window, cx| {
14465 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14466 });
14467 executor.run_until_parked();
14468
14469 cx.assert_state_with_diff(
14470 r#"
14471 use some::mod1;
14472 use some::mod2;
14473
14474 const A: u32 = 42;
14475 const B: u32 = 42;
14476 - const C: u32 = 42;
14477 + const C: u32 = 43ˇ
14478 const D: u32 = 42;
14479
14480
14481 fn main() {
14482 println!("hello");
14483
14484 println!("world");
14485 }"#
14486 .unindent(),
14487 );
14488
14489 cx.update_editor(|editor, window, cx| {
14490 editor.handle_input("\nnew_line\n", window, cx);
14491 });
14492 executor.run_until_parked();
14493
14494 cx.assert_state_with_diff(
14495 r#"
14496 use some::mod1;
14497 use some::mod2;
14498
14499 const A: u32 = 42;
14500 const B: u32 = 42;
14501 - const C: u32 = 42;
14502 + const C: u32 = 43
14503 + new_line
14504 + ˇ
14505 const D: u32 = 42;
14506
14507
14508 fn main() {
14509 println!("hello");
14510
14511 println!("world");
14512 }"#
14513 .unindent(),
14514 );
14515}
14516
14517#[gpui::test]
14518async fn test_stage_and_unstage_added_file_hunk(
14519 executor: BackgroundExecutor,
14520 cx: &mut TestAppContext,
14521) {
14522 init_test(cx, |_| {});
14523
14524 let mut cx = EditorTestContext::new(cx).await;
14525 cx.update_editor(|editor, _, cx| {
14526 editor.set_expand_all_diff_hunks(cx);
14527 });
14528
14529 let working_copy = r#"
14530 ˇfn main() {
14531 println!("hello, world!");
14532 }
14533 "#
14534 .unindent();
14535
14536 cx.set_state(&working_copy);
14537 executor.run_until_parked();
14538
14539 cx.assert_state_with_diff(
14540 r#"
14541 + ˇfn main() {
14542 + println!("hello, world!");
14543 + }
14544 "#
14545 .unindent(),
14546 );
14547 cx.assert_index_text(None);
14548
14549 cx.update_editor(|editor, window, cx| {
14550 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14551 });
14552 executor.run_until_parked();
14553 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14554 cx.assert_state_with_diff(
14555 r#"
14556 + ˇfn main() {
14557 + println!("hello, world!");
14558 + }
14559 "#
14560 .unindent(),
14561 );
14562
14563 cx.update_editor(|editor, window, cx| {
14564 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14565 });
14566 executor.run_until_parked();
14567 cx.assert_index_text(None);
14568}
14569
14570async fn setup_indent_guides_editor(
14571 text: &str,
14572 cx: &mut TestAppContext,
14573) -> (BufferId, EditorTestContext) {
14574 init_test(cx, |_| {});
14575
14576 let mut cx = EditorTestContext::new(cx).await;
14577
14578 let buffer_id = cx.update_editor(|editor, window, cx| {
14579 editor.set_text(text, window, cx);
14580 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14581
14582 buffer_ids[0]
14583 });
14584
14585 (buffer_id, cx)
14586}
14587
14588fn assert_indent_guides(
14589 range: Range<u32>,
14590 expected: Vec<IndentGuide>,
14591 active_indices: Option<Vec<usize>>,
14592 cx: &mut EditorTestContext,
14593) {
14594 let indent_guides = cx.update_editor(|editor, window, cx| {
14595 let snapshot = editor.snapshot(window, cx).display_snapshot;
14596 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14597 editor,
14598 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14599 true,
14600 &snapshot,
14601 cx,
14602 );
14603
14604 indent_guides.sort_by(|a, b| {
14605 a.depth.cmp(&b.depth).then(
14606 a.start_row
14607 .cmp(&b.start_row)
14608 .then(a.end_row.cmp(&b.end_row)),
14609 )
14610 });
14611 indent_guides
14612 });
14613
14614 if let Some(expected) = active_indices {
14615 let active_indices = cx.update_editor(|editor, window, cx| {
14616 let snapshot = editor.snapshot(window, cx).display_snapshot;
14617 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14618 });
14619
14620 assert_eq!(
14621 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14622 expected,
14623 "Active indent guide indices do not match"
14624 );
14625 }
14626
14627 assert_eq!(indent_guides, expected, "Indent guides do not match");
14628}
14629
14630fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14631 IndentGuide {
14632 buffer_id,
14633 start_row: MultiBufferRow(start_row),
14634 end_row: MultiBufferRow(end_row),
14635 depth,
14636 tab_size: 4,
14637 settings: IndentGuideSettings {
14638 enabled: true,
14639 line_width: 1,
14640 active_line_width: 1,
14641 ..Default::default()
14642 },
14643 }
14644}
14645
14646#[gpui::test]
14647async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14648 let (buffer_id, mut cx) = setup_indent_guides_editor(
14649 &"
14650 fn main() {
14651 let a = 1;
14652 }"
14653 .unindent(),
14654 cx,
14655 )
14656 .await;
14657
14658 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14659}
14660
14661#[gpui::test]
14662async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14663 let (buffer_id, mut cx) = setup_indent_guides_editor(
14664 &"
14665 fn main() {
14666 let a = 1;
14667 let b = 2;
14668 }"
14669 .unindent(),
14670 cx,
14671 )
14672 .await;
14673
14674 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14675}
14676
14677#[gpui::test]
14678async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14679 let (buffer_id, mut cx) = setup_indent_guides_editor(
14680 &"
14681 fn main() {
14682 let a = 1;
14683 if a == 3 {
14684 let b = 2;
14685 } else {
14686 let c = 3;
14687 }
14688 }"
14689 .unindent(),
14690 cx,
14691 )
14692 .await;
14693
14694 assert_indent_guides(
14695 0..8,
14696 vec![
14697 indent_guide(buffer_id, 1, 6, 0),
14698 indent_guide(buffer_id, 3, 3, 1),
14699 indent_guide(buffer_id, 5, 5, 1),
14700 ],
14701 None,
14702 &mut cx,
14703 );
14704}
14705
14706#[gpui::test]
14707async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14708 let (buffer_id, mut cx) = setup_indent_guides_editor(
14709 &"
14710 fn main() {
14711 let a = 1;
14712 let b = 2;
14713 let c = 3;
14714 }"
14715 .unindent(),
14716 cx,
14717 )
14718 .await;
14719
14720 assert_indent_guides(
14721 0..5,
14722 vec![
14723 indent_guide(buffer_id, 1, 3, 0),
14724 indent_guide(buffer_id, 2, 2, 1),
14725 ],
14726 None,
14727 &mut cx,
14728 );
14729}
14730
14731#[gpui::test]
14732async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14733 let (buffer_id, mut cx) = setup_indent_guides_editor(
14734 &"
14735 fn main() {
14736 let a = 1;
14737
14738 let c = 3;
14739 }"
14740 .unindent(),
14741 cx,
14742 )
14743 .await;
14744
14745 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14746}
14747
14748#[gpui::test]
14749async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14750 let (buffer_id, mut cx) = setup_indent_guides_editor(
14751 &"
14752 fn main() {
14753 let a = 1;
14754
14755 let c = 3;
14756
14757 if a == 3 {
14758 let b = 2;
14759 } else {
14760 let c = 3;
14761 }
14762 }"
14763 .unindent(),
14764 cx,
14765 )
14766 .await;
14767
14768 assert_indent_guides(
14769 0..11,
14770 vec![
14771 indent_guide(buffer_id, 1, 9, 0),
14772 indent_guide(buffer_id, 6, 6, 1),
14773 indent_guide(buffer_id, 8, 8, 1),
14774 ],
14775 None,
14776 &mut cx,
14777 );
14778}
14779
14780#[gpui::test]
14781async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14782 let (buffer_id, mut cx) = setup_indent_guides_editor(
14783 &"
14784 fn main() {
14785 let a = 1;
14786
14787 let c = 3;
14788
14789 if a == 3 {
14790 let b = 2;
14791 } else {
14792 let c = 3;
14793 }
14794 }"
14795 .unindent(),
14796 cx,
14797 )
14798 .await;
14799
14800 assert_indent_guides(
14801 1..11,
14802 vec![
14803 indent_guide(buffer_id, 1, 9, 0),
14804 indent_guide(buffer_id, 6, 6, 1),
14805 indent_guide(buffer_id, 8, 8, 1),
14806 ],
14807 None,
14808 &mut cx,
14809 );
14810}
14811
14812#[gpui::test]
14813async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14814 let (buffer_id, mut cx) = setup_indent_guides_editor(
14815 &"
14816 fn main() {
14817 let a = 1;
14818
14819 let c = 3;
14820
14821 if a == 3 {
14822 let b = 2;
14823 } else {
14824 let c = 3;
14825 }
14826 }"
14827 .unindent(),
14828 cx,
14829 )
14830 .await;
14831
14832 assert_indent_guides(
14833 1..10,
14834 vec![
14835 indent_guide(buffer_id, 1, 9, 0),
14836 indent_guide(buffer_id, 6, 6, 1),
14837 indent_guide(buffer_id, 8, 8, 1),
14838 ],
14839 None,
14840 &mut cx,
14841 );
14842}
14843
14844#[gpui::test]
14845async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14846 let (buffer_id, mut cx) = setup_indent_guides_editor(
14847 &"
14848 block1
14849 block2
14850 block3
14851 block4
14852 block2
14853 block1
14854 block1"
14855 .unindent(),
14856 cx,
14857 )
14858 .await;
14859
14860 assert_indent_guides(
14861 1..10,
14862 vec![
14863 indent_guide(buffer_id, 1, 4, 0),
14864 indent_guide(buffer_id, 2, 3, 1),
14865 indent_guide(buffer_id, 3, 3, 2),
14866 ],
14867 None,
14868 &mut cx,
14869 );
14870}
14871
14872#[gpui::test]
14873async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14874 let (buffer_id, mut cx) = setup_indent_guides_editor(
14875 &"
14876 block1
14877 block2
14878 block3
14879
14880 block1
14881 block1"
14882 .unindent(),
14883 cx,
14884 )
14885 .await;
14886
14887 assert_indent_guides(
14888 0..6,
14889 vec![
14890 indent_guide(buffer_id, 1, 2, 0),
14891 indent_guide(buffer_id, 2, 2, 1),
14892 ],
14893 None,
14894 &mut cx,
14895 );
14896}
14897
14898#[gpui::test]
14899async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14900 let (buffer_id, mut cx) = setup_indent_guides_editor(
14901 &"
14902 block1
14903
14904
14905
14906 block2
14907 "
14908 .unindent(),
14909 cx,
14910 )
14911 .await;
14912
14913 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14914}
14915
14916#[gpui::test]
14917async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14918 let (buffer_id, mut cx) = setup_indent_guides_editor(
14919 &"
14920 def a:
14921 \tb = 3
14922 \tif True:
14923 \t\tc = 4
14924 \t\td = 5
14925 \tprint(b)
14926 "
14927 .unindent(),
14928 cx,
14929 )
14930 .await;
14931
14932 assert_indent_guides(
14933 0..6,
14934 vec![
14935 indent_guide(buffer_id, 1, 6, 0),
14936 indent_guide(buffer_id, 3, 4, 1),
14937 ],
14938 None,
14939 &mut cx,
14940 );
14941}
14942
14943#[gpui::test]
14944async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14945 let (buffer_id, mut cx) = setup_indent_guides_editor(
14946 &"
14947 fn main() {
14948 let a = 1;
14949 }"
14950 .unindent(),
14951 cx,
14952 )
14953 .await;
14954
14955 cx.update_editor(|editor, window, cx| {
14956 editor.change_selections(None, window, cx, |s| {
14957 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14958 });
14959 });
14960
14961 assert_indent_guides(
14962 0..3,
14963 vec![indent_guide(buffer_id, 1, 1, 0)],
14964 Some(vec![0]),
14965 &mut cx,
14966 );
14967}
14968
14969#[gpui::test]
14970async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14971 let (buffer_id, mut cx) = setup_indent_guides_editor(
14972 &"
14973 fn main() {
14974 if 1 == 2 {
14975 let a = 1;
14976 }
14977 }"
14978 .unindent(),
14979 cx,
14980 )
14981 .await;
14982
14983 cx.update_editor(|editor, window, cx| {
14984 editor.change_selections(None, window, cx, |s| {
14985 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14986 });
14987 });
14988
14989 assert_indent_guides(
14990 0..4,
14991 vec![
14992 indent_guide(buffer_id, 1, 3, 0),
14993 indent_guide(buffer_id, 2, 2, 1),
14994 ],
14995 Some(vec![1]),
14996 &mut cx,
14997 );
14998
14999 cx.update_editor(|editor, window, cx| {
15000 editor.change_selections(None, window, cx, |s| {
15001 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15002 });
15003 });
15004
15005 assert_indent_guides(
15006 0..4,
15007 vec![
15008 indent_guide(buffer_id, 1, 3, 0),
15009 indent_guide(buffer_id, 2, 2, 1),
15010 ],
15011 Some(vec![1]),
15012 &mut cx,
15013 );
15014
15015 cx.update_editor(|editor, window, cx| {
15016 editor.change_selections(None, window, cx, |s| {
15017 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15018 });
15019 });
15020
15021 assert_indent_guides(
15022 0..4,
15023 vec![
15024 indent_guide(buffer_id, 1, 3, 0),
15025 indent_guide(buffer_id, 2, 2, 1),
15026 ],
15027 Some(vec![0]),
15028 &mut cx,
15029 );
15030}
15031
15032#[gpui::test]
15033async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15034 let (buffer_id, mut cx) = setup_indent_guides_editor(
15035 &"
15036 fn main() {
15037 let a = 1;
15038
15039 let b = 2;
15040 }"
15041 .unindent(),
15042 cx,
15043 )
15044 .await;
15045
15046 cx.update_editor(|editor, window, cx| {
15047 editor.change_selections(None, window, cx, |s| {
15048 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15049 });
15050 });
15051
15052 assert_indent_guides(
15053 0..5,
15054 vec![indent_guide(buffer_id, 1, 3, 0)],
15055 Some(vec![0]),
15056 &mut cx,
15057 );
15058}
15059
15060#[gpui::test]
15061async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15062 let (buffer_id, mut cx) = setup_indent_guides_editor(
15063 &"
15064 def m:
15065 a = 1
15066 pass"
15067 .unindent(),
15068 cx,
15069 )
15070 .await;
15071
15072 cx.update_editor(|editor, window, cx| {
15073 editor.change_selections(None, window, cx, |s| {
15074 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15075 });
15076 });
15077
15078 assert_indent_guides(
15079 0..3,
15080 vec![indent_guide(buffer_id, 1, 2, 0)],
15081 Some(vec![0]),
15082 &mut cx,
15083 );
15084}
15085
15086#[gpui::test]
15087async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15088 init_test(cx, |_| {});
15089 let mut cx = EditorTestContext::new(cx).await;
15090 let text = indoc! {
15091 "
15092 impl A {
15093 fn b() {
15094 0;
15095 3;
15096 5;
15097 6;
15098 7;
15099 }
15100 }
15101 "
15102 };
15103 let base_text = indoc! {
15104 "
15105 impl A {
15106 fn b() {
15107 0;
15108 1;
15109 2;
15110 3;
15111 4;
15112 }
15113 fn c() {
15114 5;
15115 6;
15116 7;
15117 }
15118 }
15119 "
15120 };
15121
15122 cx.update_editor(|editor, window, cx| {
15123 editor.set_text(text, window, cx);
15124
15125 editor.buffer().update(cx, |multibuffer, cx| {
15126 let buffer = multibuffer.as_singleton().unwrap();
15127 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15128
15129 multibuffer.set_all_diff_hunks_expanded(cx);
15130 multibuffer.add_diff(diff, cx);
15131
15132 buffer.read(cx).remote_id()
15133 })
15134 });
15135 cx.run_until_parked();
15136
15137 cx.assert_state_with_diff(
15138 indoc! { "
15139 impl A {
15140 fn b() {
15141 0;
15142 - 1;
15143 - 2;
15144 3;
15145 - 4;
15146 - }
15147 - fn c() {
15148 5;
15149 6;
15150 7;
15151 }
15152 }
15153 ˇ"
15154 }
15155 .to_string(),
15156 );
15157
15158 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15159 editor
15160 .snapshot(window, cx)
15161 .buffer_snapshot
15162 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15163 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15164 .collect::<Vec<_>>()
15165 });
15166 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15167 assert_eq!(
15168 actual_guides,
15169 vec![
15170 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15171 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15172 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15173 ]
15174 );
15175}
15176
15177#[gpui::test]
15178async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15179 init_test(cx, |_| {});
15180 let mut cx = EditorTestContext::new(cx).await;
15181
15182 let diff_base = r#"
15183 a
15184 b
15185 c
15186 "#
15187 .unindent();
15188
15189 cx.set_state(
15190 &r#"
15191 ˇA
15192 b
15193 C
15194 "#
15195 .unindent(),
15196 );
15197 cx.set_head_text(&diff_base);
15198 cx.update_editor(|editor, window, cx| {
15199 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15200 });
15201 executor.run_until_parked();
15202
15203 let both_hunks_expanded = r#"
15204 - a
15205 + ˇA
15206 b
15207 - c
15208 + C
15209 "#
15210 .unindent();
15211
15212 cx.assert_state_with_diff(both_hunks_expanded.clone());
15213
15214 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15215 let snapshot = editor.snapshot(window, cx);
15216 let hunks = editor
15217 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15218 .collect::<Vec<_>>();
15219 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15220 let buffer_id = hunks[0].buffer_id;
15221 hunks
15222 .into_iter()
15223 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15224 .collect::<Vec<_>>()
15225 });
15226 assert_eq!(hunk_ranges.len(), 2);
15227
15228 cx.update_editor(|editor, _, cx| {
15229 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15230 });
15231 executor.run_until_parked();
15232
15233 let second_hunk_expanded = r#"
15234 ˇA
15235 b
15236 - c
15237 + C
15238 "#
15239 .unindent();
15240
15241 cx.assert_state_with_diff(second_hunk_expanded);
15242
15243 cx.update_editor(|editor, _, cx| {
15244 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15245 });
15246 executor.run_until_parked();
15247
15248 cx.assert_state_with_diff(both_hunks_expanded.clone());
15249
15250 cx.update_editor(|editor, _, cx| {
15251 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15252 });
15253 executor.run_until_parked();
15254
15255 let first_hunk_expanded = r#"
15256 - a
15257 + ˇA
15258 b
15259 C
15260 "#
15261 .unindent();
15262
15263 cx.assert_state_with_diff(first_hunk_expanded);
15264
15265 cx.update_editor(|editor, _, cx| {
15266 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15267 });
15268 executor.run_until_parked();
15269
15270 cx.assert_state_with_diff(both_hunks_expanded);
15271
15272 cx.set_state(
15273 &r#"
15274 ˇA
15275 b
15276 "#
15277 .unindent(),
15278 );
15279 cx.run_until_parked();
15280
15281 // TODO this cursor position seems bad
15282 cx.assert_state_with_diff(
15283 r#"
15284 - ˇa
15285 + A
15286 b
15287 "#
15288 .unindent(),
15289 );
15290
15291 cx.update_editor(|editor, window, cx| {
15292 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15293 });
15294
15295 cx.assert_state_with_diff(
15296 r#"
15297 - ˇa
15298 + A
15299 b
15300 - c
15301 "#
15302 .unindent(),
15303 );
15304
15305 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15306 let snapshot = editor.snapshot(window, cx);
15307 let hunks = editor
15308 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15309 .collect::<Vec<_>>();
15310 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15311 let buffer_id = hunks[0].buffer_id;
15312 hunks
15313 .into_iter()
15314 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15315 .collect::<Vec<_>>()
15316 });
15317 assert_eq!(hunk_ranges.len(), 2);
15318
15319 cx.update_editor(|editor, _, cx| {
15320 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15321 });
15322 executor.run_until_parked();
15323
15324 cx.assert_state_with_diff(
15325 r#"
15326 - ˇa
15327 + A
15328 b
15329 "#
15330 .unindent(),
15331 );
15332}
15333
15334#[gpui::test]
15335async fn test_toggle_deletion_hunk_at_start_of_file(
15336 executor: BackgroundExecutor,
15337 cx: &mut TestAppContext,
15338) {
15339 init_test(cx, |_| {});
15340 let mut cx = EditorTestContext::new(cx).await;
15341
15342 let diff_base = r#"
15343 a
15344 b
15345 c
15346 "#
15347 .unindent();
15348
15349 cx.set_state(
15350 &r#"
15351 ˇb
15352 c
15353 "#
15354 .unindent(),
15355 );
15356 cx.set_head_text(&diff_base);
15357 cx.update_editor(|editor, window, cx| {
15358 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15359 });
15360 executor.run_until_parked();
15361
15362 let hunk_expanded = r#"
15363 - a
15364 ˇb
15365 c
15366 "#
15367 .unindent();
15368
15369 cx.assert_state_with_diff(hunk_expanded.clone());
15370
15371 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15372 let snapshot = editor.snapshot(window, cx);
15373 let hunks = editor
15374 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15375 .collect::<Vec<_>>();
15376 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15377 let buffer_id = hunks[0].buffer_id;
15378 hunks
15379 .into_iter()
15380 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15381 .collect::<Vec<_>>()
15382 });
15383 assert_eq!(hunk_ranges.len(), 1);
15384
15385 cx.update_editor(|editor, _, cx| {
15386 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15387 });
15388 executor.run_until_parked();
15389
15390 let hunk_collapsed = r#"
15391 ˇb
15392 c
15393 "#
15394 .unindent();
15395
15396 cx.assert_state_with_diff(hunk_collapsed);
15397
15398 cx.update_editor(|editor, _, cx| {
15399 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15400 });
15401 executor.run_until_parked();
15402
15403 cx.assert_state_with_diff(hunk_expanded.clone());
15404}
15405
15406#[gpui::test]
15407async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15408 init_test(cx, |_| {});
15409
15410 let fs = FakeFs::new(cx.executor());
15411 fs.insert_tree(
15412 path!("/test"),
15413 json!({
15414 ".git": {},
15415 "file-1": "ONE\n",
15416 "file-2": "TWO\n",
15417 "file-3": "THREE\n",
15418 }),
15419 )
15420 .await;
15421
15422 fs.set_head_for_repo(
15423 path!("/test/.git").as_ref(),
15424 &[
15425 ("file-1".into(), "one\n".into()),
15426 ("file-2".into(), "two\n".into()),
15427 ("file-3".into(), "three\n".into()),
15428 ],
15429 );
15430
15431 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15432 let mut buffers = vec![];
15433 for i in 1..=3 {
15434 let buffer = project
15435 .update(cx, |project, cx| {
15436 let path = format!(path!("/test/file-{}"), i);
15437 project.open_local_buffer(path, cx)
15438 })
15439 .await
15440 .unwrap();
15441 buffers.push(buffer);
15442 }
15443
15444 let multibuffer = cx.new(|cx| {
15445 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15446 multibuffer.set_all_diff_hunks_expanded(cx);
15447 for buffer in &buffers {
15448 let snapshot = buffer.read(cx).snapshot();
15449 multibuffer.set_excerpts_for_path(
15450 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15451 buffer.clone(),
15452 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15453 DEFAULT_MULTIBUFFER_CONTEXT,
15454 cx,
15455 );
15456 }
15457 multibuffer
15458 });
15459
15460 let editor = cx.add_window(|window, cx| {
15461 Editor::new(
15462 EditorMode::Full,
15463 multibuffer,
15464 Some(project),
15465 true,
15466 window,
15467 cx,
15468 )
15469 });
15470 cx.run_until_parked();
15471
15472 let snapshot = editor
15473 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15474 .unwrap();
15475 let hunks = snapshot
15476 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15477 .map(|hunk| match hunk {
15478 DisplayDiffHunk::Unfolded {
15479 display_row_range, ..
15480 } => display_row_range,
15481 DisplayDiffHunk::Folded { .. } => unreachable!(),
15482 })
15483 .collect::<Vec<_>>();
15484 assert_eq!(
15485 hunks,
15486 [
15487 DisplayRow(3)..DisplayRow(5),
15488 DisplayRow(10)..DisplayRow(12),
15489 DisplayRow(17)..DisplayRow(19),
15490 ]
15491 );
15492}
15493
15494#[gpui::test]
15495async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15496 init_test(cx, |_| {});
15497
15498 let mut cx = EditorTestContext::new(cx).await;
15499 cx.set_head_text(indoc! { "
15500 one
15501 two
15502 three
15503 four
15504 five
15505 "
15506 });
15507 cx.set_index_text(indoc! { "
15508 one
15509 two
15510 three
15511 four
15512 five
15513 "
15514 });
15515 cx.set_state(indoc! {"
15516 one
15517 TWO
15518 ˇTHREE
15519 FOUR
15520 five
15521 "});
15522 cx.run_until_parked();
15523 cx.update_editor(|editor, window, cx| {
15524 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15525 });
15526 cx.run_until_parked();
15527 cx.assert_index_text(Some(indoc! {"
15528 one
15529 TWO
15530 THREE
15531 FOUR
15532 five
15533 "}));
15534 cx.set_state(indoc! { "
15535 one
15536 TWO
15537 ˇTHREE-HUNDRED
15538 FOUR
15539 five
15540 "});
15541 cx.run_until_parked();
15542 cx.update_editor(|editor, window, cx| {
15543 let snapshot = editor.snapshot(window, cx);
15544 let hunks = editor
15545 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15546 .collect::<Vec<_>>();
15547 assert_eq!(hunks.len(), 1);
15548 assert_eq!(
15549 hunks[0].status(),
15550 DiffHunkStatus {
15551 kind: DiffHunkStatusKind::Modified,
15552 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15553 }
15554 );
15555
15556 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15557 });
15558 cx.run_until_parked();
15559 cx.assert_index_text(Some(indoc! {"
15560 one
15561 TWO
15562 THREE-HUNDRED
15563 FOUR
15564 five
15565 "}));
15566}
15567
15568#[gpui::test]
15569fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15570 init_test(cx, |_| {});
15571
15572 let editor = cx.add_window(|window, cx| {
15573 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15574 build_editor(buffer, window, cx)
15575 });
15576
15577 let render_args = Arc::new(Mutex::new(None));
15578 let snapshot = editor
15579 .update(cx, |editor, window, cx| {
15580 let snapshot = editor.buffer().read(cx).snapshot(cx);
15581 let range =
15582 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15583
15584 struct RenderArgs {
15585 row: MultiBufferRow,
15586 folded: bool,
15587 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15588 }
15589
15590 let crease = Crease::inline(
15591 range,
15592 FoldPlaceholder::test(),
15593 {
15594 let toggle_callback = render_args.clone();
15595 move |row, folded, callback, _window, _cx| {
15596 *toggle_callback.lock() = Some(RenderArgs {
15597 row,
15598 folded,
15599 callback,
15600 });
15601 div()
15602 }
15603 },
15604 |_row, _folded, _window, _cx| div(),
15605 );
15606
15607 editor.insert_creases(Some(crease), cx);
15608 let snapshot = editor.snapshot(window, cx);
15609 let _div = snapshot.render_crease_toggle(
15610 MultiBufferRow(1),
15611 false,
15612 cx.entity().clone(),
15613 window,
15614 cx,
15615 );
15616 snapshot
15617 })
15618 .unwrap();
15619
15620 let render_args = render_args.lock().take().unwrap();
15621 assert_eq!(render_args.row, MultiBufferRow(1));
15622 assert!(!render_args.folded);
15623 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15624
15625 cx.update_window(*editor, |_, window, cx| {
15626 (render_args.callback)(true, window, cx)
15627 })
15628 .unwrap();
15629 let snapshot = editor
15630 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15631 .unwrap();
15632 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15633
15634 cx.update_window(*editor, |_, window, cx| {
15635 (render_args.callback)(false, window, cx)
15636 })
15637 .unwrap();
15638 let snapshot = editor
15639 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15640 .unwrap();
15641 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15642}
15643
15644#[gpui::test]
15645async fn test_input_text(cx: &mut TestAppContext) {
15646 init_test(cx, |_| {});
15647 let mut cx = EditorTestContext::new(cx).await;
15648
15649 cx.set_state(
15650 &r#"ˇone
15651 two
15652
15653 three
15654 fourˇ
15655 five
15656
15657 siˇx"#
15658 .unindent(),
15659 );
15660
15661 cx.dispatch_action(HandleInput(String::new()));
15662 cx.assert_editor_state(
15663 &r#"ˇone
15664 two
15665
15666 three
15667 fourˇ
15668 five
15669
15670 siˇx"#
15671 .unindent(),
15672 );
15673
15674 cx.dispatch_action(HandleInput("AAAA".to_string()));
15675 cx.assert_editor_state(
15676 &r#"AAAAˇone
15677 two
15678
15679 three
15680 fourAAAAˇ
15681 five
15682
15683 siAAAAˇx"#
15684 .unindent(),
15685 );
15686}
15687
15688#[gpui::test]
15689async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15690 init_test(cx, |_| {});
15691
15692 let mut cx = EditorTestContext::new(cx).await;
15693 cx.set_state(
15694 r#"let foo = 1;
15695let foo = 2;
15696let foo = 3;
15697let fooˇ = 4;
15698let foo = 5;
15699let foo = 6;
15700let foo = 7;
15701let foo = 8;
15702let foo = 9;
15703let foo = 10;
15704let foo = 11;
15705let foo = 12;
15706let foo = 13;
15707let foo = 14;
15708let foo = 15;"#,
15709 );
15710
15711 cx.update_editor(|e, window, cx| {
15712 assert_eq!(
15713 e.next_scroll_position,
15714 NextScrollCursorCenterTopBottom::Center,
15715 "Default next scroll direction is center",
15716 );
15717
15718 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15719 assert_eq!(
15720 e.next_scroll_position,
15721 NextScrollCursorCenterTopBottom::Top,
15722 "After center, next scroll direction should be top",
15723 );
15724
15725 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15726 assert_eq!(
15727 e.next_scroll_position,
15728 NextScrollCursorCenterTopBottom::Bottom,
15729 "After top, next scroll direction should be bottom",
15730 );
15731
15732 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15733 assert_eq!(
15734 e.next_scroll_position,
15735 NextScrollCursorCenterTopBottom::Center,
15736 "After bottom, scrolling should start over",
15737 );
15738
15739 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15740 assert_eq!(
15741 e.next_scroll_position,
15742 NextScrollCursorCenterTopBottom::Top,
15743 "Scrolling continues if retriggered fast enough"
15744 );
15745 });
15746
15747 cx.executor()
15748 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15749 cx.executor().run_until_parked();
15750 cx.update_editor(|e, _, _| {
15751 assert_eq!(
15752 e.next_scroll_position,
15753 NextScrollCursorCenterTopBottom::Center,
15754 "If scrolling is not triggered fast enough, it should reset"
15755 );
15756 });
15757}
15758
15759#[gpui::test]
15760async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15761 init_test(cx, |_| {});
15762 let mut cx = EditorLspTestContext::new_rust(
15763 lsp::ServerCapabilities {
15764 definition_provider: Some(lsp::OneOf::Left(true)),
15765 references_provider: Some(lsp::OneOf::Left(true)),
15766 ..lsp::ServerCapabilities::default()
15767 },
15768 cx,
15769 )
15770 .await;
15771
15772 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15773 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15774 move |params, _| async move {
15775 if empty_go_to_definition {
15776 Ok(None)
15777 } else {
15778 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15779 uri: params.text_document_position_params.text_document.uri,
15780 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15781 })))
15782 }
15783 },
15784 );
15785 let references =
15786 cx.lsp
15787 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15788 Ok(Some(vec![lsp::Location {
15789 uri: params.text_document_position.text_document.uri,
15790 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15791 }]))
15792 });
15793 (go_to_definition, references)
15794 };
15795
15796 cx.set_state(
15797 &r#"fn one() {
15798 let mut a = ˇtwo();
15799 }
15800
15801 fn two() {}"#
15802 .unindent(),
15803 );
15804 set_up_lsp_handlers(false, &mut cx);
15805 let navigated = cx
15806 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15807 .await
15808 .expect("Failed to navigate to definition");
15809 assert_eq!(
15810 navigated,
15811 Navigated::Yes,
15812 "Should have navigated to definition from the GetDefinition response"
15813 );
15814 cx.assert_editor_state(
15815 &r#"fn one() {
15816 let mut a = two();
15817 }
15818
15819 fn «twoˇ»() {}"#
15820 .unindent(),
15821 );
15822
15823 let editors = cx.update_workspace(|workspace, _, cx| {
15824 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15825 });
15826 cx.update_editor(|_, _, test_editor_cx| {
15827 assert_eq!(
15828 editors.len(),
15829 1,
15830 "Initially, only one, test, editor should be open in the workspace"
15831 );
15832 assert_eq!(
15833 test_editor_cx.entity(),
15834 editors.last().expect("Asserted len is 1").clone()
15835 );
15836 });
15837
15838 set_up_lsp_handlers(true, &mut cx);
15839 let navigated = cx
15840 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15841 .await
15842 .expect("Failed to navigate to lookup references");
15843 assert_eq!(
15844 navigated,
15845 Navigated::Yes,
15846 "Should have navigated to references as a fallback after empty GoToDefinition response"
15847 );
15848 // We should not change the selections in the existing file,
15849 // if opening another milti buffer with the references
15850 cx.assert_editor_state(
15851 &r#"fn one() {
15852 let mut a = two();
15853 }
15854
15855 fn «twoˇ»() {}"#
15856 .unindent(),
15857 );
15858 let editors = cx.update_workspace(|workspace, _, cx| {
15859 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15860 });
15861 cx.update_editor(|_, _, test_editor_cx| {
15862 assert_eq!(
15863 editors.len(),
15864 2,
15865 "After falling back to references search, we open a new editor with the results"
15866 );
15867 let references_fallback_text = editors
15868 .into_iter()
15869 .find(|new_editor| *new_editor != test_editor_cx.entity())
15870 .expect("Should have one non-test editor now")
15871 .read(test_editor_cx)
15872 .text(test_editor_cx);
15873 assert_eq!(
15874 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15875 "Should use the range from the references response and not the GoToDefinition one"
15876 );
15877 });
15878}
15879
15880#[gpui::test]
15881async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15882 init_test(cx, |_| {});
15883
15884 let language = Arc::new(Language::new(
15885 LanguageConfig::default(),
15886 Some(tree_sitter_rust::LANGUAGE.into()),
15887 ));
15888
15889 let text = r#"
15890 #[cfg(test)]
15891 mod tests() {
15892 #[test]
15893 fn runnable_1() {
15894 let a = 1;
15895 }
15896
15897 #[test]
15898 fn runnable_2() {
15899 let a = 1;
15900 let b = 2;
15901 }
15902 }
15903 "#
15904 .unindent();
15905
15906 let fs = FakeFs::new(cx.executor());
15907 fs.insert_file("/file.rs", Default::default()).await;
15908
15909 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15910 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15911 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15912 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15913 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15914
15915 let editor = cx.new_window_entity(|window, cx| {
15916 Editor::new(
15917 EditorMode::Full,
15918 multi_buffer,
15919 Some(project.clone()),
15920 true,
15921 window,
15922 cx,
15923 )
15924 });
15925
15926 editor.update_in(cx, |editor, window, cx| {
15927 let snapshot = editor.buffer().read(cx).snapshot(cx);
15928 editor.tasks.insert(
15929 (buffer.read(cx).remote_id(), 3),
15930 RunnableTasks {
15931 templates: vec![],
15932 offset: snapshot.anchor_before(43),
15933 column: 0,
15934 extra_variables: HashMap::default(),
15935 context_range: BufferOffset(43)..BufferOffset(85),
15936 },
15937 );
15938 editor.tasks.insert(
15939 (buffer.read(cx).remote_id(), 8),
15940 RunnableTasks {
15941 templates: vec![],
15942 offset: snapshot.anchor_before(86),
15943 column: 0,
15944 extra_variables: HashMap::default(),
15945 context_range: BufferOffset(86)..BufferOffset(191),
15946 },
15947 );
15948
15949 // Test finding task when cursor is inside function body
15950 editor.change_selections(None, window, cx, |s| {
15951 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15952 });
15953 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15954 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15955
15956 // Test finding task when cursor is on function name
15957 editor.change_selections(None, window, cx, |s| {
15958 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15959 });
15960 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15961 assert_eq!(row, 8, "Should find task when cursor is on function name");
15962 });
15963}
15964
15965#[gpui::test]
15966async fn test_folding_buffers(cx: &mut TestAppContext) {
15967 init_test(cx, |_| {});
15968
15969 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15970 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15971 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15972
15973 let fs = FakeFs::new(cx.executor());
15974 fs.insert_tree(
15975 path!("/a"),
15976 json!({
15977 "first.rs": sample_text_1,
15978 "second.rs": sample_text_2,
15979 "third.rs": sample_text_3,
15980 }),
15981 )
15982 .await;
15983 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15984 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15985 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15986 let worktree = project.update(cx, |project, cx| {
15987 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
15988 assert_eq!(worktrees.len(), 1);
15989 worktrees.pop().unwrap()
15990 });
15991 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
15992
15993 let buffer_1 = project
15994 .update(cx, |project, cx| {
15995 project.open_buffer((worktree_id, "first.rs"), cx)
15996 })
15997 .await
15998 .unwrap();
15999 let buffer_2 = project
16000 .update(cx, |project, cx| {
16001 project.open_buffer((worktree_id, "second.rs"), cx)
16002 })
16003 .await
16004 .unwrap();
16005 let buffer_3 = project
16006 .update(cx, |project, cx| {
16007 project.open_buffer((worktree_id, "third.rs"), cx)
16008 })
16009 .await
16010 .unwrap();
16011
16012 let multi_buffer = cx.new(|cx| {
16013 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16014 multi_buffer.push_excerpts(
16015 buffer_1.clone(),
16016 [
16017 ExcerptRange {
16018 context: Point::new(0, 0)..Point::new(3, 0),
16019 primary: None,
16020 },
16021 ExcerptRange {
16022 context: Point::new(5, 0)..Point::new(7, 0),
16023 primary: None,
16024 },
16025 ExcerptRange {
16026 context: Point::new(9, 0)..Point::new(10, 4),
16027 primary: None,
16028 },
16029 ],
16030 cx,
16031 );
16032 multi_buffer.push_excerpts(
16033 buffer_2.clone(),
16034 [
16035 ExcerptRange {
16036 context: Point::new(0, 0)..Point::new(3, 0),
16037 primary: None,
16038 },
16039 ExcerptRange {
16040 context: Point::new(5, 0)..Point::new(7, 0),
16041 primary: None,
16042 },
16043 ExcerptRange {
16044 context: Point::new(9, 0)..Point::new(10, 4),
16045 primary: None,
16046 },
16047 ],
16048 cx,
16049 );
16050 multi_buffer.push_excerpts(
16051 buffer_3.clone(),
16052 [
16053 ExcerptRange {
16054 context: Point::new(0, 0)..Point::new(3, 0),
16055 primary: None,
16056 },
16057 ExcerptRange {
16058 context: Point::new(5, 0)..Point::new(7, 0),
16059 primary: None,
16060 },
16061 ExcerptRange {
16062 context: Point::new(9, 0)..Point::new(10, 4),
16063 primary: None,
16064 },
16065 ],
16066 cx,
16067 );
16068 multi_buffer
16069 });
16070 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16071 Editor::new(
16072 EditorMode::Full,
16073 multi_buffer.clone(),
16074 Some(project.clone()),
16075 true,
16076 window,
16077 cx,
16078 )
16079 });
16080
16081 assert_eq!(
16082 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16083 "\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",
16084 );
16085
16086 multi_buffer_editor.update(cx, |editor, cx| {
16087 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16088 });
16089 assert_eq!(
16090 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16091 "\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",
16092 "After folding the first buffer, its text should not be displayed"
16093 );
16094
16095 multi_buffer_editor.update(cx, |editor, cx| {
16096 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16097 });
16098 assert_eq!(
16099 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16100 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16101 "After folding the second buffer, its text should not be displayed"
16102 );
16103
16104 multi_buffer_editor.update(cx, |editor, cx| {
16105 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16106 });
16107 assert_eq!(
16108 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16109 "\n\n\n\n\n",
16110 "After folding the third buffer, its text should not be displayed"
16111 );
16112
16113 // Emulate selection inside the fold logic, that should work
16114 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16115 editor
16116 .snapshot(window, cx)
16117 .next_line_boundary(Point::new(0, 4));
16118 });
16119
16120 multi_buffer_editor.update(cx, |editor, cx| {
16121 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16122 });
16123 assert_eq!(
16124 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16125 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16126 "After unfolding the second buffer, its text should be displayed"
16127 );
16128
16129 // Typing inside of buffer 1 causes that buffer to be unfolded.
16130 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16131 assert_eq!(
16132 multi_buffer
16133 .read(cx)
16134 .snapshot(cx)
16135 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16136 .collect::<String>(),
16137 "bbbb"
16138 );
16139 editor.change_selections(None, window, cx, |selections| {
16140 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16141 });
16142 editor.handle_input("B", window, cx);
16143 });
16144
16145 assert_eq!(
16146 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16147 "\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",
16148 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16149 );
16150
16151 multi_buffer_editor.update(cx, |editor, cx| {
16152 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16153 });
16154 assert_eq!(
16155 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16156 "\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",
16157 "After unfolding the all buffers, all original text should be displayed"
16158 );
16159}
16160
16161#[gpui::test]
16162async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16163 init_test(cx, |_| {});
16164
16165 let sample_text_1 = "1111\n2222\n3333".to_string();
16166 let sample_text_2 = "4444\n5555\n6666".to_string();
16167 let sample_text_3 = "7777\n8888\n9999".to_string();
16168
16169 let fs = FakeFs::new(cx.executor());
16170 fs.insert_tree(
16171 path!("/a"),
16172 json!({
16173 "first.rs": sample_text_1,
16174 "second.rs": sample_text_2,
16175 "third.rs": sample_text_3,
16176 }),
16177 )
16178 .await;
16179 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16180 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16181 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16182 let worktree = project.update(cx, |project, cx| {
16183 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16184 assert_eq!(worktrees.len(), 1);
16185 worktrees.pop().unwrap()
16186 });
16187 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16188
16189 let buffer_1 = project
16190 .update(cx, |project, cx| {
16191 project.open_buffer((worktree_id, "first.rs"), cx)
16192 })
16193 .await
16194 .unwrap();
16195 let buffer_2 = project
16196 .update(cx, |project, cx| {
16197 project.open_buffer((worktree_id, "second.rs"), cx)
16198 })
16199 .await
16200 .unwrap();
16201 let buffer_3 = project
16202 .update(cx, |project, cx| {
16203 project.open_buffer((worktree_id, "third.rs"), cx)
16204 })
16205 .await
16206 .unwrap();
16207
16208 let multi_buffer = cx.new(|cx| {
16209 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16210 multi_buffer.push_excerpts(
16211 buffer_1.clone(),
16212 [ExcerptRange {
16213 context: Point::new(0, 0)..Point::new(3, 0),
16214 primary: None,
16215 }],
16216 cx,
16217 );
16218 multi_buffer.push_excerpts(
16219 buffer_2.clone(),
16220 [ExcerptRange {
16221 context: Point::new(0, 0)..Point::new(3, 0),
16222 primary: None,
16223 }],
16224 cx,
16225 );
16226 multi_buffer.push_excerpts(
16227 buffer_3.clone(),
16228 [ExcerptRange {
16229 context: Point::new(0, 0)..Point::new(3, 0),
16230 primary: None,
16231 }],
16232 cx,
16233 );
16234 multi_buffer
16235 });
16236
16237 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16238 Editor::new(
16239 EditorMode::Full,
16240 multi_buffer,
16241 Some(project.clone()),
16242 true,
16243 window,
16244 cx,
16245 )
16246 });
16247
16248 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16249 assert_eq!(
16250 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16251 full_text,
16252 );
16253
16254 multi_buffer_editor.update(cx, |editor, cx| {
16255 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16256 });
16257 assert_eq!(
16258 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16259 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16260 "After folding the first buffer, its text should not be displayed"
16261 );
16262
16263 multi_buffer_editor.update(cx, |editor, cx| {
16264 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16265 });
16266
16267 assert_eq!(
16268 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16269 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16270 "After folding the second buffer, its text should not be displayed"
16271 );
16272
16273 multi_buffer_editor.update(cx, |editor, cx| {
16274 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16275 });
16276 assert_eq!(
16277 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16278 "\n\n\n\n\n",
16279 "After folding the third buffer, its text should not be displayed"
16280 );
16281
16282 multi_buffer_editor.update(cx, |editor, cx| {
16283 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16284 });
16285 assert_eq!(
16286 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16287 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16288 "After unfolding the second buffer, its text should be displayed"
16289 );
16290
16291 multi_buffer_editor.update(cx, |editor, cx| {
16292 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16293 });
16294 assert_eq!(
16295 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16296 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16297 "After unfolding the first buffer, its text should be displayed"
16298 );
16299
16300 multi_buffer_editor.update(cx, |editor, cx| {
16301 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16302 });
16303 assert_eq!(
16304 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16305 full_text,
16306 "After unfolding all buffers, all original text should be displayed"
16307 );
16308}
16309
16310#[gpui::test]
16311async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16312 init_test(cx, |_| {});
16313
16314 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16315
16316 let fs = FakeFs::new(cx.executor());
16317 fs.insert_tree(
16318 path!("/a"),
16319 json!({
16320 "main.rs": sample_text,
16321 }),
16322 )
16323 .await;
16324 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16325 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16326 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16327 let worktree = project.update(cx, |project, cx| {
16328 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16329 assert_eq!(worktrees.len(), 1);
16330 worktrees.pop().unwrap()
16331 });
16332 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16333
16334 let buffer_1 = project
16335 .update(cx, |project, cx| {
16336 project.open_buffer((worktree_id, "main.rs"), cx)
16337 })
16338 .await
16339 .unwrap();
16340
16341 let multi_buffer = cx.new(|cx| {
16342 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16343 multi_buffer.push_excerpts(
16344 buffer_1.clone(),
16345 [ExcerptRange {
16346 context: Point::new(0, 0)
16347 ..Point::new(
16348 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16349 0,
16350 ),
16351 primary: None,
16352 }],
16353 cx,
16354 );
16355 multi_buffer
16356 });
16357 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16358 Editor::new(
16359 EditorMode::Full,
16360 multi_buffer,
16361 Some(project.clone()),
16362 true,
16363 window,
16364 cx,
16365 )
16366 });
16367
16368 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16369 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16370 enum TestHighlight {}
16371 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16372 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16373 editor.highlight_text::<TestHighlight>(
16374 vec![highlight_range.clone()],
16375 HighlightStyle::color(Hsla::green()),
16376 cx,
16377 );
16378 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16379 });
16380
16381 let full_text = format!("\n\n\n{sample_text}\n");
16382 assert_eq!(
16383 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16384 full_text,
16385 );
16386}
16387
16388#[gpui::test]
16389async fn test_inline_completion_text(cx: &mut TestAppContext) {
16390 init_test(cx, |_| {});
16391
16392 // Simple insertion
16393 assert_highlighted_edits(
16394 "Hello, world!",
16395 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16396 true,
16397 cx,
16398 |highlighted_edits, cx| {
16399 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16400 assert_eq!(highlighted_edits.highlights.len(), 1);
16401 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16402 assert_eq!(
16403 highlighted_edits.highlights[0].1.background_color,
16404 Some(cx.theme().status().created_background)
16405 );
16406 },
16407 )
16408 .await;
16409
16410 // Replacement
16411 assert_highlighted_edits(
16412 "This is a test.",
16413 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16414 false,
16415 cx,
16416 |highlighted_edits, cx| {
16417 assert_eq!(highlighted_edits.text, "That is a test.");
16418 assert_eq!(highlighted_edits.highlights.len(), 1);
16419 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16420 assert_eq!(
16421 highlighted_edits.highlights[0].1.background_color,
16422 Some(cx.theme().status().created_background)
16423 );
16424 },
16425 )
16426 .await;
16427
16428 // Multiple edits
16429 assert_highlighted_edits(
16430 "Hello, world!",
16431 vec![
16432 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16433 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16434 ],
16435 false,
16436 cx,
16437 |highlighted_edits, cx| {
16438 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16439 assert_eq!(highlighted_edits.highlights.len(), 2);
16440 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16441 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16442 assert_eq!(
16443 highlighted_edits.highlights[0].1.background_color,
16444 Some(cx.theme().status().created_background)
16445 );
16446 assert_eq!(
16447 highlighted_edits.highlights[1].1.background_color,
16448 Some(cx.theme().status().created_background)
16449 );
16450 },
16451 )
16452 .await;
16453
16454 // Multiple lines with edits
16455 assert_highlighted_edits(
16456 "First line\nSecond line\nThird line\nFourth line",
16457 vec![
16458 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16459 (
16460 Point::new(2, 0)..Point::new(2, 10),
16461 "New third line".to_string(),
16462 ),
16463 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16464 ],
16465 false,
16466 cx,
16467 |highlighted_edits, cx| {
16468 assert_eq!(
16469 highlighted_edits.text,
16470 "Second modified\nNew third line\nFourth updated line"
16471 );
16472 assert_eq!(highlighted_edits.highlights.len(), 3);
16473 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16474 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16475 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16476 for highlight in &highlighted_edits.highlights {
16477 assert_eq!(
16478 highlight.1.background_color,
16479 Some(cx.theme().status().created_background)
16480 );
16481 }
16482 },
16483 )
16484 .await;
16485}
16486
16487#[gpui::test]
16488async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16489 init_test(cx, |_| {});
16490
16491 // Deletion
16492 assert_highlighted_edits(
16493 "Hello, world!",
16494 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16495 true,
16496 cx,
16497 |highlighted_edits, cx| {
16498 assert_eq!(highlighted_edits.text, "Hello, world!");
16499 assert_eq!(highlighted_edits.highlights.len(), 1);
16500 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16501 assert_eq!(
16502 highlighted_edits.highlights[0].1.background_color,
16503 Some(cx.theme().status().deleted_background)
16504 );
16505 },
16506 )
16507 .await;
16508
16509 // Insertion
16510 assert_highlighted_edits(
16511 "Hello, world!",
16512 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16513 true,
16514 cx,
16515 |highlighted_edits, cx| {
16516 assert_eq!(highlighted_edits.highlights.len(), 1);
16517 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16518 assert_eq!(
16519 highlighted_edits.highlights[0].1.background_color,
16520 Some(cx.theme().status().created_background)
16521 );
16522 },
16523 )
16524 .await;
16525}
16526
16527async fn assert_highlighted_edits(
16528 text: &str,
16529 edits: Vec<(Range<Point>, String)>,
16530 include_deletions: bool,
16531 cx: &mut TestAppContext,
16532 assertion_fn: impl Fn(HighlightedText, &App),
16533) {
16534 let window = cx.add_window(|window, cx| {
16535 let buffer = MultiBuffer::build_simple(text, cx);
16536 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16537 });
16538 let cx = &mut VisualTestContext::from_window(*window, cx);
16539
16540 let (buffer, snapshot) = window
16541 .update(cx, |editor, _window, cx| {
16542 (
16543 editor.buffer().clone(),
16544 editor.buffer().read(cx).snapshot(cx),
16545 )
16546 })
16547 .unwrap();
16548
16549 let edits = edits
16550 .into_iter()
16551 .map(|(range, edit)| {
16552 (
16553 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16554 edit,
16555 )
16556 })
16557 .collect::<Vec<_>>();
16558
16559 let text_anchor_edits = edits
16560 .clone()
16561 .into_iter()
16562 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16563 .collect::<Vec<_>>();
16564
16565 let edit_preview = window
16566 .update(cx, |_, _window, cx| {
16567 buffer
16568 .read(cx)
16569 .as_singleton()
16570 .unwrap()
16571 .read(cx)
16572 .preview_edits(text_anchor_edits.into(), cx)
16573 })
16574 .unwrap()
16575 .await;
16576
16577 cx.update(|_window, cx| {
16578 let highlighted_edits = inline_completion_edit_text(
16579 &snapshot.as_singleton().unwrap().2,
16580 &edits,
16581 &edit_preview,
16582 include_deletions,
16583 cx,
16584 );
16585 assertion_fn(highlighted_edits, cx)
16586 });
16587}
16588
16589#[gpui::test]
16590async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16591 init_test(cx, |_| {});
16592 let capabilities = lsp::ServerCapabilities {
16593 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16594 prepare_provider: Some(true),
16595 work_done_progress_options: Default::default(),
16596 })),
16597 ..Default::default()
16598 };
16599 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16600
16601 cx.set_state(indoc! {"
16602 struct Fˇoo {}
16603 "});
16604
16605 cx.update_editor(|editor, _, cx| {
16606 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16607 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16608 editor.highlight_background::<DocumentHighlightRead>(
16609 &[highlight_range],
16610 |c| c.editor_document_highlight_read_background,
16611 cx,
16612 );
16613 });
16614
16615 let mut prepare_rename_handler =
16616 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16617 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16618 start: lsp::Position {
16619 line: 0,
16620 character: 7,
16621 },
16622 end: lsp::Position {
16623 line: 0,
16624 character: 10,
16625 },
16626 })))
16627 });
16628 let prepare_rename_task = cx
16629 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16630 .expect("Prepare rename was not started");
16631 prepare_rename_handler.next().await.unwrap();
16632 prepare_rename_task.await.expect("Prepare rename failed");
16633
16634 let mut rename_handler =
16635 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16636 let edit = lsp::TextEdit {
16637 range: lsp::Range {
16638 start: lsp::Position {
16639 line: 0,
16640 character: 7,
16641 },
16642 end: lsp::Position {
16643 line: 0,
16644 character: 10,
16645 },
16646 },
16647 new_text: "FooRenamed".to_string(),
16648 };
16649 Ok(Some(lsp::WorkspaceEdit::new(
16650 // Specify the same edit twice
16651 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16652 )))
16653 });
16654 let rename_task = cx
16655 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16656 .expect("Confirm rename was not started");
16657 rename_handler.next().await.unwrap();
16658 rename_task.await.expect("Confirm rename failed");
16659 cx.run_until_parked();
16660
16661 // Despite two edits, only one is actually applied as those are identical
16662 cx.assert_editor_state(indoc! {"
16663 struct FooRenamedˇ {}
16664 "});
16665}
16666
16667#[gpui::test]
16668async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16669 init_test(cx, |_| {});
16670 // These capabilities indicate that the server does not support prepare rename.
16671 let capabilities = lsp::ServerCapabilities {
16672 rename_provider: Some(lsp::OneOf::Left(true)),
16673 ..Default::default()
16674 };
16675 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16676
16677 cx.set_state(indoc! {"
16678 struct Fˇoo {}
16679 "});
16680
16681 cx.update_editor(|editor, _window, cx| {
16682 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16683 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16684 editor.highlight_background::<DocumentHighlightRead>(
16685 &[highlight_range],
16686 |c| c.editor_document_highlight_read_background,
16687 cx,
16688 );
16689 });
16690
16691 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16692 .expect("Prepare rename was not started")
16693 .await
16694 .expect("Prepare rename failed");
16695
16696 let mut rename_handler =
16697 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16698 let edit = lsp::TextEdit {
16699 range: lsp::Range {
16700 start: lsp::Position {
16701 line: 0,
16702 character: 7,
16703 },
16704 end: lsp::Position {
16705 line: 0,
16706 character: 10,
16707 },
16708 },
16709 new_text: "FooRenamed".to_string(),
16710 };
16711 Ok(Some(lsp::WorkspaceEdit::new(
16712 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16713 )))
16714 });
16715 let rename_task = cx
16716 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16717 .expect("Confirm rename was not started");
16718 rename_handler.next().await.unwrap();
16719 rename_task.await.expect("Confirm rename failed");
16720 cx.run_until_parked();
16721
16722 // Correct range is renamed, as `surrounding_word` is used to find it.
16723 cx.assert_editor_state(indoc! {"
16724 struct FooRenamedˇ {}
16725 "});
16726}
16727
16728#[gpui::test]
16729async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16730 init_test(cx, |_| {});
16731 let mut cx = EditorTestContext::new(cx).await;
16732
16733 let language = Arc::new(
16734 Language::new(
16735 LanguageConfig::default(),
16736 Some(tree_sitter_html::LANGUAGE.into()),
16737 )
16738 .with_brackets_query(
16739 r#"
16740 ("<" @open "/>" @close)
16741 ("</" @open ">" @close)
16742 ("<" @open ">" @close)
16743 ("\"" @open "\"" @close)
16744 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16745 "#,
16746 )
16747 .unwrap(),
16748 );
16749 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16750
16751 cx.set_state(indoc! {"
16752 <span>ˇ</span>
16753 "});
16754 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16755 cx.assert_editor_state(indoc! {"
16756 <span>
16757 ˇ
16758 </span>
16759 "});
16760
16761 cx.set_state(indoc! {"
16762 <span><span></span>ˇ</span>
16763 "});
16764 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16765 cx.assert_editor_state(indoc! {"
16766 <span><span></span>
16767 ˇ</span>
16768 "});
16769
16770 cx.set_state(indoc! {"
16771 <span>ˇ
16772 </span>
16773 "});
16774 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16775 cx.assert_editor_state(indoc! {"
16776 <span>
16777 ˇ
16778 </span>
16779 "});
16780}
16781
16782fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
16783 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
16784 point..point
16785}
16786
16787fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
16788 let (text, ranges) = marked_text_ranges(marked_text, true);
16789 assert_eq!(editor.text(cx), text);
16790 assert_eq!(
16791 editor.selections.ranges(cx),
16792 ranges,
16793 "Assert selections are {}",
16794 marked_text
16795 );
16796}
16797
16798pub fn handle_signature_help_request(
16799 cx: &mut EditorLspTestContext,
16800 mocked_response: lsp::SignatureHelp,
16801) -> impl Future<Output = ()> {
16802 let mut request =
16803 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
16804 let mocked_response = mocked_response.clone();
16805 async move { Ok(Some(mocked_response)) }
16806 });
16807
16808 async move {
16809 request.next().await;
16810 }
16811}
16812
16813/// Handle completion request passing a marked string specifying where the completion
16814/// should be triggered from using '|' character, what range should be replaced, and what completions
16815/// should be returned using '<' and '>' to delimit the range
16816pub fn handle_completion_request(
16817 cx: &mut EditorLspTestContext,
16818 marked_string: &str,
16819 completions: Vec<&'static str>,
16820 counter: Arc<AtomicUsize>,
16821) -> impl Future<Output = ()> {
16822 let complete_from_marker: TextRangeMarker = '|'.into();
16823 let replace_range_marker: TextRangeMarker = ('<', '>').into();
16824 let (_, mut marked_ranges) = marked_text_ranges_by(
16825 marked_string,
16826 vec![complete_from_marker.clone(), replace_range_marker.clone()],
16827 );
16828
16829 let complete_from_position =
16830 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
16831 let replace_range =
16832 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
16833
16834 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
16835 let completions = completions.clone();
16836 counter.fetch_add(1, atomic::Ordering::Release);
16837 async move {
16838 assert_eq!(params.text_document_position.text_document.uri, url.clone());
16839 assert_eq!(
16840 params.text_document_position.position,
16841 complete_from_position
16842 );
16843 Ok(Some(lsp::CompletionResponse::Array(
16844 completions
16845 .iter()
16846 .map(|completion_text| lsp::CompletionItem {
16847 label: completion_text.to_string(),
16848 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16849 range: replace_range,
16850 new_text: completion_text.to_string(),
16851 })),
16852 ..Default::default()
16853 })
16854 .collect(),
16855 )))
16856 }
16857 });
16858
16859 async move {
16860 request.next().await;
16861 }
16862}
16863
16864fn handle_resolve_completion_request(
16865 cx: &mut EditorLspTestContext,
16866 edits: Option<Vec<(&'static str, &'static str)>>,
16867) -> impl Future<Output = ()> {
16868 let edits = edits.map(|edits| {
16869 edits
16870 .iter()
16871 .map(|(marked_string, new_text)| {
16872 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
16873 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
16874 lsp::TextEdit::new(replace_range, new_text.to_string())
16875 })
16876 .collect::<Vec<_>>()
16877 });
16878
16879 let mut request =
16880 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16881 let edits = edits.clone();
16882 async move {
16883 Ok(lsp::CompletionItem {
16884 additional_text_edits: edits,
16885 ..Default::default()
16886 })
16887 }
16888 });
16889
16890 async move {
16891 request.next().await;
16892 }
16893}
16894
16895pub(crate) fn update_test_language_settings(
16896 cx: &mut TestAppContext,
16897 f: impl Fn(&mut AllLanguageSettingsContent),
16898) {
16899 cx.update(|cx| {
16900 SettingsStore::update_global(cx, |store, cx| {
16901 store.update_user_settings::<AllLanguageSettings>(cx, f);
16902 });
16903 });
16904}
16905
16906pub(crate) fn update_test_project_settings(
16907 cx: &mut TestAppContext,
16908 f: impl Fn(&mut ProjectSettings),
16909) {
16910 cx.update(|cx| {
16911 SettingsStore::update_global(cx, |store, cx| {
16912 store.update_user_settings::<ProjectSettings>(cx, f);
16913 });
16914 });
16915}
16916
16917pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
16918 cx.update(|cx| {
16919 assets::Assets.load_test_fonts(cx);
16920 let store = SettingsStore::test(cx);
16921 cx.set_global(store);
16922 theme::init(theme::LoadThemes::JustBase, cx);
16923 release_channel::init(SemanticVersion::default(), cx);
16924 client::init_settings(cx);
16925 language::init(cx);
16926 Project::init_settings(cx);
16927 workspace::init_settings(cx);
16928 crate::init(cx);
16929 });
16930
16931 update_test_language_settings(cx, f);
16932}
16933
16934#[track_caller]
16935fn assert_hunk_revert(
16936 not_reverted_text_with_selections: &str,
16937 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
16938 expected_reverted_text_with_selections: &str,
16939 base_text: &str,
16940 cx: &mut EditorLspTestContext,
16941) {
16942 cx.set_state(not_reverted_text_with_selections);
16943 cx.set_head_text(base_text);
16944 cx.executor().run_until_parked();
16945
16946 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
16947 let snapshot = editor.snapshot(window, cx);
16948 let reverted_hunk_statuses = snapshot
16949 .buffer_snapshot
16950 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
16951 .map(|hunk| hunk.status().kind)
16952 .collect::<Vec<_>>();
16953
16954 editor.git_restore(&Default::default(), window, cx);
16955 reverted_hunk_statuses
16956 });
16957 cx.executor().run_until_parked();
16958 cx.assert_editor_state(expected_reverted_text_with_selections);
16959 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
16960}