1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
24 Override, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::{IndentGuide, PathKey};
28use parking_lot::Mutex;
29use pretty_assertions::{assert_eq, assert_ne};
30use project::project_settings::{LspSettings, ProjectSettings};
31use project::FakeFs;
32use serde_json::{self, json};
33use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
34use std::{
35 iter,
36 sync::atomic::{self, AtomicUsize},
37};
38use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
39use text::ToPoint as _;
40use unindent::Unindent;
41use util::{
42 assert_set_eq, path,
43 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
44 uri,
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |window, cx| {
65 let entity = cx.entity().clone();
66 cx.subscribe_in(
67 &entity,
68 window,
69 move |_, _, event: &EditorEvent, _, _| match event {
70 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
71 EditorEvent::BufferEdited => {
72 events.borrow_mut().push(("editor1", "buffer edited"))
73 }
74 _ => {}
75 },
76 )
77 .detach();
78 Editor::for_buffer(buffer.clone(), None, window, cx)
79 }
80 });
81
82 let editor2 = cx.add_window({
83 let events = events.clone();
84 |window, cx| {
85 cx.subscribe_in(
86 &cx.entity().clone(),
87 window,
88 move |_, _, event: &EditorEvent, _, _| match event {
89 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
90 EditorEvent::BufferEdited => {
91 events.borrow_mut().push(("editor2", "buffer edited"))
92 }
93 _ => {}
94 },
95 )
96 .detach();
97 Editor::for_buffer(buffer.clone(), None, window, cx)
98 }
99 });
100
101 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
102
103 // Mutating editor 1 will emit an `Edited` event only for that editor.
104 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor1", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Mutating editor 2 will emit an `Edited` event only for that editor.
115 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor2", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Undoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Redoing on editor 1 will emit an `Edited` event only for that editor.
137 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor1", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Undoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // Redoing on editor 2 will emit an `Edited` event only for that editor.
159 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
160 assert_eq!(
161 mem::take(&mut *events.borrow_mut()),
162 [
163 ("editor2", "edited"),
164 ("editor1", "buffer edited"),
165 ("editor2", "buffer edited"),
166 ]
167 );
168
169 // No event is emitted when the mutation is a no-op.
170 _ = editor2.update(cx, |editor, window, cx| {
171 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
172
173 editor.backspace(&Backspace, window, cx);
174 });
175 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
176}
177
178#[gpui::test]
179fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
180 init_test(cx, |_| {});
181
182 let mut now = Instant::now();
183 let group_interval = Duration::from_millis(1);
184 let buffer = cx.new(|cx| {
185 let mut buf = language::Buffer::local("123456", cx);
186 buf.set_group_interval(group_interval);
187 buf
188 });
189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
190 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
191
192 _ = editor.update(cx, |editor, window, cx| {
193 editor.start_transaction_at(now, window, cx);
194 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
195
196 editor.insert("cd", window, cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cd56");
199 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
200
201 editor.start_transaction_at(now, window, cx);
202 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
203 editor.insert("e", window, cx);
204 editor.end_transaction_at(now, cx);
205 assert_eq!(editor.text(cx), "12cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
207
208 now += group_interval + Duration::from_millis(1);
209 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
210
211 // Simulate an edit in another editor
212 buffer.update(cx, |buffer, cx| {
213 buffer.start_transaction_at(now, cx);
214 buffer.edit([(0..1, "a")], None, cx);
215 buffer.edit([(1..1, "b")], None, cx);
216 buffer.end_transaction_at(now, cx);
217 });
218
219 assert_eq!(editor.text(cx), "ab2cde6");
220 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
221
222 // Last transaction happened past the group interval in a different editor.
223 // Undo it individually and don't restore selections.
224 editor.undo(&Undo, window, cx);
225 assert_eq!(editor.text(cx), "12cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
227
228 // First two transactions happened within the group interval in this editor.
229 // Undo them together and restore selections.
230 editor.undo(&Undo, window, cx);
231 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
232 assert_eq!(editor.text(cx), "123456");
233 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
234
235 // Redo the first two transactions together.
236 editor.redo(&Redo, window, cx);
237 assert_eq!(editor.text(cx), "12cde6");
238 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
239
240 // Redo the last transaction on its own.
241 editor.redo(&Redo, window, cx);
242 assert_eq!(editor.text(cx), "ab2cde6");
243 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
244
245 // Test empty transactions.
246 editor.start_transaction_at(now, window, cx);
247 editor.end_transaction_at(now, cx);
248 editor.undo(&Undo, window, cx);
249 assert_eq!(editor.text(cx), "12cde6");
250 });
251}
252
253#[gpui::test]
254fn test_ime_composition(cx: &mut TestAppContext) {
255 init_test(cx, |_| {});
256
257 let buffer = cx.new(|cx| {
258 let mut buffer = language::Buffer::local("abcde", cx);
259 // Ensure automatic grouping doesn't occur.
260 buffer.set_group_interval(Duration::ZERO);
261 buffer
262 });
263
264 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
265 cx.add_window(|window, cx| {
266 let mut editor = build_editor(buffer.clone(), window, cx);
267
268 // Start a new IME composition.
269 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
270 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
272 assert_eq!(editor.text(cx), "äbcde");
273 assert_eq!(
274 editor.marked_text_ranges(cx),
275 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
276 );
277
278 // Finalize IME composition.
279 editor.replace_text_in_range(None, "ā", window, cx);
280 assert_eq!(editor.text(cx), "ābcde");
281 assert_eq!(editor.marked_text_ranges(cx), None);
282
283 // IME composition edits are grouped and are undone/redone at once.
284 editor.undo(&Default::default(), window, cx);
285 assert_eq!(editor.text(cx), "abcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287 editor.redo(&Default::default(), window, cx);
288 assert_eq!(editor.text(cx), "ābcde");
289 assert_eq!(editor.marked_text_ranges(cx), None);
290
291 // Start a new IME composition.
292 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
293 assert_eq!(
294 editor.marked_text_ranges(cx),
295 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
296 );
297
298 // Undoing during an IME composition cancels it.
299 editor.undo(&Default::default(), window, cx);
300 assert_eq!(editor.text(cx), "ābcde");
301 assert_eq!(editor.marked_text_ranges(cx), None);
302
303 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
304 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
305 assert_eq!(editor.text(cx), "ābcdè");
306 assert_eq!(
307 editor.marked_text_ranges(cx),
308 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
309 );
310
311 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
312 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
313 assert_eq!(editor.text(cx), "ābcdę");
314 assert_eq!(editor.marked_text_ranges(cx), None);
315
316 // Start a new IME composition with multiple cursors.
317 editor.change_selections(None, window, cx, |s| {
318 s.select_ranges([
319 OffsetUtf16(1)..OffsetUtf16(1),
320 OffsetUtf16(3)..OffsetUtf16(3),
321 OffsetUtf16(5)..OffsetUtf16(5),
322 ])
323 });
324 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
325 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
326 assert_eq!(
327 editor.marked_text_ranges(cx),
328 Some(vec![
329 OffsetUtf16(0)..OffsetUtf16(3),
330 OffsetUtf16(4)..OffsetUtf16(7),
331 OffsetUtf16(8)..OffsetUtf16(11)
332 ])
333 );
334
335 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
336 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
337 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
338 assert_eq!(
339 editor.marked_text_ranges(cx),
340 Some(vec![
341 OffsetUtf16(1)..OffsetUtf16(2),
342 OffsetUtf16(5)..OffsetUtf16(6),
343 OffsetUtf16(9)..OffsetUtf16(10)
344 ])
345 );
346
347 // Finalize IME composition with multiple cursors.
348 editor.replace_text_in_range(Some(9..10), "2", window, cx);
349 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
350 assert_eq!(editor.marked_text_ranges(cx), None);
351
352 editor
353 });
354}
355
356#[gpui::test]
357fn test_selection_with_mouse(cx: &mut TestAppContext) {
358 init_test(cx, |_| {});
359
360 let editor = cx.add_window(|window, cx| {
361 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
362 build_editor(buffer, window, cx)
363 });
364
365 _ = editor.update(cx, |editor, window, cx| {
366 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
367 });
368 assert_eq!(
369 editor
370 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
371 .unwrap(),
372 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
373 );
374
375 _ = editor.update(cx, |editor, window, cx| {
376 editor.update_selection(
377 DisplayPoint::new(DisplayRow(3), 3),
378 0,
379 gpui::Point::<f32>::default(),
380 window,
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
390 );
391
392 _ = editor.update(cx, |editor, window, cx| {
393 editor.update_selection(
394 DisplayPoint::new(DisplayRow(1), 1),
395 0,
396 gpui::Point::<f32>::default(),
397 window,
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |editor, window, cx| {
410 editor.end_selection(window, cx);
411 editor.update_selection(
412 DisplayPoint::new(DisplayRow(3), 3),
413 0,
414 gpui::Point::<f32>::default(),
415 window,
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
423 .unwrap(),
424 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
425 );
426
427 _ = editor.update(cx, |editor, window, cx| {
428 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
429 editor.update_selection(
430 DisplayPoint::new(DisplayRow(0), 0),
431 0,
432 gpui::Point::<f32>::default(),
433 window,
434 cx,
435 );
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
441 .unwrap(),
442 [
443 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
444 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
445 ]
446 );
447
448 _ = editor.update(cx, |editor, window, cx| {
449 editor.end_selection(window, cx);
450 });
451
452 assert_eq!(
453 editor
454 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
455 .unwrap(),
456 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
457 );
458}
459
460#[gpui::test]
461fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
462 init_test(cx, |_| {});
463
464 let editor = cx.add_window(|window, cx| {
465 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
466 build_editor(buffer, window, cx)
467 });
468
469 _ = editor.update(cx, |editor, window, cx| {
470 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
471 });
472
473 _ = editor.update(cx, |editor, window, cx| {
474 editor.end_selection(window, cx);
475 });
476
477 _ = editor.update(cx, |editor, window, cx| {
478 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
479 });
480
481 _ = editor.update(cx, |editor, window, cx| {
482 editor.end_selection(window, cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
488 .unwrap(),
489 [
490 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
491 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
492 ]
493 );
494
495 _ = editor.update(cx, |editor, window, cx| {
496 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
497 });
498
499 _ = editor.update(cx, |editor, window, cx| {
500 editor.end_selection(window, cx);
501 });
502
503 assert_eq!(
504 editor
505 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
506 .unwrap(),
507 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
508 );
509}
510
511#[gpui::test]
512fn test_canceling_pending_selection(cx: &mut TestAppContext) {
513 init_test(cx, |_| {});
514
515 let editor = cx.add_window(|window, cx| {
516 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
517 build_editor(buffer, window, cx)
518 });
519
520 _ = editor.update(cx, |editor, window, cx| {
521 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
522 assert_eq!(
523 editor.selections.display_ranges(cx),
524 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
525 );
526 });
527
528 _ = editor.update(cx, |editor, window, cx| {
529 editor.update_selection(
530 DisplayPoint::new(DisplayRow(3), 3),
531 0,
532 gpui::Point::<f32>::default(),
533 window,
534 cx,
535 );
536 assert_eq!(
537 editor.selections.display_ranges(cx),
538 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
539 );
540 });
541
542 _ = editor.update(cx, |editor, window, cx| {
543 editor.cancel(&Cancel, window, cx);
544 editor.update_selection(
545 DisplayPoint::new(DisplayRow(1), 1),
546 0,
547 gpui::Point::<f32>::default(),
548 window,
549 cx,
550 );
551 assert_eq!(
552 editor.selections.display_ranges(cx),
553 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
554 );
555 });
556}
557
558#[gpui::test]
559fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
560 init_test(cx, |_| {});
561
562 let editor = cx.add_window(|window, cx| {
563 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
564 build_editor(buffer, window, cx)
565 });
566
567 _ = editor.update(cx, |editor, window, cx| {
568 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
569 assert_eq!(
570 editor.selections.display_ranges(cx),
571 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
572 );
573
574 editor.move_down(&Default::default(), window, cx);
575 assert_eq!(
576 editor.selections.display_ranges(cx),
577 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
578 );
579
580 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
581 assert_eq!(
582 editor.selections.display_ranges(cx),
583 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
584 );
585
586 editor.move_up(&Default::default(), window, cx);
587 assert_eq!(
588 editor.selections.display_ranges(cx),
589 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
590 );
591 });
592}
593
594#[gpui::test]
595fn test_clone(cx: &mut TestAppContext) {
596 init_test(cx, |_| {});
597
598 let (text, selection_ranges) = marked_text_ranges(
599 indoc! {"
600 one
601 two
602 threeˇ
603 four
604 fiveˇ
605 "},
606 true,
607 );
608
609 let editor = cx.add_window(|window, cx| {
610 let buffer = MultiBuffer::build_simple(&text, cx);
611 build_editor(buffer, window, cx)
612 });
613
614 _ = editor.update(cx, |editor, window, cx| {
615 editor.change_selections(None, window, cx, |s| {
616 s.select_ranges(selection_ranges.clone())
617 });
618 editor.fold_creases(
619 vec![
620 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
621 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
622 ],
623 true,
624 window,
625 cx,
626 );
627 });
628
629 let cloned_editor = editor
630 .update(cx, |editor, _, cx| {
631 cx.open_window(Default::default(), |window, cx| {
632 cx.new(|cx| editor.clone(window, cx))
633 })
634 })
635 .unwrap()
636 .unwrap();
637
638 let snapshot = editor
639 .update(cx, |e, window, cx| e.snapshot(window, cx))
640 .unwrap();
641 let cloned_snapshot = cloned_editor
642 .update(cx, |e, window, cx| e.snapshot(window, cx))
643 .unwrap();
644
645 assert_eq!(
646 cloned_editor
647 .update(cx, |e, _, cx| e.display_text(cx))
648 .unwrap(),
649 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
650 );
651 assert_eq!(
652 cloned_snapshot
653 .folds_in_range(0..text.len())
654 .collect::<Vec<_>>(),
655 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
656 );
657 assert_set_eq!(
658 cloned_editor
659 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
660 .unwrap(),
661 editor
662 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
663 .unwrap()
664 );
665 assert_set_eq!(
666 cloned_editor
667 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
668 .unwrap(),
669 editor
670 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
671 .unwrap()
672 );
673}
674
675#[gpui::test]
676async fn test_navigation_history(cx: &mut TestAppContext) {
677 init_test(cx, |_| {});
678
679 use workspace::item::Item;
680
681 let fs = FakeFs::new(cx.executor());
682 let project = Project::test(fs, [], cx).await;
683 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
684 let pane = workspace
685 .update(cx, |workspace, _, _| workspace.active_pane().clone())
686 .unwrap();
687
688 _ = workspace.update(cx, |_v, window, cx| {
689 cx.new(|cx| {
690 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
691 let mut editor = build_editor(buffer.clone(), window, cx);
692 let handle = cx.entity();
693 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
694
695 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
696 editor.nav_history.as_mut().unwrap().pop_backward(cx)
697 }
698
699 // Move the cursor a small distance.
700 // Nothing is added to the navigation history.
701 editor.change_selections(None, window, cx, |s| {
702 s.select_display_ranges([
703 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
704 ])
705 });
706 editor.change_selections(None, window, cx, |s| {
707 s.select_display_ranges([
708 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
709 ])
710 });
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance.
714 // The history can jump back to the previous position.
715 editor.change_selections(None, window, cx, |s| {
716 s.select_display_ranges([
717 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
718 ])
719 });
720 let nav_entry = pop_history(&mut editor, cx).unwrap();
721 editor.navigate(nav_entry.data.unwrap(), window, cx);
722 assert_eq!(nav_entry.item.id(), cx.entity_id());
723 assert_eq!(
724 editor.selections.display_ranges(cx),
725 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
726 );
727 assert!(pop_history(&mut editor, cx).is_none());
728
729 // Move the cursor a small distance via the mouse.
730 // Nothing is added to the navigation history.
731 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
732 editor.end_selection(window, cx);
733 assert_eq!(
734 editor.selections.display_ranges(cx),
735 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
736 );
737 assert!(pop_history(&mut editor, cx).is_none());
738
739 // Move the cursor a large distance via the mouse.
740 // The history can jump back to the previous position.
741 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
742 editor.end_selection(window, cx);
743 assert_eq!(
744 editor.selections.display_ranges(cx),
745 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
746 );
747 let nav_entry = pop_history(&mut editor, cx).unwrap();
748 editor.navigate(nav_entry.data.unwrap(), window, cx);
749 assert_eq!(nav_entry.item.id(), cx.entity_id());
750 assert_eq!(
751 editor.selections.display_ranges(cx),
752 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
753 );
754 assert!(pop_history(&mut editor, cx).is_none());
755
756 // Set scroll position to check later
757 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
758 let original_scroll_position = editor.scroll_manager.anchor();
759
760 // Jump to the end of the document and adjust scroll
761 editor.move_to_end(&MoveToEnd, window, cx);
762 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
763 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
764
765 let nav_entry = pop_history(&mut editor, cx).unwrap();
766 editor.navigate(nav_entry.data.unwrap(), window, cx);
767 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
768
769 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
770 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
771 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
772 let invalid_point = Point::new(9999, 0);
773 editor.navigate(
774 Box::new(NavigationData {
775 cursor_anchor: invalid_anchor,
776 cursor_position: invalid_point,
777 scroll_anchor: ScrollAnchor {
778 anchor: invalid_anchor,
779 offset: Default::default(),
780 },
781 scroll_top_row: invalid_point.row,
782 }),
783 window,
784 cx,
785 );
786 assert_eq!(
787 editor.selections.display_ranges(cx),
788 &[editor.max_point(cx)..editor.max_point(cx)]
789 );
790 assert_eq!(
791 editor.scroll_position(cx),
792 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
793 );
794
795 editor
796 })
797 });
798}
799
800#[gpui::test]
801fn test_cancel(cx: &mut TestAppContext) {
802 init_test(cx, |_| {});
803
804 let editor = cx.add_window(|window, cx| {
805 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
806 build_editor(buffer, window, cx)
807 });
808
809 _ = editor.update(cx, |editor, window, cx| {
810 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
811 editor.update_selection(
812 DisplayPoint::new(DisplayRow(1), 1),
813 0,
814 gpui::Point::<f32>::default(),
815 window,
816 cx,
817 );
818 editor.end_selection(window, cx);
819
820 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
821 editor.update_selection(
822 DisplayPoint::new(DisplayRow(0), 3),
823 0,
824 gpui::Point::<f32>::default(),
825 window,
826 cx,
827 );
828 editor.end_selection(window, cx);
829 assert_eq!(
830 editor.selections.display_ranges(cx),
831 [
832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
833 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
834 ]
835 );
836 });
837
838 _ = editor.update(cx, |editor, window, cx| {
839 editor.cancel(&Cancel, window, cx);
840 assert_eq!(
841 editor.selections.display_ranges(cx),
842 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
843 );
844 });
845
846 _ = editor.update(cx, |editor, window, cx| {
847 editor.cancel(&Cancel, window, cx);
848 assert_eq!(
849 editor.selections.display_ranges(cx),
850 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
851 );
852 });
853}
854
855#[gpui::test]
856fn test_fold_action(cx: &mut TestAppContext) {
857 init_test(cx, |_| {});
858
859 let editor = cx.add_window(|window, cx| {
860 let buffer = MultiBuffer::build_simple(
861 &"
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {
870 2
871 }
872
873 fn c() {
874 3
875 }
876 }
877 "
878 .unindent(),
879 cx,
880 );
881 build_editor(buffer.clone(), window, cx)
882 });
883
884 _ = editor.update(cx, |editor, window, cx| {
885 editor.change_selections(None, window, cx, |s| {
886 s.select_display_ranges([
887 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
888 ]);
889 });
890 editor.fold(&Fold, window, cx);
891 assert_eq!(
892 editor.display_text(cx),
893 "
894 impl Foo {
895 // Hello!
896
897 fn a() {
898 1
899 }
900
901 fn b() {⋯
902 }
903
904 fn c() {⋯
905 }
906 }
907 "
908 .unindent(),
909 );
910
911 editor.fold(&Fold, window, cx);
912 assert_eq!(
913 editor.display_text(cx),
914 "
915 impl Foo {⋯
916 }
917 "
918 .unindent(),
919 );
920
921 editor.unfold_lines(&UnfoldLines, window, cx);
922 assert_eq!(
923 editor.display_text(cx),
924 "
925 impl Foo {
926 // Hello!
927
928 fn a() {
929 1
930 }
931
932 fn b() {⋯
933 }
934
935 fn c() {⋯
936 }
937 }
938 "
939 .unindent(),
940 );
941
942 editor.unfold_lines(&UnfoldLines, window, cx);
943 assert_eq!(
944 editor.display_text(cx),
945 editor.buffer.read(cx).read(cx).text()
946 );
947 });
948}
949
950#[gpui::test]
951fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
952 init_test(cx, |_| {});
953
954 let editor = cx.add_window(|window, cx| {
955 let buffer = MultiBuffer::build_simple(
956 &"
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():
964 print(2)
965
966 def c():
967 print(3)
968 "
969 .unindent(),
970 cx,
971 );
972 build_editor(buffer.clone(), window, cx)
973 });
974
975 _ = editor.update(cx, |editor, window, cx| {
976 editor.change_selections(None, window, cx, |s| {
977 s.select_display_ranges([
978 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
979 ]);
980 });
981 editor.fold(&Fold, window, cx);
982 assert_eq!(
983 editor.display_text(cx),
984 "
985 class Foo:
986 # Hello!
987
988 def a():
989 print(1)
990
991 def b():⋯
992
993 def c():⋯
994 "
995 .unindent(),
996 );
997
998 editor.fold(&Fold, window, cx);
999 assert_eq!(
1000 editor.display_text(cx),
1001 "
1002 class Foo:⋯
1003 "
1004 .unindent(),
1005 );
1006
1007 editor.unfold_lines(&UnfoldLines, window, cx);
1008 assert_eq!(
1009 editor.display_text(cx),
1010 "
1011 class Foo:
1012 # Hello!
1013
1014 def a():
1015 print(1)
1016
1017 def b():⋯
1018
1019 def c():⋯
1020 "
1021 .unindent(),
1022 );
1023
1024 editor.unfold_lines(&UnfoldLines, window, cx);
1025 assert_eq!(
1026 editor.display_text(cx),
1027 editor.buffer.read(cx).read(cx).text()
1028 );
1029 });
1030}
1031
1032#[gpui::test]
1033fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1034 init_test(cx, |_| {});
1035
1036 let editor = cx.add_window(|window, cx| {
1037 let buffer = MultiBuffer::build_simple(
1038 &"
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():
1046 print(2)
1047
1048
1049 def c():
1050 print(3)
1051
1052
1053 "
1054 .unindent(),
1055 cx,
1056 );
1057 build_editor(buffer.clone(), window, cx)
1058 });
1059
1060 _ = editor.update(cx, |editor, window, cx| {
1061 editor.change_selections(None, window, cx, |s| {
1062 s.select_display_ranges([
1063 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1064 ]);
1065 });
1066 editor.fold(&Fold, window, cx);
1067 assert_eq!(
1068 editor.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 editor.fold(&Fold, window, cx);
1087 assert_eq!(
1088 editor.display_text(cx),
1089 "
1090 class Foo:⋯
1091
1092
1093 "
1094 .unindent(),
1095 );
1096
1097 editor.unfold_lines(&UnfoldLines, window, cx);
1098 assert_eq!(
1099 editor.display_text(cx),
1100 "
1101 class Foo:
1102 # Hello!
1103
1104 def a():
1105 print(1)
1106
1107 def b():⋯
1108
1109
1110 def c():⋯
1111
1112
1113 "
1114 .unindent(),
1115 );
1116
1117 editor.unfold_lines(&UnfoldLines, window, cx);
1118 assert_eq!(
1119 editor.display_text(cx),
1120 editor.buffer.read(cx).read(cx).text()
1121 );
1122 });
1123}
1124
1125#[gpui::test]
1126fn test_fold_at_level(cx: &mut TestAppContext) {
1127 init_test(cx, |_| {});
1128
1129 let editor = cx.add_window(|window, cx| {
1130 let buffer = MultiBuffer::build_simple(
1131 &"
1132 class Foo:
1133 # Hello!
1134
1135 def a():
1136 print(1)
1137
1138 def b():
1139 print(2)
1140
1141
1142 class Bar:
1143 # World!
1144
1145 def a():
1146 print(1)
1147
1148 def b():
1149 print(2)
1150
1151
1152 "
1153 .unindent(),
1154 cx,
1155 );
1156 build_editor(buffer.clone(), window, cx)
1157 });
1158
1159 _ = editor.update(cx, |editor, window, cx| {
1160 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1161 assert_eq!(
1162 editor.display_text(cx),
1163 "
1164 class Foo:
1165 # Hello!
1166
1167 def a():⋯
1168
1169 def b():⋯
1170
1171
1172 class Bar:
1173 # World!
1174
1175 def a():⋯
1176
1177 def b():⋯
1178
1179
1180 "
1181 .unindent(),
1182 );
1183
1184 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1185 assert_eq!(
1186 editor.display_text(cx),
1187 "
1188 class Foo:⋯
1189
1190
1191 class Bar:⋯
1192
1193
1194 "
1195 .unindent(),
1196 );
1197
1198 editor.unfold_all(&UnfoldAll, window, cx);
1199 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1200 assert_eq!(
1201 editor.display_text(cx),
1202 "
1203 class Foo:
1204 # Hello!
1205
1206 def a():
1207 print(1)
1208
1209 def b():
1210 print(2)
1211
1212
1213 class Bar:
1214 # World!
1215
1216 def a():
1217 print(1)
1218
1219 def b():
1220 print(2)
1221
1222
1223 "
1224 .unindent(),
1225 );
1226
1227 assert_eq!(
1228 editor.display_text(cx),
1229 editor.buffer.read(cx).read(cx).text()
1230 );
1231 });
1232}
1233
1234#[gpui::test]
1235fn test_move_cursor(cx: &mut TestAppContext) {
1236 init_test(cx, |_| {});
1237
1238 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1239 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1240
1241 buffer.update(cx, |buffer, cx| {
1242 buffer.edit(
1243 vec![
1244 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1245 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1246 ],
1247 None,
1248 cx,
1249 );
1250 });
1251 _ = editor.update(cx, |editor, window, cx| {
1252 assert_eq!(
1253 editor.selections.display_ranges(cx),
1254 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1255 );
1256
1257 editor.move_down(&MoveDown, window, cx);
1258 assert_eq!(
1259 editor.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1261 );
1262
1263 editor.move_right(&MoveRight, window, cx);
1264 assert_eq!(
1265 editor.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1267 );
1268
1269 editor.move_left(&MoveLeft, window, cx);
1270 assert_eq!(
1271 editor.selections.display_ranges(cx),
1272 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1273 );
1274
1275 editor.move_up(&MoveUp, window, cx);
1276 assert_eq!(
1277 editor.selections.display_ranges(cx),
1278 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1279 );
1280
1281 editor.move_to_end(&MoveToEnd, window, cx);
1282 assert_eq!(
1283 editor.selections.display_ranges(cx),
1284 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1285 );
1286
1287 editor.move_to_beginning(&MoveToBeginning, window, cx);
1288 assert_eq!(
1289 editor.selections.display_ranges(cx),
1290 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1291 );
1292
1293 editor.change_selections(None, window, cx, |s| {
1294 s.select_display_ranges([
1295 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1296 ]);
1297 });
1298 editor.select_to_beginning(&SelectToBeginning, window, cx);
1299 assert_eq!(
1300 editor.selections.display_ranges(cx),
1301 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1302 );
1303
1304 editor.select_to_end(&SelectToEnd, window, cx);
1305 assert_eq!(
1306 editor.selections.display_ranges(cx),
1307 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1308 );
1309 });
1310}
1311
1312// TODO: Re-enable this test
1313#[cfg(target_os = "macos")]
1314#[gpui::test]
1315fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1316 init_test(cx, |_| {});
1317
1318 let editor = cx.add_window(|window, cx| {
1319 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1320 build_editor(buffer.clone(), window, cx)
1321 });
1322
1323 assert_eq!('🟥'.len_utf8(), 4);
1324 assert_eq!('α'.len_utf8(), 2);
1325
1326 _ = editor.update(cx, |editor, window, cx| {
1327 editor.fold_creases(
1328 vec![
1329 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1330 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1332 ],
1333 true,
1334 window,
1335 cx,
1336 );
1337 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1338
1339 editor.move_right(&MoveRight, window, cx);
1340 assert_eq!(
1341 editor.selections.display_ranges(cx),
1342 &[empty_range(0, "🟥".len())]
1343 );
1344 editor.move_right(&MoveRight, window, cx);
1345 assert_eq!(
1346 editor.selections.display_ranges(cx),
1347 &[empty_range(0, "🟥🟧".len())]
1348 );
1349 editor.move_right(&MoveRight, window, cx);
1350 assert_eq!(
1351 editor.selections.display_ranges(cx),
1352 &[empty_range(0, "🟥🟧⋯".len())]
1353 );
1354
1355 editor.move_down(&MoveDown, window, cx);
1356 assert_eq!(
1357 editor.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 editor.move_left(&MoveLeft, window, cx);
1361 assert_eq!(
1362 editor.selections.display_ranges(cx),
1363 &[empty_range(1, "ab⋯".len())]
1364 );
1365 editor.move_left(&MoveLeft, window, cx);
1366 assert_eq!(
1367 editor.selections.display_ranges(cx),
1368 &[empty_range(1, "ab".len())]
1369 );
1370 editor.move_left(&MoveLeft, window, cx);
1371 assert_eq!(
1372 editor.selections.display_ranges(cx),
1373 &[empty_range(1, "a".len())]
1374 );
1375
1376 editor.move_down(&MoveDown, window, cx);
1377 assert_eq!(
1378 editor.selections.display_ranges(cx),
1379 &[empty_range(2, "α".len())]
1380 );
1381 editor.move_right(&MoveRight, window, cx);
1382 assert_eq!(
1383 editor.selections.display_ranges(cx),
1384 &[empty_range(2, "αβ".len())]
1385 );
1386 editor.move_right(&MoveRight, window, cx);
1387 assert_eq!(
1388 editor.selections.display_ranges(cx),
1389 &[empty_range(2, "αβ⋯".len())]
1390 );
1391 editor.move_right(&MoveRight, window, cx);
1392 assert_eq!(
1393 editor.selections.display_ranges(cx),
1394 &[empty_range(2, "αβ⋯ε".len())]
1395 );
1396
1397 editor.move_up(&MoveUp, window, cx);
1398 assert_eq!(
1399 editor.selections.display_ranges(cx),
1400 &[empty_range(1, "ab⋯e".len())]
1401 );
1402 editor.move_down(&MoveDown, window, cx);
1403 assert_eq!(
1404 editor.selections.display_ranges(cx),
1405 &[empty_range(2, "αβ⋯ε".len())]
1406 );
1407 editor.move_up(&MoveUp, window, cx);
1408 assert_eq!(
1409 editor.selections.display_ranges(cx),
1410 &[empty_range(1, "ab⋯e".len())]
1411 );
1412
1413 editor.move_up(&MoveUp, window, cx);
1414 assert_eq!(
1415 editor.selections.display_ranges(cx),
1416 &[empty_range(0, "🟥🟧".len())]
1417 );
1418 editor.move_left(&MoveLeft, window, cx);
1419 assert_eq!(
1420 editor.selections.display_ranges(cx),
1421 &[empty_range(0, "🟥".len())]
1422 );
1423 editor.move_left(&MoveLeft, window, cx);
1424 assert_eq!(
1425 editor.selections.display_ranges(cx),
1426 &[empty_range(0, "".len())]
1427 );
1428 });
1429}
1430
1431#[gpui::test]
1432fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1433 init_test(cx, |_| {});
1434
1435 let editor = cx.add_window(|window, cx| {
1436 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1437 build_editor(buffer.clone(), window, cx)
1438 });
1439 _ = editor.update(cx, |editor, window, cx| {
1440 editor.change_selections(None, window, cx, |s| {
1441 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1442 });
1443
1444 // moving above start of document should move selection to start of document,
1445 // but the next move down should still be at the original goal_x
1446 editor.move_up(&MoveUp, window, cx);
1447 assert_eq!(
1448 editor.selections.display_ranges(cx),
1449 &[empty_range(0, "".len())]
1450 );
1451
1452 editor.move_down(&MoveDown, window, cx);
1453 assert_eq!(
1454 editor.selections.display_ranges(cx),
1455 &[empty_range(1, "abcd".len())]
1456 );
1457
1458 editor.move_down(&MoveDown, window, cx);
1459 assert_eq!(
1460 editor.selections.display_ranges(cx),
1461 &[empty_range(2, "αβγ".len())]
1462 );
1463
1464 editor.move_down(&MoveDown, window, cx);
1465 assert_eq!(
1466 editor.selections.display_ranges(cx),
1467 &[empty_range(3, "abcd".len())]
1468 );
1469
1470 editor.move_down(&MoveDown, window, cx);
1471 assert_eq!(
1472 editor.selections.display_ranges(cx),
1473 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1474 );
1475
1476 // moving past end of document should not change goal_x
1477 editor.move_down(&MoveDown, window, cx);
1478 assert_eq!(
1479 editor.selections.display_ranges(cx),
1480 &[empty_range(5, "".len())]
1481 );
1482
1483 editor.move_down(&MoveDown, window, cx);
1484 assert_eq!(
1485 editor.selections.display_ranges(cx),
1486 &[empty_range(5, "".len())]
1487 );
1488
1489 editor.move_up(&MoveUp, window, cx);
1490 assert_eq!(
1491 editor.selections.display_ranges(cx),
1492 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1493 );
1494
1495 editor.move_up(&MoveUp, window, cx);
1496 assert_eq!(
1497 editor.selections.display_ranges(cx),
1498 &[empty_range(3, "abcd".len())]
1499 );
1500
1501 editor.move_up(&MoveUp, window, cx);
1502 assert_eq!(
1503 editor.selections.display_ranges(cx),
1504 &[empty_range(2, "αβγ".len())]
1505 );
1506 });
1507}
1508
1509#[gpui::test]
1510fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1511 init_test(cx, |_| {});
1512 let move_to_beg = MoveToBeginningOfLine {
1513 stop_at_soft_wraps: true,
1514 stop_at_indent: true,
1515 };
1516
1517 let delete_to_beg = DeleteToBeginningOfLine {
1518 stop_at_indent: false,
1519 };
1520
1521 let move_to_end = MoveToEndOfLine {
1522 stop_at_soft_wraps: true,
1523 };
1524
1525 let editor = cx.add_window(|window, cx| {
1526 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1527 build_editor(buffer, window, cx)
1528 });
1529 _ = editor.update(cx, |editor, window, cx| {
1530 editor.change_selections(None, window, cx, |s| {
1531 s.select_display_ranges([
1532 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1533 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1534 ]);
1535 });
1536 });
1537
1538 _ = editor.update(cx, |editor, window, cx| {
1539 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1540 assert_eq!(
1541 editor.selections.display_ranges(cx),
1542 &[
1543 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1544 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1545 ]
1546 );
1547 });
1548
1549 _ = editor.update(cx, |editor, window, cx| {
1550 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1551 assert_eq!(
1552 editor.selections.display_ranges(cx),
1553 &[
1554 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1555 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1556 ]
1557 );
1558 });
1559
1560 _ = editor.update(cx, |editor, window, cx| {
1561 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1562 assert_eq!(
1563 editor.selections.display_ranges(cx),
1564 &[
1565 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1566 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1567 ]
1568 );
1569 });
1570
1571 _ = editor.update(cx, |editor, window, cx| {
1572 editor.move_to_end_of_line(&move_to_end, window, cx);
1573 assert_eq!(
1574 editor.selections.display_ranges(cx),
1575 &[
1576 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1577 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1578 ]
1579 );
1580 });
1581
1582 // Moving to the end of line again is a no-op.
1583 _ = editor.update(cx, |editor, window, cx| {
1584 editor.move_to_end_of_line(&move_to_end, window, cx);
1585 assert_eq!(
1586 editor.selections.display_ranges(cx),
1587 &[
1588 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1589 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1590 ]
1591 );
1592 });
1593
1594 _ = editor.update(cx, |editor, window, cx| {
1595 editor.move_left(&MoveLeft, window, cx);
1596 editor.select_to_beginning_of_line(
1597 &SelectToBeginningOfLine {
1598 stop_at_soft_wraps: true,
1599 stop_at_indent: true,
1600 },
1601 window,
1602 cx,
1603 );
1604 assert_eq!(
1605 editor.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1609 ]
1610 );
1611 });
1612
1613 _ = editor.update(cx, |editor, window, cx| {
1614 editor.select_to_beginning_of_line(
1615 &SelectToBeginningOfLine {
1616 stop_at_soft_wraps: true,
1617 stop_at_indent: true,
1618 },
1619 window,
1620 cx,
1621 );
1622 assert_eq!(
1623 editor.selections.display_ranges(cx),
1624 &[
1625 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1626 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1627 ]
1628 );
1629 });
1630
1631 _ = editor.update(cx, |editor, window, cx| {
1632 editor.select_to_beginning_of_line(
1633 &SelectToBeginningOfLine {
1634 stop_at_soft_wraps: true,
1635 stop_at_indent: true,
1636 },
1637 window,
1638 cx,
1639 );
1640 assert_eq!(
1641 editor.selections.display_ranges(cx),
1642 &[
1643 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1644 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1645 ]
1646 );
1647 });
1648
1649 _ = editor.update(cx, |editor, window, cx| {
1650 editor.select_to_end_of_line(
1651 &SelectToEndOfLine {
1652 stop_at_soft_wraps: true,
1653 },
1654 window,
1655 cx,
1656 );
1657 assert_eq!(
1658 editor.selections.display_ranges(cx),
1659 &[
1660 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1661 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1662 ]
1663 );
1664 });
1665
1666 _ = editor.update(cx, |editor, window, cx| {
1667 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1668 assert_eq!(editor.display_text(cx), "ab\n de");
1669 assert_eq!(
1670 editor.selections.display_ranges(cx),
1671 &[
1672 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1673 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1674 ]
1675 );
1676 });
1677
1678 _ = editor.update(cx, |editor, window, cx| {
1679 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1680 assert_eq!(editor.display_text(cx), "\n");
1681 assert_eq!(
1682 editor.selections.display_ranges(cx),
1683 &[
1684 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1685 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1686 ]
1687 );
1688 });
1689}
1690
1691#[gpui::test]
1692fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1693 init_test(cx, |_| {});
1694 let move_to_beg = MoveToBeginningOfLine {
1695 stop_at_soft_wraps: false,
1696 stop_at_indent: false,
1697 };
1698
1699 let move_to_end = MoveToEndOfLine {
1700 stop_at_soft_wraps: false,
1701 };
1702
1703 let editor = cx.add_window(|window, cx| {
1704 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1705 build_editor(buffer, window, cx)
1706 });
1707
1708 _ = editor.update(cx, |editor, window, cx| {
1709 editor.set_wrap_width(Some(140.0.into()), cx);
1710
1711 // We expect the following lines after wrapping
1712 // ```
1713 // thequickbrownfox
1714 // jumpedoverthelazydo
1715 // gs
1716 // ```
1717 // The final `gs` was soft-wrapped onto a new line.
1718 assert_eq!(
1719 "thequickbrownfox\njumpedoverthelaz\nydogs",
1720 editor.display_text(cx),
1721 );
1722
1723 // First, let's assert behavior on the first line, that was not soft-wrapped.
1724 // Start the cursor at the `k` on the first line
1725 editor.change_selections(None, window, cx, |s| {
1726 s.select_display_ranges([
1727 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1728 ]);
1729 });
1730
1731 // Moving to the beginning of the line should put us at the beginning of the line.
1732 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1733 assert_eq!(
1734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1735 editor.selections.display_ranges(cx)
1736 );
1737
1738 // Moving to the end of the line should put us at the end of the line.
1739 editor.move_to_end_of_line(&move_to_end, window, cx);
1740 assert_eq!(
1741 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1742 editor.selections.display_ranges(cx)
1743 );
1744
1745 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1746 // Start the cursor at the last line (`y` that was wrapped to a new line)
1747 editor.change_selections(None, window, cx, |s| {
1748 s.select_display_ranges([
1749 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1750 ]);
1751 });
1752
1753 // Moving to the beginning of the line should put us at the start of the second line of
1754 // display text, i.e., the `j`.
1755 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1756 assert_eq!(
1757 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1758 editor.selections.display_ranges(cx)
1759 );
1760
1761 // Moving to the beginning of the line again should be a no-op.
1762 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1763 assert_eq!(
1764 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1765 editor.selections.display_ranges(cx)
1766 );
1767
1768 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1769 // next display line.
1770 editor.move_to_end_of_line(&move_to_end, window, cx);
1771 assert_eq!(
1772 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1773 editor.selections.display_ranges(cx)
1774 );
1775
1776 // Moving to the end of the line again should be a no-op.
1777 editor.move_to_end_of_line(&move_to_end, window, cx);
1778 assert_eq!(
1779 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1780 editor.selections.display_ranges(cx)
1781 );
1782 });
1783}
1784
1785#[gpui::test]
1786fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1787 init_test(cx, |_| {});
1788
1789 let move_to_beg = MoveToBeginningOfLine {
1790 stop_at_soft_wraps: true,
1791 stop_at_indent: true,
1792 };
1793
1794 let select_to_beg = SelectToBeginningOfLine {
1795 stop_at_soft_wraps: true,
1796 stop_at_indent: true,
1797 };
1798
1799 let delete_to_beg = DeleteToBeginningOfLine {
1800 stop_at_indent: true,
1801 };
1802
1803 let move_to_end = MoveToEndOfLine {
1804 stop_at_soft_wraps: false,
1805 };
1806
1807 let editor = cx.add_window(|window, cx| {
1808 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1809 build_editor(buffer, window, cx)
1810 });
1811
1812 _ = editor.update(cx, |editor, window, cx| {
1813 editor.change_selections(None, window, cx, |s| {
1814 s.select_display_ranges([
1815 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1816 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1817 ]);
1818 });
1819
1820 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1821 // and the second cursor at the first non-whitespace character in the line.
1822 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1823 assert_eq!(
1824 editor.selections.display_ranges(cx),
1825 &[
1826 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1827 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1828 ]
1829 );
1830
1831 // Moving to the beginning of the line again should be a no-op for the first cursor,
1832 // and should move the second cursor to the beginning of the line.
1833 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1834 assert_eq!(
1835 editor.selections.display_ranges(cx),
1836 &[
1837 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1838 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1839 ]
1840 );
1841
1842 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1843 // and should move the second cursor back to the first non-whitespace character in the line.
1844 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1845 assert_eq!(
1846 editor.selections.display_ranges(cx),
1847 &[
1848 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1849 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1850 ]
1851 );
1852
1853 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1854 // and to the first non-whitespace character in the line for the second cursor.
1855 editor.move_to_end_of_line(&move_to_end, window, cx);
1856 editor.move_left(&MoveLeft, window, cx);
1857 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1858 assert_eq!(
1859 editor.selections.display_ranges(cx),
1860 &[
1861 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1862 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1863 ]
1864 );
1865
1866 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1867 // and should select to the beginning of the line for the second cursor.
1868 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1869 assert_eq!(
1870 editor.selections.display_ranges(cx),
1871 &[
1872 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1873 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1874 ]
1875 );
1876
1877 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1878 // and should delete to the first non-whitespace character in the line for the second cursor.
1879 editor.move_to_end_of_line(&move_to_end, window, cx);
1880 editor.move_left(&MoveLeft, window, cx);
1881 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1882 assert_eq!(editor.text(cx), "c\n f");
1883 });
1884}
1885
1886#[gpui::test]
1887fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1888 init_test(cx, |_| {});
1889
1890 let editor = cx.add_window(|window, cx| {
1891 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1892 build_editor(buffer, window, cx)
1893 });
1894 _ = editor.update(cx, |editor, window, cx| {
1895 editor.change_selections(None, window, cx, |s| {
1896 s.select_display_ranges([
1897 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1898 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1899 ])
1900 });
1901
1902 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1903 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1904
1905 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1906 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1907
1908 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1909 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1910
1911 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1912 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1913
1914 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1915 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1916
1917 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1918 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1919
1920 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1921 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1922
1923 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1924 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1925
1926 editor.move_right(&MoveRight, window, cx);
1927 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1928 assert_selection_ranges(
1929 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1930 editor,
1931 cx,
1932 );
1933
1934 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1935 assert_selection_ranges(
1936 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1937 editor,
1938 cx,
1939 );
1940
1941 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1942 assert_selection_ranges(
1943 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1944 editor,
1945 cx,
1946 );
1947 });
1948}
1949
1950#[gpui::test]
1951fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1952 init_test(cx, |_| {});
1953
1954 let editor = cx.add_window(|window, cx| {
1955 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1956 build_editor(buffer, window, cx)
1957 });
1958
1959 _ = editor.update(cx, |editor, window, cx| {
1960 editor.set_wrap_width(Some(140.0.into()), cx);
1961 assert_eq!(
1962 editor.display_text(cx),
1963 "use one::{\n two::three::\n four::five\n};"
1964 );
1965
1966 editor.change_selections(None, window, cx, |s| {
1967 s.select_display_ranges([
1968 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1969 ]);
1970 });
1971
1972 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1973 assert_eq!(
1974 editor.selections.display_ranges(cx),
1975 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1976 );
1977
1978 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1979 assert_eq!(
1980 editor.selections.display_ranges(cx),
1981 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1982 );
1983
1984 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1985 assert_eq!(
1986 editor.selections.display_ranges(cx),
1987 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1988 );
1989
1990 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1991 assert_eq!(
1992 editor.selections.display_ranges(cx),
1993 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1994 );
1995
1996 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1997 assert_eq!(
1998 editor.selections.display_ranges(cx),
1999 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2000 );
2001
2002 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2003 assert_eq!(
2004 editor.selections.display_ranges(cx),
2005 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.editor(|editor, window, _| {
2016 editor
2017 .style()
2018 .unwrap()
2019 .text
2020 .line_height_in_pixels(window.rem_size())
2021 });
2022 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2023
2024 cx.set_state(
2025 &r#"ˇone
2026 two
2027
2028 three
2029 fourˇ
2030 five
2031
2032 six"#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, window, cx| {
2037 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2038 });
2039 cx.assert_editor_state(
2040 &r#"one
2041 two
2042 ˇ
2043 three
2044 four
2045 five
2046 ˇ
2047 six"#
2048 .unindent(),
2049 );
2050
2051 cx.update_editor(|editor, window, cx| {
2052 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2053 });
2054 cx.assert_editor_state(
2055 &r#"one
2056 two
2057
2058 three
2059 four
2060 five
2061 ˇ
2062 sixˇ"#
2063 .unindent(),
2064 );
2065
2066 cx.update_editor(|editor, window, cx| {
2067 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2068 });
2069 cx.assert_editor_state(
2070 &r#"one
2071 two
2072
2073 three
2074 four
2075 five
2076
2077 sixˇ"#
2078 .unindent(),
2079 );
2080
2081 cx.update_editor(|editor, window, cx| {
2082 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2083 });
2084 cx.assert_editor_state(
2085 &r#"one
2086 two
2087
2088 three
2089 four
2090 five
2091 ˇ
2092 six"#
2093 .unindent(),
2094 );
2095
2096 cx.update_editor(|editor, window, cx| {
2097 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2098 });
2099 cx.assert_editor_state(
2100 &r#"one
2101 two
2102 ˇ
2103 three
2104 four
2105 five
2106
2107 six"#
2108 .unindent(),
2109 );
2110
2111 cx.update_editor(|editor, window, cx| {
2112 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2113 });
2114 cx.assert_editor_state(
2115 &r#"ˇone
2116 two
2117
2118 three
2119 four
2120 five
2121
2122 six"#
2123 .unindent(),
2124 );
2125}
2126
2127#[gpui::test]
2128async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2129 init_test(cx, |_| {});
2130 let mut cx = EditorTestContext::new(cx).await;
2131 let line_height = cx.editor(|editor, window, _| {
2132 editor
2133 .style()
2134 .unwrap()
2135 .text
2136 .line_height_in_pixels(window.rem_size())
2137 });
2138 let window = cx.window;
2139 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2140
2141 cx.set_state(
2142 r#"ˇone
2143 two
2144 three
2145 four
2146 five
2147 six
2148 seven
2149 eight
2150 nine
2151 ten
2152 "#,
2153 );
2154
2155 cx.update_editor(|editor, window, cx| {
2156 assert_eq!(
2157 editor.snapshot(window, cx).scroll_position(),
2158 gpui::Point::new(0., 0.)
2159 );
2160 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2161 assert_eq!(
2162 editor.snapshot(window, cx).scroll_position(),
2163 gpui::Point::new(0., 3.)
2164 );
2165 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2166 assert_eq!(
2167 editor.snapshot(window, cx).scroll_position(),
2168 gpui::Point::new(0., 6.)
2169 );
2170 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2171 assert_eq!(
2172 editor.snapshot(window, cx).scroll_position(),
2173 gpui::Point::new(0., 3.)
2174 );
2175
2176 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2177 assert_eq!(
2178 editor.snapshot(window, cx).scroll_position(),
2179 gpui::Point::new(0., 1.)
2180 );
2181 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2182 assert_eq!(
2183 editor.snapshot(window, cx).scroll_position(),
2184 gpui::Point::new(0., 3.)
2185 );
2186 });
2187}
2188
2189#[gpui::test]
2190async fn test_autoscroll(cx: &mut TestAppContext) {
2191 init_test(cx, |_| {});
2192 let mut cx = EditorTestContext::new(cx).await;
2193
2194 let line_height = cx.update_editor(|editor, window, cx| {
2195 editor.set_vertical_scroll_margin(2, cx);
2196 editor
2197 .style()
2198 .unwrap()
2199 .text
2200 .line_height_in_pixels(window.rem_size())
2201 });
2202 let window = cx.window;
2203 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2204
2205 cx.set_state(
2206 r#"ˇone
2207 two
2208 three
2209 four
2210 five
2211 six
2212 seven
2213 eight
2214 nine
2215 ten
2216 "#,
2217 );
2218 cx.update_editor(|editor, window, cx| {
2219 assert_eq!(
2220 editor.snapshot(window, cx).scroll_position(),
2221 gpui::Point::new(0., 0.0)
2222 );
2223 });
2224
2225 // Add a cursor below the visible area. Since both cursors cannot fit
2226 // on screen, the editor autoscrolls to reveal the newest cursor, and
2227 // allows the vertical scroll margin below that cursor.
2228 cx.update_editor(|editor, window, cx| {
2229 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2230 selections.select_ranges([
2231 Point::new(0, 0)..Point::new(0, 0),
2232 Point::new(6, 0)..Point::new(6, 0),
2233 ]);
2234 })
2235 });
2236 cx.update_editor(|editor, window, cx| {
2237 assert_eq!(
2238 editor.snapshot(window, cx).scroll_position(),
2239 gpui::Point::new(0., 3.0)
2240 );
2241 });
2242
2243 // Move down. The editor cursor scrolls down to track the newest cursor.
2244 cx.update_editor(|editor, window, cx| {
2245 editor.move_down(&Default::default(), window, cx);
2246 });
2247 cx.update_editor(|editor, window, cx| {
2248 assert_eq!(
2249 editor.snapshot(window, cx).scroll_position(),
2250 gpui::Point::new(0., 4.0)
2251 );
2252 });
2253
2254 // Add a cursor above the visible area. Since both cursors fit on screen,
2255 // the editor scrolls to show both.
2256 cx.update_editor(|editor, window, cx| {
2257 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2258 selections.select_ranges([
2259 Point::new(1, 0)..Point::new(1, 0),
2260 Point::new(6, 0)..Point::new(6, 0),
2261 ]);
2262 })
2263 });
2264 cx.update_editor(|editor, window, cx| {
2265 assert_eq!(
2266 editor.snapshot(window, cx).scroll_position(),
2267 gpui::Point::new(0., 1.0)
2268 );
2269 });
2270}
2271
2272#[gpui::test]
2273async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2274 init_test(cx, |_| {});
2275 let mut cx = EditorTestContext::new(cx).await;
2276
2277 let line_height = cx.editor(|editor, window, _cx| {
2278 editor
2279 .style()
2280 .unwrap()
2281 .text
2282 .line_height_in_pixels(window.rem_size())
2283 });
2284 let window = cx.window;
2285 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2286 cx.set_state(
2287 &r#"
2288 ˇone
2289 two
2290 threeˇ
2291 four
2292 five
2293 six
2294 seven
2295 eight
2296 nine
2297 ten
2298 "#
2299 .unindent(),
2300 );
2301
2302 cx.update_editor(|editor, window, cx| {
2303 editor.move_page_down(&MovePageDown::default(), window, cx)
2304 });
2305 cx.assert_editor_state(
2306 &r#"
2307 one
2308 two
2309 three
2310 ˇfour
2311 five
2312 sixˇ
2313 seven
2314 eight
2315 nine
2316 ten
2317 "#
2318 .unindent(),
2319 );
2320
2321 cx.update_editor(|editor, window, cx| {
2322 editor.move_page_down(&MovePageDown::default(), window, cx)
2323 });
2324 cx.assert_editor_state(
2325 &r#"
2326 one
2327 two
2328 three
2329 four
2330 five
2331 six
2332 ˇseven
2333 eight
2334 nineˇ
2335 ten
2336 "#
2337 .unindent(),
2338 );
2339
2340 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2341 cx.assert_editor_state(
2342 &r#"
2343 one
2344 two
2345 three
2346 ˇfour
2347 five
2348 sixˇ
2349 seven
2350 eight
2351 nine
2352 ten
2353 "#
2354 .unindent(),
2355 );
2356
2357 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2358 cx.assert_editor_state(
2359 &r#"
2360 ˇone
2361 two
2362 threeˇ
2363 four
2364 five
2365 six
2366 seven
2367 eight
2368 nine
2369 ten
2370 "#
2371 .unindent(),
2372 );
2373
2374 // Test select collapsing
2375 cx.update_editor(|editor, window, cx| {
2376 editor.move_page_down(&MovePageDown::default(), window, cx);
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 });
2380 cx.assert_editor_state(
2381 &r#"
2382 one
2383 two
2384 three
2385 four
2386 five
2387 six
2388 seven
2389 eight
2390 nine
2391 ˇten
2392 ˇ"#
2393 .unindent(),
2394 );
2395}
2396
2397#[gpui::test]
2398async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2399 init_test(cx, |_| {});
2400 let mut cx = EditorTestContext::new(cx).await;
2401 cx.set_state("one «two threeˇ» four");
2402 cx.update_editor(|editor, window, cx| {
2403 editor.delete_to_beginning_of_line(
2404 &DeleteToBeginningOfLine {
2405 stop_at_indent: false,
2406 },
2407 window,
2408 cx,
2409 );
2410 assert_eq!(editor.text(cx), " four");
2411 });
2412}
2413
2414#[gpui::test]
2415fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2416 init_test(cx, |_| {});
2417
2418 let editor = cx.add_window(|window, cx| {
2419 let buffer = MultiBuffer::build_simple("one two three four", cx);
2420 build_editor(buffer.clone(), window, cx)
2421 });
2422
2423 _ = editor.update(cx, |editor, window, cx| {
2424 editor.change_selections(None, window, cx, |s| {
2425 s.select_display_ranges([
2426 // an empty selection - the preceding word fragment is deleted
2427 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2428 // characters selected - they are deleted
2429 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2430 ])
2431 });
2432 editor.delete_to_previous_word_start(
2433 &DeleteToPreviousWordStart {
2434 ignore_newlines: false,
2435 },
2436 window,
2437 cx,
2438 );
2439 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2440 });
2441
2442 _ = editor.update(cx, |editor, window, cx| {
2443 editor.change_selections(None, window, cx, |s| {
2444 s.select_display_ranges([
2445 // an empty selection - the following word fragment is deleted
2446 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2447 // characters selected - they are deleted
2448 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2449 ])
2450 });
2451 editor.delete_to_next_word_end(
2452 &DeleteToNextWordEnd {
2453 ignore_newlines: false,
2454 },
2455 window,
2456 cx,
2457 );
2458 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2459 });
2460}
2461
2462#[gpui::test]
2463fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2464 init_test(cx, |_| {});
2465
2466 let editor = cx.add_window(|window, cx| {
2467 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2468 build_editor(buffer.clone(), window, cx)
2469 });
2470 let del_to_prev_word_start = DeleteToPreviousWordStart {
2471 ignore_newlines: false,
2472 };
2473 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2474 ignore_newlines: true,
2475 };
2476
2477 _ = editor.update(cx, |editor, window, cx| {
2478 editor.change_selections(None, window, cx, |s| {
2479 s.select_display_ranges([
2480 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2481 ])
2482 });
2483 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2484 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2485 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2486 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2487 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2488 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2489 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2490 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2491 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2492 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2493 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2494 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2495 });
2496}
2497
2498#[gpui::test]
2499fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2500 init_test(cx, |_| {});
2501
2502 let editor = cx.add_window(|window, cx| {
2503 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2504 build_editor(buffer.clone(), window, cx)
2505 });
2506 let del_to_next_word_end = DeleteToNextWordEnd {
2507 ignore_newlines: false,
2508 };
2509 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2510 ignore_newlines: true,
2511 };
2512
2513 _ = editor.update(cx, |editor, window, cx| {
2514 editor.change_selections(None, window, cx, |s| {
2515 s.select_display_ranges([
2516 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2517 ])
2518 });
2519 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2520 assert_eq!(
2521 editor.buffer.read(cx).read(cx).text(),
2522 "one\n two\nthree\n four"
2523 );
2524 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2525 assert_eq!(
2526 editor.buffer.read(cx).read(cx).text(),
2527 "\n two\nthree\n four"
2528 );
2529 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2530 assert_eq!(
2531 editor.buffer.read(cx).read(cx).text(),
2532 "two\nthree\n four"
2533 );
2534 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2535 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2536 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2537 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2538 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2539 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2540 });
2541}
2542
2543#[gpui::test]
2544fn test_newline(cx: &mut TestAppContext) {
2545 init_test(cx, |_| {});
2546
2547 let editor = cx.add_window(|window, cx| {
2548 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2549 build_editor(buffer.clone(), window, cx)
2550 });
2551
2552 _ = editor.update(cx, |editor, window, cx| {
2553 editor.change_selections(None, window, cx, |s| {
2554 s.select_display_ranges([
2555 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2556 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2557 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2558 ])
2559 });
2560
2561 editor.newline(&Newline, window, cx);
2562 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2563 });
2564}
2565
2566#[gpui::test]
2567fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2568 init_test(cx, |_| {});
2569
2570 let editor = cx.add_window(|window, cx| {
2571 let buffer = MultiBuffer::build_simple(
2572 "
2573 a
2574 b(
2575 X
2576 )
2577 c(
2578 X
2579 )
2580 "
2581 .unindent()
2582 .as_str(),
2583 cx,
2584 );
2585 let mut editor = build_editor(buffer.clone(), window, cx);
2586 editor.change_selections(None, window, cx, |s| {
2587 s.select_ranges([
2588 Point::new(2, 4)..Point::new(2, 5),
2589 Point::new(5, 4)..Point::new(5, 5),
2590 ])
2591 });
2592 editor
2593 });
2594
2595 _ = editor.update(cx, |editor, window, cx| {
2596 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2597 editor.buffer.update(cx, |buffer, cx| {
2598 buffer.edit(
2599 [
2600 (Point::new(1, 2)..Point::new(3, 0), ""),
2601 (Point::new(4, 2)..Point::new(6, 0), ""),
2602 ],
2603 None,
2604 cx,
2605 );
2606 assert_eq!(
2607 buffer.read(cx).text(),
2608 "
2609 a
2610 b()
2611 c()
2612 "
2613 .unindent()
2614 );
2615 });
2616 assert_eq!(
2617 editor.selections.ranges(cx),
2618 &[
2619 Point::new(1, 2)..Point::new(1, 2),
2620 Point::new(2, 2)..Point::new(2, 2),
2621 ],
2622 );
2623
2624 editor.newline(&Newline, window, cx);
2625 assert_eq!(
2626 editor.text(cx),
2627 "
2628 a
2629 b(
2630 )
2631 c(
2632 )
2633 "
2634 .unindent()
2635 );
2636
2637 // The selections are moved after the inserted newlines
2638 assert_eq!(
2639 editor.selections.ranges(cx),
2640 &[
2641 Point::new(2, 0)..Point::new(2, 0),
2642 Point::new(4, 0)..Point::new(4, 0),
2643 ],
2644 );
2645 });
2646}
2647
2648#[gpui::test]
2649async fn test_newline_above(cx: &mut TestAppContext) {
2650 init_test(cx, |settings| {
2651 settings.defaults.tab_size = NonZeroU32::new(4)
2652 });
2653
2654 let language = Arc::new(
2655 Language::new(
2656 LanguageConfig::default(),
2657 Some(tree_sitter_rust::LANGUAGE.into()),
2658 )
2659 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2660 .unwrap(),
2661 );
2662
2663 let mut cx = EditorTestContext::new(cx).await;
2664 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2665 cx.set_state(indoc! {"
2666 const a: ˇA = (
2667 (ˇ
2668 «const_functionˇ»(ˇ),
2669 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2670 )ˇ
2671 ˇ);ˇ
2672 "});
2673
2674 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2675 cx.assert_editor_state(indoc! {"
2676 ˇ
2677 const a: A = (
2678 ˇ
2679 (
2680 ˇ
2681 ˇ
2682 const_function(),
2683 ˇ
2684 ˇ
2685 ˇ
2686 ˇ
2687 something_else,
2688 ˇ
2689 )
2690 ˇ
2691 ˇ
2692 );
2693 "});
2694}
2695
2696#[gpui::test]
2697async fn test_newline_below(cx: &mut TestAppContext) {
2698 init_test(cx, |settings| {
2699 settings.defaults.tab_size = NonZeroU32::new(4)
2700 });
2701
2702 let language = Arc::new(
2703 Language::new(
2704 LanguageConfig::default(),
2705 Some(tree_sitter_rust::LANGUAGE.into()),
2706 )
2707 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2708 .unwrap(),
2709 );
2710
2711 let mut cx = EditorTestContext::new(cx).await;
2712 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2713 cx.set_state(indoc! {"
2714 const a: ˇA = (
2715 (ˇ
2716 «const_functionˇ»(ˇ),
2717 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2718 )ˇ
2719 ˇ);ˇ
2720 "});
2721
2722 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2723 cx.assert_editor_state(indoc! {"
2724 const a: A = (
2725 ˇ
2726 (
2727 ˇ
2728 const_function(),
2729 ˇ
2730 ˇ
2731 something_else,
2732 ˇ
2733 ˇ
2734 ˇ
2735 ˇ
2736 )
2737 ˇ
2738 );
2739 ˇ
2740 ˇ
2741 "});
2742}
2743
2744#[gpui::test]
2745async fn test_newline_comments(cx: &mut TestAppContext) {
2746 init_test(cx, |settings| {
2747 settings.defaults.tab_size = NonZeroU32::new(4)
2748 });
2749
2750 let language = Arc::new(Language::new(
2751 LanguageConfig {
2752 line_comments: vec!["//".into()],
2753 ..LanguageConfig::default()
2754 },
2755 None,
2756 ));
2757 {
2758 let mut cx = EditorTestContext::new(cx).await;
2759 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2760 cx.set_state(indoc! {"
2761 // Fooˇ
2762 "});
2763
2764 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2765 cx.assert_editor_state(indoc! {"
2766 // Foo
2767 //ˇ
2768 "});
2769 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2770 cx.set_state(indoc! {"
2771 ˇ// Foo
2772 "});
2773 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2774 cx.assert_editor_state(indoc! {"
2775
2776 ˇ// Foo
2777 "});
2778 }
2779 // Ensure that comment continuations can be disabled.
2780 update_test_language_settings(cx, |settings| {
2781 settings.defaults.extend_comment_on_newline = Some(false);
2782 });
2783 let mut cx = EditorTestContext::new(cx).await;
2784 cx.set_state(indoc! {"
2785 // Fooˇ
2786 "});
2787 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2788 cx.assert_editor_state(indoc! {"
2789 // Foo
2790 ˇ
2791 "});
2792}
2793
2794#[gpui::test]
2795fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2796 init_test(cx, |_| {});
2797
2798 let editor = cx.add_window(|window, cx| {
2799 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2800 let mut editor = build_editor(buffer.clone(), window, cx);
2801 editor.change_selections(None, window, cx, |s| {
2802 s.select_ranges([3..4, 11..12, 19..20])
2803 });
2804 editor
2805 });
2806
2807 _ = editor.update(cx, |editor, window, cx| {
2808 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2809 editor.buffer.update(cx, |buffer, cx| {
2810 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2811 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2812 });
2813 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2814
2815 editor.insert("Z", window, cx);
2816 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2817
2818 // The selections are moved after the inserted characters
2819 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2820 });
2821}
2822
2823#[gpui::test]
2824async fn test_tab(cx: &mut TestAppContext) {
2825 init_test(cx, |settings| {
2826 settings.defaults.tab_size = NonZeroU32::new(3)
2827 });
2828
2829 let mut cx = EditorTestContext::new(cx).await;
2830 cx.set_state(indoc! {"
2831 ˇabˇc
2832 ˇ🏀ˇ🏀ˇefg
2833 dˇ
2834 "});
2835 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2836 cx.assert_editor_state(indoc! {"
2837 ˇab ˇc
2838 ˇ🏀 ˇ🏀 ˇefg
2839 d ˇ
2840 "});
2841
2842 cx.set_state(indoc! {"
2843 a
2844 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2845 "});
2846 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2847 cx.assert_editor_state(indoc! {"
2848 a
2849 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2850 "});
2851}
2852
2853#[gpui::test]
2854async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2855 init_test(cx, |_| {});
2856
2857 let mut cx = EditorTestContext::new(cx).await;
2858 let language = Arc::new(
2859 Language::new(
2860 LanguageConfig::default(),
2861 Some(tree_sitter_rust::LANGUAGE.into()),
2862 )
2863 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2864 .unwrap(),
2865 );
2866 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2867
2868 // cursors that are already at the suggested indent level insert
2869 // a soft tab. cursors that are to the left of the suggested indent
2870 // auto-indent their line.
2871 cx.set_state(indoc! {"
2872 ˇ
2873 const a: B = (
2874 c(
2875 d(
2876 ˇ
2877 )
2878 ˇ
2879 ˇ )
2880 );
2881 "});
2882 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2883 cx.assert_editor_state(indoc! {"
2884 ˇ
2885 const a: B = (
2886 c(
2887 d(
2888 ˇ
2889 )
2890 ˇ
2891 ˇ)
2892 );
2893 "});
2894
2895 // handle auto-indent when there are multiple cursors on the same line
2896 cx.set_state(indoc! {"
2897 const a: B = (
2898 c(
2899 ˇ ˇ
2900 ˇ )
2901 );
2902 "});
2903 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2904 cx.assert_editor_state(indoc! {"
2905 const a: B = (
2906 c(
2907 ˇ
2908 ˇ)
2909 );
2910 "});
2911}
2912
2913#[gpui::test]
2914async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2915 init_test(cx, |settings| {
2916 settings.defaults.tab_size = NonZeroU32::new(4)
2917 });
2918
2919 let language = Arc::new(
2920 Language::new(
2921 LanguageConfig::default(),
2922 Some(tree_sitter_rust::LANGUAGE.into()),
2923 )
2924 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2925 .unwrap(),
2926 );
2927
2928 let mut cx = EditorTestContext::new(cx).await;
2929 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2930 cx.set_state(indoc! {"
2931 fn a() {
2932 if b {
2933 \t ˇc
2934 }
2935 }
2936 "});
2937
2938 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2939 cx.assert_editor_state(indoc! {"
2940 fn a() {
2941 if b {
2942 ˇc
2943 }
2944 }
2945 "});
2946}
2947
2948#[gpui::test]
2949async fn test_indent_outdent(cx: &mut TestAppContext) {
2950 init_test(cx, |settings| {
2951 settings.defaults.tab_size = NonZeroU32::new(4);
2952 });
2953
2954 let mut cx = EditorTestContext::new(cx).await;
2955
2956 cx.set_state(indoc! {"
2957 «oneˇ» «twoˇ»
2958 three
2959 four
2960 "});
2961 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2962 cx.assert_editor_state(indoc! {"
2963 «oneˇ» «twoˇ»
2964 three
2965 four
2966 "});
2967
2968 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 «oneˇ» «twoˇ»
2971 three
2972 four
2973 "});
2974
2975 // select across line ending
2976 cx.set_state(indoc! {"
2977 one two
2978 t«hree
2979 ˇ» four
2980 "});
2981 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2982 cx.assert_editor_state(indoc! {"
2983 one two
2984 t«hree
2985 ˇ» four
2986 "});
2987
2988 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2989 cx.assert_editor_state(indoc! {"
2990 one two
2991 t«hree
2992 ˇ» four
2993 "});
2994
2995 // Ensure that indenting/outdenting works when the cursor is at column 0.
2996 cx.set_state(indoc! {"
2997 one two
2998 ˇthree
2999 four
3000 "});
3001 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3002 cx.assert_editor_state(indoc! {"
3003 one two
3004 ˇthree
3005 four
3006 "});
3007
3008 cx.set_state(indoc! {"
3009 one two
3010 ˇ three
3011 four
3012 "});
3013 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3014 cx.assert_editor_state(indoc! {"
3015 one two
3016 ˇthree
3017 four
3018 "});
3019}
3020
3021#[gpui::test]
3022async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3023 init_test(cx, |settings| {
3024 settings.defaults.hard_tabs = Some(true);
3025 });
3026
3027 let mut cx = EditorTestContext::new(cx).await;
3028
3029 // select two ranges on one line
3030 cx.set_state(indoc! {"
3031 «oneˇ» «twoˇ»
3032 three
3033 four
3034 "});
3035 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3036 cx.assert_editor_state(indoc! {"
3037 \t«oneˇ» «twoˇ»
3038 three
3039 four
3040 "});
3041 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3042 cx.assert_editor_state(indoc! {"
3043 \t\t«oneˇ» «twoˇ»
3044 three
3045 four
3046 "});
3047 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3048 cx.assert_editor_state(indoc! {"
3049 \t«oneˇ» «twoˇ»
3050 three
3051 four
3052 "});
3053 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3054 cx.assert_editor_state(indoc! {"
3055 «oneˇ» «twoˇ»
3056 three
3057 four
3058 "});
3059
3060 // select across a line ending
3061 cx.set_state(indoc! {"
3062 one two
3063 t«hree
3064 ˇ»four
3065 "});
3066 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3067 cx.assert_editor_state(indoc! {"
3068 one two
3069 \tt«hree
3070 ˇ»four
3071 "});
3072 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3073 cx.assert_editor_state(indoc! {"
3074 one two
3075 \t\tt«hree
3076 ˇ»four
3077 "});
3078 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3079 cx.assert_editor_state(indoc! {"
3080 one two
3081 \tt«hree
3082 ˇ»four
3083 "});
3084 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3085 cx.assert_editor_state(indoc! {"
3086 one two
3087 t«hree
3088 ˇ»four
3089 "});
3090
3091 // Ensure that indenting/outdenting works when the cursor is at column 0.
3092 cx.set_state(indoc! {"
3093 one two
3094 ˇthree
3095 four
3096 "});
3097 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3098 cx.assert_editor_state(indoc! {"
3099 one two
3100 ˇthree
3101 four
3102 "});
3103 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3104 cx.assert_editor_state(indoc! {"
3105 one two
3106 \tˇthree
3107 four
3108 "});
3109 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3110 cx.assert_editor_state(indoc! {"
3111 one two
3112 ˇthree
3113 four
3114 "});
3115}
3116
3117#[gpui::test]
3118fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3119 init_test(cx, |settings| {
3120 settings.languages.extend([
3121 (
3122 "TOML".into(),
3123 LanguageSettingsContent {
3124 tab_size: NonZeroU32::new(2),
3125 ..Default::default()
3126 },
3127 ),
3128 (
3129 "Rust".into(),
3130 LanguageSettingsContent {
3131 tab_size: NonZeroU32::new(4),
3132 ..Default::default()
3133 },
3134 ),
3135 ]);
3136 });
3137
3138 let toml_language = Arc::new(Language::new(
3139 LanguageConfig {
3140 name: "TOML".into(),
3141 ..Default::default()
3142 },
3143 None,
3144 ));
3145 let rust_language = Arc::new(Language::new(
3146 LanguageConfig {
3147 name: "Rust".into(),
3148 ..Default::default()
3149 },
3150 None,
3151 ));
3152
3153 let toml_buffer =
3154 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3155 let rust_buffer =
3156 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3157 let multibuffer = cx.new(|cx| {
3158 let mut multibuffer = MultiBuffer::new(ReadWrite);
3159 multibuffer.push_excerpts(
3160 toml_buffer.clone(),
3161 [ExcerptRange {
3162 context: Point::new(0, 0)..Point::new(2, 0),
3163 primary: None,
3164 }],
3165 cx,
3166 );
3167 multibuffer.push_excerpts(
3168 rust_buffer.clone(),
3169 [ExcerptRange {
3170 context: Point::new(0, 0)..Point::new(1, 0),
3171 primary: None,
3172 }],
3173 cx,
3174 );
3175 multibuffer
3176 });
3177
3178 cx.add_window(|window, cx| {
3179 let mut editor = build_editor(multibuffer, window, cx);
3180
3181 assert_eq!(
3182 editor.text(cx),
3183 indoc! {"
3184 a = 1
3185 b = 2
3186
3187 const c: usize = 3;
3188 "}
3189 );
3190
3191 select_ranges(
3192 &mut editor,
3193 indoc! {"
3194 «aˇ» = 1
3195 b = 2
3196
3197 «const c:ˇ» usize = 3;
3198 "},
3199 window,
3200 cx,
3201 );
3202
3203 editor.tab(&Tab, window, cx);
3204 assert_text_with_selections(
3205 &mut editor,
3206 indoc! {"
3207 «aˇ» = 1
3208 b = 2
3209
3210 «const c:ˇ» usize = 3;
3211 "},
3212 cx,
3213 );
3214 editor.backtab(&Backtab, window, cx);
3215 assert_text_with_selections(
3216 &mut editor,
3217 indoc! {"
3218 «aˇ» = 1
3219 b = 2
3220
3221 «const c:ˇ» usize = 3;
3222 "},
3223 cx,
3224 );
3225
3226 editor
3227 });
3228}
3229
3230#[gpui::test]
3231async fn test_backspace(cx: &mut TestAppContext) {
3232 init_test(cx, |_| {});
3233
3234 let mut cx = EditorTestContext::new(cx).await;
3235
3236 // Basic backspace
3237 cx.set_state(indoc! {"
3238 onˇe two three
3239 fou«rˇ» five six
3240 seven «ˇeight nine
3241 »ten
3242 "});
3243 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3244 cx.assert_editor_state(indoc! {"
3245 oˇe two three
3246 fouˇ five six
3247 seven ˇten
3248 "});
3249
3250 // Test backspace inside and around indents
3251 cx.set_state(indoc! {"
3252 zero
3253 ˇone
3254 ˇtwo
3255 ˇ ˇ ˇ three
3256 ˇ ˇ four
3257 "});
3258 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3259 cx.assert_editor_state(indoc! {"
3260 zero
3261 ˇone
3262 ˇtwo
3263 ˇ threeˇ four
3264 "});
3265
3266 // Test backspace with line_mode set to true
3267 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3268 cx.set_state(indoc! {"
3269 The ˇquick ˇbrown
3270 fox jumps over
3271 the lazy dog
3272 ˇThe qu«ick bˇ»rown"});
3273 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3274 cx.assert_editor_state(indoc! {"
3275 ˇfox jumps over
3276 the lazy dogˇ"});
3277}
3278
3279#[gpui::test]
3280async fn test_delete(cx: &mut TestAppContext) {
3281 init_test(cx, |_| {});
3282
3283 let mut cx = EditorTestContext::new(cx).await;
3284 cx.set_state(indoc! {"
3285 onˇe two three
3286 fou«rˇ» five six
3287 seven «ˇeight nine
3288 »ten
3289 "});
3290 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3291 cx.assert_editor_state(indoc! {"
3292 onˇ two three
3293 fouˇ five six
3294 seven ˇten
3295 "});
3296
3297 // Test backspace with line_mode set to true
3298 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3299 cx.set_state(indoc! {"
3300 The ˇquick ˇbrown
3301 fox «ˇjum»ps over
3302 the lazy dog
3303 ˇThe qu«ick bˇ»rown"});
3304 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3305 cx.assert_editor_state("ˇthe lazy dogˇ");
3306}
3307
3308#[gpui::test]
3309fn test_delete_line(cx: &mut TestAppContext) {
3310 init_test(cx, |_| {});
3311
3312 let editor = cx.add_window(|window, cx| {
3313 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3314 build_editor(buffer, window, cx)
3315 });
3316 _ = editor.update(cx, |editor, window, cx| {
3317 editor.change_selections(None, window, cx, |s| {
3318 s.select_display_ranges([
3319 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3320 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3321 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3322 ])
3323 });
3324 editor.delete_line(&DeleteLine, window, cx);
3325 assert_eq!(editor.display_text(cx), "ghi");
3326 assert_eq!(
3327 editor.selections.display_ranges(cx),
3328 vec![
3329 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3330 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3331 ]
3332 );
3333 });
3334
3335 let editor = cx.add_window(|window, cx| {
3336 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3337 build_editor(buffer, window, cx)
3338 });
3339 _ = editor.update(cx, |editor, window, cx| {
3340 editor.change_selections(None, window, cx, |s| {
3341 s.select_display_ranges([
3342 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3343 ])
3344 });
3345 editor.delete_line(&DeleteLine, window, cx);
3346 assert_eq!(editor.display_text(cx), "ghi\n");
3347 assert_eq!(
3348 editor.selections.display_ranges(cx),
3349 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3350 );
3351 });
3352}
3353
3354#[gpui::test]
3355fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3356 init_test(cx, |_| {});
3357
3358 cx.add_window(|window, cx| {
3359 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3360 let mut editor = build_editor(buffer.clone(), window, cx);
3361 let buffer = buffer.read(cx).as_singleton().unwrap();
3362
3363 assert_eq!(
3364 editor.selections.ranges::<Point>(cx),
3365 &[Point::new(0, 0)..Point::new(0, 0)]
3366 );
3367
3368 // When on single line, replace newline at end by space
3369 editor.join_lines(&JoinLines, window, cx);
3370 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3371 assert_eq!(
3372 editor.selections.ranges::<Point>(cx),
3373 &[Point::new(0, 3)..Point::new(0, 3)]
3374 );
3375
3376 // When multiple lines are selected, remove newlines that are spanned by the selection
3377 editor.change_selections(None, window, cx, |s| {
3378 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3379 });
3380 editor.join_lines(&JoinLines, window, cx);
3381 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3382 assert_eq!(
3383 editor.selections.ranges::<Point>(cx),
3384 &[Point::new(0, 11)..Point::new(0, 11)]
3385 );
3386
3387 // Undo should be transactional
3388 editor.undo(&Undo, window, cx);
3389 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3390 assert_eq!(
3391 editor.selections.ranges::<Point>(cx),
3392 &[Point::new(0, 5)..Point::new(2, 2)]
3393 );
3394
3395 // When joining an empty line don't insert a space
3396 editor.change_selections(None, window, cx, |s| {
3397 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3398 });
3399 editor.join_lines(&JoinLines, window, cx);
3400 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3401 assert_eq!(
3402 editor.selections.ranges::<Point>(cx),
3403 [Point::new(2, 3)..Point::new(2, 3)]
3404 );
3405
3406 // We can remove trailing newlines
3407 editor.join_lines(&JoinLines, window, cx);
3408 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3409 assert_eq!(
3410 editor.selections.ranges::<Point>(cx),
3411 [Point::new(2, 3)..Point::new(2, 3)]
3412 );
3413
3414 // We don't blow up on the last line
3415 editor.join_lines(&JoinLines, window, cx);
3416 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3417 assert_eq!(
3418 editor.selections.ranges::<Point>(cx),
3419 [Point::new(2, 3)..Point::new(2, 3)]
3420 );
3421
3422 // reset to test indentation
3423 editor.buffer.update(cx, |buffer, cx| {
3424 buffer.edit(
3425 [
3426 (Point::new(1, 0)..Point::new(1, 2), " "),
3427 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3428 ],
3429 None,
3430 cx,
3431 )
3432 });
3433
3434 // We remove any leading spaces
3435 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3436 editor.change_selections(None, window, cx, |s| {
3437 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3438 });
3439 editor.join_lines(&JoinLines, window, cx);
3440 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3441
3442 // We don't insert a space for a line containing only spaces
3443 editor.join_lines(&JoinLines, window, cx);
3444 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3445
3446 // We ignore any leading tabs
3447 editor.join_lines(&JoinLines, window, cx);
3448 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3449
3450 editor
3451 });
3452}
3453
3454#[gpui::test]
3455fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3456 init_test(cx, |_| {});
3457
3458 cx.add_window(|window, cx| {
3459 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3460 let mut editor = build_editor(buffer.clone(), window, cx);
3461 let buffer = buffer.read(cx).as_singleton().unwrap();
3462
3463 editor.change_selections(None, window, cx, |s| {
3464 s.select_ranges([
3465 Point::new(0, 2)..Point::new(1, 1),
3466 Point::new(1, 2)..Point::new(1, 2),
3467 Point::new(3, 1)..Point::new(3, 2),
3468 ])
3469 });
3470
3471 editor.join_lines(&JoinLines, window, cx);
3472 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3473
3474 assert_eq!(
3475 editor.selections.ranges::<Point>(cx),
3476 [
3477 Point::new(0, 7)..Point::new(0, 7),
3478 Point::new(1, 3)..Point::new(1, 3)
3479 ]
3480 );
3481 editor
3482 });
3483}
3484
3485#[gpui::test]
3486async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3487 init_test(cx, |_| {});
3488
3489 let mut cx = EditorTestContext::new(cx).await;
3490
3491 let diff_base = r#"
3492 Line 0
3493 Line 1
3494 Line 2
3495 Line 3
3496 "#
3497 .unindent();
3498
3499 cx.set_state(
3500 &r#"
3501 ˇLine 0
3502 Line 1
3503 Line 2
3504 Line 3
3505 "#
3506 .unindent(),
3507 );
3508
3509 cx.set_head_text(&diff_base);
3510 executor.run_until_parked();
3511
3512 // Join lines
3513 cx.update_editor(|editor, window, cx| {
3514 editor.join_lines(&JoinLines, window, cx);
3515 });
3516 executor.run_until_parked();
3517
3518 cx.assert_editor_state(
3519 &r#"
3520 Line 0ˇ Line 1
3521 Line 2
3522 Line 3
3523 "#
3524 .unindent(),
3525 );
3526 // Join again
3527 cx.update_editor(|editor, window, cx| {
3528 editor.join_lines(&JoinLines, window, cx);
3529 });
3530 executor.run_until_parked();
3531
3532 cx.assert_editor_state(
3533 &r#"
3534 Line 0 Line 1ˇ Line 2
3535 Line 3
3536 "#
3537 .unindent(),
3538 );
3539}
3540
3541#[gpui::test]
3542async fn test_custom_newlines_cause_no_false_positive_diffs(
3543 executor: BackgroundExecutor,
3544 cx: &mut TestAppContext,
3545) {
3546 init_test(cx, |_| {});
3547 let mut cx = EditorTestContext::new(cx).await;
3548 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3549 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3550 executor.run_until_parked();
3551
3552 cx.update_editor(|editor, window, cx| {
3553 let snapshot = editor.snapshot(window, cx);
3554 assert_eq!(
3555 snapshot
3556 .buffer_snapshot
3557 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3558 .collect::<Vec<_>>(),
3559 Vec::new(),
3560 "Should not have any diffs for files with custom newlines"
3561 );
3562 });
3563}
3564
3565#[gpui::test]
3566async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 // Test sort_lines_case_insensitive()
3572 cx.set_state(indoc! {"
3573 «z
3574 y
3575 x
3576 Z
3577 Y
3578 Xˇ»
3579 "});
3580 cx.update_editor(|e, window, cx| {
3581 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3582 });
3583 cx.assert_editor_state(indoc! {"
3584 «x
3585 X
3586 y
3587 Y
3588 z
3589 Zˇ»
3590 "});
3591
3592 // Test reverse_lines()
3593 cx.set_state(indoc! {"
3594 «5
3595 4
3596 3
3597 2
3598 1ˇ»
3599 "});
3600 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3601 cx.assert_editor_state(indoc! {"
3602 «1
3603 2
3604 3
3605 4
3606 5ˇ»
3607 "});
3608
3609 // Skip testing shuffle_line()
3610
3611 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3612 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3613
3614 // Don't manipulate when cursor is on single line, but expand the selection
3615 cx.set_state(indoc! {"
3616 ddˇdd
3617 ccc
3618 bb
3619 a
3620 "});
3621 cx.update_editor(|e, window, cx| {
3622 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3623 });
3624 cx.assert_editor_state(indoc! {"
3625 «ddddˇ»
3626 ccc
3627 bb
3628 a
3629 "});
3630
3631 // Basic manipulate case
3632 // Start selection moves to column 0
3633 // End of selection shrinks to fit shorter line
3634 cx.set_state(indoc! {"
3635 dd«d
3636 ccc
3637 bb
3638 aaaaaˇ»
3639 "});
3640 cx.update_editor(|e, window, cx| {
3641 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3642 });
3643 cx.assert_editor_state(indoc! {"
3644 «aaaaa
3645 bb
3646 ccc
3647 dddˇ»
3648 "});
3649
3650 // Manipulate case with newlines
3651 cx.set_state(indoc! {"
3652 dd«d
3653 ccc
3654
3655 bb
3656 aaaaa
3657
3658 ˇ»
3659 "});
3660 cx.update_editor(|e, window, cx| {
3661 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3662 });
3663 cx.assert_editor_state(indoc! {"
3664 «
3665
3666 aaaaa
3667 bb
3668 ccc
3669 dddˇ»
3670
3671 "});
3672
3673 // Adding new line
3674 cx.set_state(indoc! {"
3675 aa«a
3676 bbˇ»b
3677 "});
3678 cx.update_editor(|e, window, cx| {
3679 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3680 });
3681 cx.assert_editor_state(indoc! {"
3682 «aaa
3683 bbb
3684 added_lineˇ»
3685 "});
3686
3687 // Removing line
3688 cx.set_state(indoc! {"
3689 aa«a
3690 bbbˇ»
3691 "});
3692 cx.update_editor(|e, window, cx| {
3693 e.manipulate_lines(window, cx, |lines| {
3694 lines.pop();
3695 })
3696 });
3697 cx.assert_editor_state(indoc! {"
3698 «aaaˇ»
3699 "});
3700
3701 // Removing all lines
3702 cx.set_state(indoc! {"
3703 aa«a
3704 bbbˇ»
3705 "});
3706 cx.update_editor(|e, window, cx| {
3707 e.manipulate_lines(window, cx, |lines| {
3708 lines.drain(..);
3709 })
3710 });
3711 cx.assert_editor_state(indoc! {"
3712 ˇ
3713 "});
3714}
3715
3716#[gpui::test]
3717async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3718 init_test(cx, |_| {});
3719
3720 let mut cx = EditorTestContext::new(cx).await;
3721
3722 // Consider continuous selection as single selection
3723 cx.set_state(indoc! {"
3724 Aaa«aa
3725 cˇ»c«c
3726 bb
3727 aaaˇ»aa
3728 "});
3729 cx.update_editor(|e, window, cx| {
3730 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3731 });
3732 cx.assert_editor_state(indoc! {"
3733 «Aaaaa
3734 ccc
3735 bb
3736 aaaaaˇ»
3737 "});
3738
3739 cx.set_state(indoc! {"
3740 Aaa«aa
3741 cˇ»c«c
3742 bb
3743 aaaˇ»aa
3744 "});
3745 cx.update_editor(|e, window, cx| {
3746 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3747 });
3748 cx.assert_editor_state(indoc! {"
3749 «Aaaaa
3750 ccc
3751 bbˇ»
3752 "});
3753
3754 // Consider non continuous selection as distinct dedup operations
3755 cx.set_state(indoc! {"
3756 «aaaaa
3757 bb
3758 aaaaa
3759 aaaaaˇ»
3760
3761 aaa«aaˇ»
3762 "});
3763 cx.update_editor(|e, window, cx| {
3764 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3765 });
3766 cx.assert_editor_state(indoc! {"
3767 «aaaaa
3768 bbˇ»
3769
3770 «aaaaaˇ»
3771 "});
3772}
3773
3774#[gpui::test]
3775async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3776 init_test(cx, |_| {});
3777
3778 let mut cx = EditorTestContext::new(cx).await;
3779
3780 cx.set_state(indoc! {"
3781 «Aaa
3782 aAa
3783 Aaaˇ»
3784 "});
3785 cx.update_editor(|e, window, cx| {
3786 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3787 });
3788 cx.assert_editor_state(indoc! {"
3789 «Aaa
3790 aAaˇ»
3791 "});
3792
3793 cx.set_state(indoc! {"
3794 «Aaa
3795 aAa
3796 aaAˇ»
3797 "});
3798 cx.update_editor(|e, window, cx| {
3799 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3800 });
3801 cx.assert_editor_state(indoc! {"
3802 «Aaaˇ»
3803 "});
3804}
3805
3806#[gpui::test]
3807async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3808 init_test(cx, |_| {});
3809
3810 let mut cx = EditorTestContext::new(cx).await;
3811
3812 // Manipulate with multiple selections on a single line
3813 cx.set_state(indoc! {"
3814 dd«dd
3815 cˇ»c«c
3816 bb
3817 aaaˇ»aa
3818 "});
3819 cx.update_editor(|e, window, cx| {
3820 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3821 });
3822 cx.assert_editor_state(indoc! {"
3823 «aaaaa
3824 bb
3825 ccc
3826 ddddˇ»
3827 "});
3828
3829 // Manipulate with multiple disjoin selections
3830 cx.set_state(indoc! {"
3831 5«
3832 4
3833 3
3834 2
3835 1ˇ»
3836
3837 dd«dd
3838 ccc
3839 bb
3840 aaaˇ»aa
3841 "});
3842 cx.update_editor(|e, window, cx| {
3843 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3844 });
3845 cx.assert_editor_state(indoc! {"
3846 «1
3847 2
3848 3
3849 4
3850 5ˇ»
3851
3852 «aaaaa
3853 bb
3854 ccc
3855 ddddˇ»
3856 "});
3857
3858 // Adding lines on each selection
3859 cx.set_state(indoc! {"
3860 2«
3861 1ˇ»
3862
3863 bb«bb
3864 aaaˇ»aa
3865 "});
3866 cx.update_editor(|e, window, cx| {
3867 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3868 });
3869 cx.assert_editor_state(indoc! {"
3870 «2
3871 1
3872 added lineˇ»
3873
3874 «bbbb
3875 aaaaa
3876 added lineˇ»
3877 "});
3878
3879 // Removing lines on each selection
3880 cx.set_state(indoc! {"
3881 2«
3882 1ˇ»
3883
3884 bb«bb
3885 aaaˇ»aa
3886 "});
3887 cx.update_editor(|e, window, cx| {
3888 e.manipulate_lines(window, cx, |lines| {
3889 lines.pop();
3890 })
3891 });
3892 cx.assert_editor_state(indoc! {"
3893 «2ˇ»
3894
3895 «bbbbˇ»
3896 "});
3897}
3898
3899#[gpui::test]
3900async fn test_manipulate_text(cx: &mut TestAppContext) {
3901 init_test(cx, |_| {});
3902
3903 let mut cx = EditorTestContext::new(cx).await;
3904
3905 // Test convert_to_upper_case()
3906 cx.set_state(indoc! {"
3907 «hello worldˇ»
3908 "});
3909 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3910 cx.assert_editor_state(indoc! {"
3911 «HELLO WORLDˇ»
3912 "});
3913
3914 // Test convert_to_lower_case()
3915 cx.set_state(indoc! {"
3916 «HELLO WORLDˇ»
3917 "});
3918 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3919 cx.assert_editor_state(indoc! {"
3920 «hello worldˇ»
3921 "});
3922
3923 // Test multiple line, single selection case
3924 cx.set_state(indoc! {"
3925 «The quick brown
3926 fox jumps over
3927 the lazy dogˇ»
3928 "});
3929 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3930 cx.assert_editor_state(indoc! {"
3931 «The Quick Brown
3932 Fox Jumps Over
3933 The Lazy Dogˇ»
3934 "});
3935
3936 // Test multiple line, single selection case
3937 cx.set_state(indoc! {"
3938 «The quick brown
3939 fox jumps over
3940 the lazy dogˇ»
3941 "});
3942 cx.update_editor(|e, window, cx| {
3943 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3944 });
3945 cx.assert_editor_state(indoc! {"
3946 «TheQuickBrown
3947 FoxJumpsOver
3948 TheLazyDogˇ»
3949 "});
3950
3951 // From here on out, test more complex cases of manipulate_text()
3952
3953 // Test no selection case - should affect words cursors are in
3954 // Cursor at beginning, middle, and end of word
3955 cx.set_state(indoc! {"
3956 ˇhello big beauˇtiful worldˇ
3957 "});
3958 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3959 cx.assert_editor_state(indoc! {"
3960 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3961 "});
3962
3963 // Test multiple selections on a single line and across multiple lines
3964 cx.set_state(indoc! {"
3965 «Theˇ» quick «brown
3966 foxˇ» jumps «overˇ»
3967 the «lazyˇ» dog
3968 "});
3969 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3970 cx.assert_editor_state(indoc! {"
3971 «THEˇ» quick «BROWN
3972 FOXˇ» jumps «OVERˇ»
3973 the «LAZYˇ» dog
3974 "});
3975
3976 // Test case where text length grows
3977 cx.set_state(indoc! {"
3978 «tschüߡ»
3979 "});
3980 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3981 cx.assert_editor_state(indoc! {"
3982 «TSCHÜSSˇ»
3983 "});
3984
3985 // Test to make sure we don't crash when text shrinks
3986 cx.set_state(indoc! {"
3987 aaa_bbbˇ
3988 "});
3989 cx.update_editor(|e, window, cx| {
3990 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3991 });
3992 cx.assert_editor_state(indoc! {"
3993 «aaaBbbˇ»
3994 "});
3995
3996 // Test to make sure we all aware of the fact that each word can grow and shrink
3997 // Final selections should be aware of this fact
3998 cx.set_state(indoc! {"
3999 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4000 "});
4001 cx.update_editor(|e, window, cx| {
4002 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4003 });
4004 cx.assert_editor_state(indoc! {"
4005 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4006 "});
4007
4008 cx.set_state(indoc! {"
4009 «hElLo, WoRld!ˇ»
4010 "});
4011 cx.update_editor(|e, window, cx| {
4012 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4013 });
4014 cx.assert_editor_state(indoc! {"
4015 «HeLlO, wOrLD!ˇ»
4016 "});
4017}
4018
4019#[gpui::test]
4020fn test_duplicate_line(cx: &mut TestAppContext) {
4021 init_test(cx, |_| {});
4022
4023 let editor = cx.add_window(|window, cx| {
4024 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4025 build_editor(buffer, window, cx)
4026 });
4027 _ = editor.update(cx, |editor, window, cx| {
4028 editor.change_selections(None, window, cx, |s| {
4029 s.select_display_ranges([
4030 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4031 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4032 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4033 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4034 ])
4035 });
4036 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4037 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4038 assert_eq!(
4039 editor.selections.display_ranges(cx),
4040 vec![
4041 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4042 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4043 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4044 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4045 ]
4046 );
4047 });
4048
4049 let editor = cx.add_window(|window, cx| {
4050 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4051 build_editor(buffer, window, cx)
4052 });
4053 _ = editor.update(cx, |editor, window, cx| {
4054 editor.change_selections(None, window, cx, |s| {
4055 s.select_display_ranges([
4056 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4057 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4058 ])
4059 });
4060 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4061 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4062 assert_eq!(
4063 editor.selections.display_ranges(cx),
4064 vec![
4065 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4066 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4067 ]
4068 );
4069 });
4070
4071 // With `move_upwards` the selections stay in place, except for
4072 // the lines inserted above them
4073 let editor = cx.add_window(|window, cx| {
4074 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4075 build_editor(buffer, window, cx)
4076 });
4077 _ = editor.update(cx, |editor, window, cx| {
4078 editor.change_selections(None, window, cx, |s| {
4079 s.select_display_ranges([
4080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4081 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4082 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4083 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4084 ])
4085 });
4086 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4087 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4088 assert_eq!(
4089 editor.selections.display_ranges(cx),
4090 vec![
4091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4092 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4093 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4094 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4095 ]
4096 );
4097 });
4098
4099 let editor = cx.add_window(|window, cx| {
4100 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4101 build_editor(buffer, window, cx)
4102 });
4103 _ = editor.update(cx, |editor, window, cx| {
4104 editor.change_selections(None, window, cx, |s| {
4105 s.select_display_ranges([
4106 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4107 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4108 ])
4109 });
4110 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4111 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4112 assert_eq!(
4113 editor.selections.display_ranges(cx),
4114 vec![
4115 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4116 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4117 ]
4118 );
4119 });
4120
4121 let editor = cx.add_window(|window, cx| {
4122 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4123 build_editor(buffer, window, cx)
4124 });
4125 _ = editor.update(cx, |editor, window, cx| {
4126 editor.change_selections(None, window, cx, |s| {
4127 s.select_display_ranges([
4128 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4129 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4130 ])
4131 });
4132 editor.duplicate_selection(&DuplicateSelection, window, cx);
4133 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4134 assert_eq!(
4135 editor.selections.display_ranges(cx),
4136 vec![
4137 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4138 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4139 ]
4140 );
4141 });
4142}
4143
4144#[gpui::test]
4145fn test_move_line_up_down(cx: &mut TestAppContext) {
4146 init_test(cx, |_| {});
4147
4148 let editor = cx.add_window(|window, cx| {
4149 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4150 build_editor(buffer, window, cx)
4151 });
4152 _ = editor.update(cx, |editor, window, cx| {
4153 editor.fold_creases(
4154 vec![
4155 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4156 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4158 ],
4159 true,
4160 window,
4161 cx,
4162 );
4163 editor.change_selections(None, window, cx, |s| {
4164 s.select_display_ranges([
4165 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4166 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4167 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4168 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4169 ])
4170 });
4171 assert_eq!(
4172 editor.display_text(cx),
4173 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4174 );
4175
4176 editor.move_line_up(&MoveLineUp, window, cx);
4177 assert_eq!(
4178 editor.display_text(cx),
4179 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4180 );
4181 assert_eq!(
4182 editor.selections.display_ranges(cx),
4183 vec![
4184 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4185 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4186 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4187 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4188 ]
4189 );
4190 });
4191
4192 _ = editor.update(cx, |editor, window, cx| {
4193 editor.move_line_down(&MoveLineDown, window, cx);
4194 assert_eq!(
4195 editor.display_text(cx),
4196 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4197 );
4198 assert_eq!(
4199 editor.selections.display_ranges(cx),
4200 vec![
4201 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4202 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4203 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4204 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4205 ]
4206 );
4207 });
4208
4209 _ = editor.update(cx, |editor, window, cx| {
4210 editor.move_line_down(&MoveLineDown, window, cx);
4211 assert_eq!(
4212 editor.display_text(cx),
4213 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4214 );
4215 assert_eq!(
4216 editor.selections.display_ranges(cx),
4217 vec![
4218 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4219 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4220 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4221 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4222 ]
4223 );
4224 });
4225
4226 _ = editor.update(cx, |editor, window, cx| {
4227 editor.move_line_up(&MoveLineUp, window, cx);
4228 assert_eq!(
4229 editor.display_text(cx),
4230 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4231 );
4232 assert_eq!(
4233 editor.selections.display_ranges(cx),
4234 vec![
4235 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4236 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4237 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4238 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4239 ]
4240 );
4241 });
4242}
4243
4244#[gpui::test]
4245fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4246 init_test(cx, |_| {});
4247
4248 let editor = cx.add_window(|window, cx| {
4249 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4250 build_editor(buffer, window, cx)
4251 });
4252 _ = editor.update(cx, |editor, window, cx| {
4253 let snapshot = editor.buffer.read(cx).snapshot(cx);
4254 editor.insert_blocks(
4255 [BlockProperties {
4256 style: BlockStyle::Fixed,
4257 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4258 height: 1,
4259 render: Arc::new(|_| div().into_any()),
4260 priority: 0,
4261 }],
4262 Some(Autoscroll::fit()),
4263 cx,
4264 );
4265 editor.change_selections(None, window, cx, |s| {
4266 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4267 });
4268 editor.move_line_down(&MoveLineDown, window, cx);
4269 });
4270}
4271
4272#[gpui::test]
4273async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4274 init_test(cx, |_| {});
4275
4276 let mut cx = EditorTestContext::new(cx).await;
4277 cx.set_state(
4278 &"
4279 ˇzero
4280 one
4281 two
4282 three
4283 four
4284 five
4285 "
4286 .unindent(),
4287 );
4288
4289 // Create a four-line block that replaces three lines of text.
4290 cx.update_editor(|editor, window, cx| {
4291 let snapshot = editor.snapshot(window, cx);
4292 let snapshot = &snapshot.buffer_snapshot;
4293 let placement = BlockPlacement::Replace(
4294 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4295 );
4296 editor.insert_blocks(
4297 [BlockProperties {
4298 placement,
4299 height: 4,
4300 style: BlockStyle::Sticky,
4301 render: Arc::new(|_| gpui::div().into_any_element()),
4302 priority: 0,
4303 }],
4304 None,
4305 cx,
4306 );
4307 });
4308
4309 // Move down so that the cursor touches the block.
4310 cx.update_editor(|editor, window, cx| {
4311 editor.move_down(&Default::default(), window, cx);
4312 });
4313 cx.assert_editor_state(
4314 &"
4315 zero
4316 «one
4317 two
4318 threeˇ»
4319 four
4320 five
4321 "
4322 .unindent(),
4323 );
4324
4325 // Move down past the block.
4326 cx.update_editor(|editor, window, cx| {
4327 editor.move_down(&Default::default(), window, cx);
4328 });
4329 cx.assert_editor_state(
4330 &"
4331 zero
4332 one
4333 two
4334 three
4335 ˇfour
4336 five
4337 "
4338 .unindent(),
4339 );
4340}
4341
4342#[gpui::test]
4343fn test_transpose(cx: &mut TestAppContext) {
4344 init_test(cx, |_| {});
4345
4346 _ = cx.add_window(|window, cx| {
4347 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4348 editor.set_style(EditorStyle::default(), window, cx);
4349 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4350 editor.transpose(&Default::default(), window, cx);
4351 assert_eq!(editor.text(cx), "bac");
4352 assert_eq!(editor.selections.ranges(cx), [2..2]);
4353
4354 editor.transpose(&Default::default(), window, cx);
4355 assert_eq!(editor.text(cx), "bca");
4356 assert_eq!(editor.selections.ranges(cx), [3..3]);
4357
4358 editor.transpose(&Default::default(), window, cx);
4359 assert_eq!(editor.text(cx), "bac");
4360 assert_eq!(editor.selections.ranges(cx), [3..3]);
4361
4362 editor
4363 });
4364
4365 _ = cx.add_window(|window, cx| {
4366 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4367 editor.set_style(EditorStyle::default(), window, cx);
4368 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4369 editor.transpose(&Default::default(), window, cx);
4370 assert_eq!(editor.text(cx), "acb\nde");
4371 assert_eq!(editor.selections.ranges(cx), [3..3]);
4372
4373 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4374 editor.transpose(&Default::default(), window, cx);
4375 assert_eq!(editor.text(cx), "acbd\ne");
4376 assert_eq!(editor.selections.ranges(cx), [5..5]);
4377
4378 editor.transpose(&Default::default(), window, cx);
4379 assert_eq!(editor.text(cx), "acbde\n");
4380 assert_eq!(editor.selections.ranges(cx), [6..6]);
4381
4382 editor.transpose(&Default::default(), window, cx);
4383 assert_eq!(editor.text(cx), "acbd\ne");
4384 assert_eq!(editor.selections.ranges(cx), [6..6]);
4385
4386 editor
4387 });
4388
4389 _ = cx.add_window(|window, cx| {
4390 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4391 editor.set_style(EditorStyle::default(), window, cx);
4392 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4393 editor.transpose(&Default::default(), window, cx);
4394 assert_eq!(editor.text(cx), "bacd\ne");
4395 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4396
4397 editor.transpose(&Default::default(), window, cx);
4398 assert_eq!(editor.text(cx), "bcade\n");
4399 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4400
4401 editor.transpose(&Default::default(), window, cx);
4402 assert_eq!(editor.text(cx), "bcda\ne");
4403 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4404
4405 editor.transpose(&Default::default(), window, cx);
4406 assert_eq!(editor.text(cx), "bcade\n");
4407 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4408
4409 editor.transpose(&Default::default(), window, cx);
4410 assert_eq!(editor.text(cx), "bcaed\n");
4411 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4412
4413 editor
4414 });
4415
4416 _ = cx.add_window(|window, cx| {
4417 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4418 editor.set_style(EditorStyle::default(), window, cx);
4419 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4420 editor.transpose(&Default::default(), window, cx);
4421 assert_eq!(editor.text(cx), "🏀🍐✋");
4422 assert_eq!(editor.selections.ranges(cx), [8..8]);
4423
4424 editor.transpose(&Default::default(), window, cx);
4425 assert_eq!(editor.text(cx), "🏀✋🍐");
4426 assert_eq!(editor.selections.ranges(cx), [11..11]);
4427
4428 editor.transpose(&Default::default(), window, cx);
4429 assert_eq!(editor.text(cx), "🏀🍐✋");
4430 assert_eq!(editor.selections.ranges(cx), [11..11]);
4431
4432 editor
4433 });
4434}
4435
4436#[gpui::test]
4437async fn test_rewrap(cx: &mut TestAppContext) {
4438 init_test(cx, |settings| {
4439 settings.languages.extend([
4440 (
4441 "Markdown".into(),
4442 LanguageSettingsContent {
4443 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4444 ..Default::default()
4445 },
4446 ),
4447 (
4448 "Plain Text".into(),
4449 LanguageSettingsContent {
4450 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4451 ..Default::default()
4452 },
4453 ),
4454 ])
4455 });
4456
4457 let mut cx = EditorTestContext::new(cx).await;
4458
4459 let language_with_c_comments = Arc::new(Language::new(
4460 LanguageConfig {
4461 line_comments: vec!["// ".into()],
4462 ..LanguageConfig::default()
4463 },
4464 None,
4465 ));
4466 let language_with_pound_comments = Arc::new(Language::new(
4467 LanguageConfig {
4468 line_comments: vec!["# ".into()],
4469 ..LanguageConfig::default()
4470 },
4471 None,
4472 ));
4473 let markdown_language = Arc::new(Language::new(
4474 LanguageConfig {
4475 name: "Markdown".into(),
4476 ..LanguageConfig::default()
4477 },
4478 None,
4479 ));
4480 let language_with_doc_comments = Arc::new(Language::new(
4481 LanguageConfig {
4482 line_comments: vec!["// ".into(), "/// ".into()],
4483 ..LanguageConfig::default()
4484 },
4485 Some(tree_sitter_rust::LANGUAGE.into()),
4486 ));
4487
4488 let plaintext_language = Arc::new(Language::new(
4489 LanguageConfig {
4490 name: "Plain Text".into(),
4491 ..LanguageConfig::default()
4492 },
4493 None,
4494 ));
4495
4496 assert_rewrap(
4497 indoc! {"
4498 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4499 "},
4500 indoc! {"
4501 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4502 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4503 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4504 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4505 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4506 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4507 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4508 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4509 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4510 // porttitor id. Aliquam id accumsan eros.
4511 "},
4512 language_with_c_comments.clone(),
4513 &mut cx,
4514 );
4515
4516 // Test that rewrapping works inside of a selection
4517 assert_rewrap(
4518 indoc! {"
4519 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4520 "},
4521 indoc! {"
4522 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4523 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4524 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4525 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4526 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4527 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4528 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4529 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4530 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4531 // porttitor id. Aliquam id accumsan eros.ˇ»
4532 "},
4533 language_with_c_comments.clone(),
4534 &mut cx,
4535 );
4536
4537 // Test that cursors that expand to the same region are collapsed.
4538 assert_rewrap(
4539 indoc! {"
4540 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4541 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4542 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4543 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4544 "},
4545 indoc! {"
4546 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4547 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4548 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4549 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4550 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4551 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4552 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4553 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4554 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4555 // porttitor id. Aliquam id accumsan eros.
4556 "},
4557 language_with_c_comments.clone(),
4558 &mut cx,
4559 );
4560
4561 // Test that non-contiguous selections are treated separately.
4562 assert_rewrap(
4563 indoc! {"
4564 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4565 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4566 //
4567 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4568 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4569 "},
4570 indoc! {"
4571 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4572 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4573 // auctor, eu lacinia sapien scelerisque.
4574 //
4575 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4576 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4577 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4578 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4579 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4580 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4581 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4582 "},
4583 language_with_c_comments.clone(),
4584 &mut cx,
4585 );
4586
4587 // Test that different comment prefixes are supported.
4588 assert_rewrap(
4589 indoc! {"
4590 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4591 "},
4592 indoc! {"
4593 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4594 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4595 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4596 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4597 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4598 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4599 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4600 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4601 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4602 # accumsan eros.
4603 "},
4604 language_with_pound_comments.clone(),
4605 &mut cx,
4606 );
4607
4608 // Test that rewrapping is ignored outside of comments in most languages.
4609 assert_rewrap(
4610 indoc! {"
4611 /// Adds two numbers.
4612 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4613 fn add(a: u32, b: u32) -> u32 {
4614 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4615 }
4616 "},
4617 indoc! {"
4618 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4619 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4620 fn add(a: u32, b: u32) -> u32 {
4621 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4622 }
4623 "},
4624 language_with_doc_comments.clone(),
4625 &mut cx,
4626 );
4627
4628 // Test that rewrapping works in Markdown and Plain Text languages.
4629 assert_rewrap(
4630 indoc! {"
4631 # Hello
4632
4633 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4634 "},
4635 indoc! {"
4636 # Hello
4637
4638 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4639 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4640 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4641 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4642 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4643 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4644 Integer sit amet scelerisque nisi.
4645 "},
4646 markdown_language,
4647 &mut cx,
4648 );
4649
4650 assert_rewrap(
4651 indoc! {"
4652 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4653 "},
4654 indoc! {"
4655 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4656 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4657 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4658 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4659 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4660 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4661 Integer sit amet scelerisque nisi.
4662 "},
4663 plaintext_language,
4664 &mut cx,
4665 );
4666
4667 // Test rewrapping unaligned comments in a selection.
4668 assert_rewrap(
4669 indoc! {"
4670 fn foo() {
4671 if true {
4672 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4673 // Praesent semper egestas tellus id dignissim.ˇ»
4674 do_something();
4675 } else {
4676 //
4677 }
4678 }
4679 "},
4680 indoc! {"
4681 fn foo() {
4682 if true {
4683 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4684 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4685 // egestas tellus id dignissim.ˇ»
4686 do_something();
4687 } else {
4688 //
4689 }
4690 }
4691 "},
4692 language_with_doc_comments.clone(),
4693 &mut cx,
4694 );
4695
4696 assert_rewrap(
4697 indoc! {"
4698 fn foo() {
4699 if true {
4700 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4701 // Praesent semper egestas tellus id dignissim.»
4702 do_something();
4703 } else {
4704 //
4705 }
4706
4707 }
4708 "},
4709 indoc! {"
4710 fn foo() {
4711 if true {
4712 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4713 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4714 // egestas tellus id dignissim.»
4715 do_something();
4716 } else {
4717 //
4718 }
4719
4720 }
4721 "},
4722 language_with_doc_comments.clone(),
4723 &mut cx,
4724 );
4725
4726 #[track_caller]
4727 fn assert_rewrap(
4728 unwrapped_text: &str,
4729 wrapped_text: &str,
4730 language: Arc<Language>,
4731 cx: &mut EditorTestContext,
4732 ) {
4733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4734 cx.set_state(unwrapped_text);
4735 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4736 cx.assert_editor_state(wrapped_text);
4737 }
4738}
4739
4740#[gpui::test]
4741async fn test_hard_wrap(cx: &mut TestAppContext) {
4742 init_test(cx, |_| {});
4743 let mut cx = EditorTestContext::new(cx).await;
4744
4745 cx.update_editor(|editor, _, cx| {
4746 editor.set_hard_wrap(Some(14), cx);
4747 });
4748
4749 cx.set_state(indoc!(
4750 "
4751 one two three ˇ
4752 "
4753 ));
4754 cx.simulate_input("four");
4755 cx.run_until_parked();
4756
4757 cx.assert_editor_state(indoc!(
4758 "
4759 one two three
4760 fourˇ
4761 "
4762 ));
4763}
4764
4765#[gpui::test]
4766async fn test_clipboard(cx: &mut TestAppContext) {
4767 init_test(cx, |_| {});
4768
4769 let mut cx = EditorTestContext::new(cx).await;
4770
4771 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4772 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4773 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4774
4775 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4776 cx.set_state("two ˇfour ˇsix ˇ");
4777 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4778 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4779
4780 // Paste again but with only two cursors. Since the number of cursors doesn't
4781 // match the number of slices in the clipboard, the entire clipboard text
4782 // is pasted at each cursor.
4783 cx.set_state("ˇtwo one✅ four three six five ˇ");
4784 cx.update_editor(|e, window, cx| {
4785 e.handle_input("( ", window, cx);
4786 e.paste(&Paste, window, cx);
4787 e.handle_input(") ", window, cx);
4788 });
4789 cx.assert_editor_state(
4790 &([
4791 "( one✅ ",
4792 "three ",
4793 "five ) ˇtwo one✅ four three six five ( one✅ ",
4794 "three ",
4795 "five ) ˇ",
4796 ]
4797 .join("\n")),
4798 );
4799
4800 // Cut with three selections, one of which is full-line.
4801 cx.set_state(indoc! {"
4802 1«2ˇ»3
4803 4ˇ567
4804 «8ˇ»9"});
4805 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4806 cx.assert_editor_state(indoc! {"
4807 1ˇ3
4808 ˇ9"});
4809
4810 // Paste with three selections, noticing how the copied selection that was full-line
4811 // gets inserted before the second cursor.
4812 cx.set_state(indoc! {"
4813 1ˇ3
4814 9ˇ
4815 «oˇ»ne"});
4816 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4817 cx.assert_editor_state(indoc! {"
4818 12ˇ3
4819 4567
4820 9ˇ
4821 8ˇne"});
4822
4823 // Copy with a single cursor only, which writes the whole line into the clipboard.
4824 cx.set_state(indoc! {"
4825 The quick brown
4826 fox juˇmps over
4827 the lazy dog"});
4828 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4829 assert_eq!(
4830 cx.read_from_clipboard()
4831 .and_then(|item| item.text().as_deref().map(str::to_string)),
4832 Some("fox jumps over\n".to_string())
4833 );
4834
4835 // Paste with three selections, noticing how the copied full-line selection is inserted
4836 // before the empty selections but replaces the selection that is non-empty.
4837 cx.set_state(indoc! {"
4838 Tˇhe quick brown
4839 «foˇ»x jumps over
4840 tˇhe lazy dog"});
4841 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4842 cx.assert_editor_state(indoc! {"
4843 fox jumps over
4844 Tˇhe quick brown
4845 fox jumps over
4846 ˇx jumps over
4847 fox jumps over
4848 tˇhe lazy dog"});
4849}
4850
4851#[gpui::test]
4852async fn test_paste_multiline(cx: &mut TestAppContext) {
4853 init_test(cx, |_| {});
4854
4855 let mut cx = EditorTestContext::new(cx).await;
4856 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4857
4858 // Cut an indented block, without the leading whitespace.
4859 cx.set_state(indoc! {"
4860 const a: B = (
4861 c(),
4862 «d(
4863 e,
4864 f
4865 )ˇ»
4866 );
4867 "});
4868 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4869 cx.assert_editor_state(indoc! {"
4870 const a: B = (
4871 c(),
4872 ˇ
4873 );
4874 "});
4875
4876 // Paste it at the same position.
4877 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4878 cx.assert_editor_state(indoc! {"
4879 const a: B = (
4880 c(),
4881 d(
4882 e,
4883 f
4884 )ˇ
4885 );
4886 "});
4887
4888 // Paste it at a line with a lower indent level.
4889 cx.set_state(indoc! {"
4890 ˇ
4891 const a: B = (
4892 c(),
4893 );
4894 "});
4895 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4896 cx.assert_editor_state(indoc! {"
4897 d(
4898 e,
4899 f
4900 )ˇ
4901 const a: B = (
4902 c(),
4903 );
4904 "});
4905
4906 // Cut an indented block, with the leading whitespace.
4907 cx.set_state(indoc! {"
4908 const a: B = (
4909 c(),
4910 « d(
4911 e,
4912 f
4913 )
4914 ˇ»);
4915 "});
4916 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4917 cx.assert_editor_state(indoc! {"
4918 const a: B = (
4919 c(),
4920 ˇ);
4921 "});
4922
4923 // Paste it at the same position.
4924 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4925 cx.assert_editor_state(indoc! {"
4926 const a: B = (
4927 c(),
4928 d(
4929 e,
4930 f
4931 )
4932 ˇ);
4933 "});
4934
4935 // Paste it at a line with a higher indent level.
4936 cx.set_state(indoc! {"
4937 const a: B = (
4938 c(),
4939 d(
4940 e,
4941 fˇ
4942 )
4943 );
4944 "});
4945 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4946 cx.assert_editor_state(indoc! {"
4947 const a: B = (
4948 c(),
4949 d(
4950 e,
4951 f d(
4952 e,
4953 f
4954 )
4955 ˇ
4956 )
4957 );
4958 "});
4959
4960 // Copy an indented block, starting mid-line
4961 cx.set_state(indoc! {"
4962 const a: B = (
4963 c(),
4964 somethin«g(
4965 e,
4966 f
4967 )ˇ»
4968 );
4969 "});
4970 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4971
4972 // Paste it on a line with a lower indent level
4973 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4974 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4975 cx.assert_editor_state(indoc! {"
4976 const a: B = (
4977 c(),
4978 something(
4979 e,
4980 f
4981 )
4982 );
4983 g(
4984 e,
4985 f
4986 )ˇ"});
4987}
4988
4989#[gpui::test]
4990async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4991 init_test(cx, |_| {});
4992
4993 cx.write_to_clipboard(ClipboardItem::new_string(
4994 " d(\n e\n );\n".into(),
4995 ));
4996
4997 let mut cx = EditorTestContext::new(cx).await;
4998 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4999
5000 cx.set_state(indoc! {"
5001 fn a() {
5002 b();
5003 if c() {
5004 ˇ
5005 }
5006 }
5007 "});
5008
5009 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5010 cx.assert_editor_state(indoc! {"
5011 fn a() {
5012 b();
5013 if c() {
5014 d(
5015 e
5016 );
5017 ˇ
5018 }
5019 }
5020 "});
5021
5022 cx.set_state(indoc! {"
5023 fn a() {
5024 b();
5025 ˇ
5026 }
5027 "});
5028
5029 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5030 cx.assert_editor_state(indoc! {"
5031 fn a() {
5032 b();
5033 d(
5034 e
5035 );
5036 ˇ
5037 }
5038 "});
5039}
5040
5041#[gpui::test]
5042fn test_select_all(cx: &mut TestAppContext) {
5043 init_test(cx, |_| {});
5044
5045 let editor = cx.add_window(|window, cx| {
5046 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5047 build_editor(buffer, window, cx)
5048 });
5049 _ = editor.update(cx, |editor, window, cx| {
5050 editor.select_all(&SelectAll, window, cx);
5051 assert_eq!(
5052 editor.selections.display_ranges(cx),
5053 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5054 );
5055 });
5056}
5057
5058#[gpui::test]
5059fn test_select_line(cx: &mut TestAppContext) {
5060 init_test(cx, |_| {});
5061
5062 let editor = cx.add_window(|window, cx| {
5063 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5064 build_editor(buffer, window, cx)
5065 });
5066 _ = editor.update(cx, |editor, window, cx| {
5067 editor.change_selections(None, window, cx, |s| {
5068 s.select_display_ranges([
5069 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5070 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5071 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5072 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5073 ])
5074 });
5075 editor.select_line(&SelectLine, window, cx);
5076 assert_eq!(
5077 editor.selections.display_ranges(cx),
5078 vec![
5079 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5080 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5081 ]
5082 );
5083 });
5084
5085 _ = editor.update(cx, |editor, window, cx| {
5086 editor.select_line(&SelectLine, window, cx);
5087 assert_eq!(
5088 editor.selections.display_ranges(cx),
5089 vec![
5090 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5091 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5092 ]
5093 );
5094 });
5095
5096 _ = editor.update(cx, |editor, window, cx| {
5097 editor.select_line(&SelectLine, window, cx);
5098 assert_eq!(
5099 editor.selections.display_ranges(cx),
5100 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5101 );
5102 });
5103}
5104
5105#[gpui::test]
5106async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5107 init_test(cx, |_| {});
5108 let mut cx = EditorTestContext::new(cx).await;
5109
5110 #[track_caller]
5111 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5112 cx.set_state(initial_state);
5113 cx.update_editor(|e, window, cx| {
5114 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5115 });
5116 cx.assert_editor_state(expected_state);
5117 }
5118
5119 // Selection starts and ends at the middle of lines, left-to-right
5120 test(
5121 &mut cx,
5122 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5123 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5124 );
5125 // Same thing, right-to-left
5126 test(
5127 &mut cx,
5128 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5129 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5130 );
5131
5132 // Whole buffer, left-to-right, last line *doesn't* end with newline
5133 test(
5134 &mut cx,
5135 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5136 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5137 );
5138 // Same thing, right-to-left
5139 test(
5140 &mut cx,
5141 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5142 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5143 );
5144
5145 // Whole buffer, left-to-right, last line ends with newline
5146 test(
5147 &mut cx,
5148 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5149 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5150 );
5151 // Same thing, right-to-left
5152 test(
5153 &mut cx,
5154 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5155 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5156 );
5157
5158 // Starts at the end of a line, ends at the start of another
5159 test(
5160 &mut cx,
5161 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5162 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5163 );
5164}
5165
5166#[gpui::test]
5167async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5168 init_test(cx, |_| {});
5169
5170 let editor = cx.add_window(|window, cx| {
5171 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5172 build_editor(buffer, window, cx)
5173 });
5174
5175 // setup
5176 _ = editor.update(cx, |editor, window, cx| {
5177 editor.fold_creases(
5178 vec![
5179 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5180 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5181 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5182 ],
5183 true,
5184 window,
5185 cx,
5186 );
5187 assert_eq!(
5188 editor.display_text(cx),
5189 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5190 );
5191 });
5192
5193 _ = editor.update(cx, |editor, window, cx| {
5194 editor.change_selections(None, window, cx, |s| {
5195 s.select_display_ranges([
5196 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5197 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5198 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5199 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5200 ])
5201 });
5202 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5203 assert_eq!(
5204 editor.display_text(cx),
5205 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5206 );
5207 });
5208 EditorTestContext::for_editor(editor, cx)
5209 .await
5210 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5211
5212 _ = editor.update(cx, |editor, window, cx| {
5213 editor.change_selections(None, window, cx, |s| {
5214 s.select_display_ranges([
5215 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5216 ])
5217 });
5218 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5219 assert_eq!(
5220 editor.display_text(cx),
5221 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5222 );
5223 assert_eq!(
5224 editor.selections.display_ranges(cx),
5225 [
5226 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5227 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5228 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5229 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5230 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5231 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5232 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5233 ]
5234 );
5235 });
5236 EditorTestContext::for_editor(editor, cx)
5237 .await
5238 .assert_editor_state(
5239 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248
5249 cx.set_state(indoc!(
5250 r#"abc
5251 defˇghi
5252
5253 jk
5254 nlmo
5255 "#
5256 ));
5257
5258 cx.update_editor(|editor, window, cx| {
5259 editor.add_selection_above(&Default::default(), window, cx);
5260 });
5261
5262 cx.assert_editor_state(indoc!(
5263 r#"abcˇ
5264 defˇghi
5265
5266 jk
5267 nlmo
5268 "#
5269 ));
5270
5271 cx.update_editor(|editor, window, cx| {
5272 editor.add_selection_above(&Default::default(), window, cx);
5273 });
5274
5275 cx.assert_editor_state(indoc!(
5276 r#"abcˇ
5277 defˇghi
5278
5279 jk
5280 nlmo
5281 "#
5282 ));
5283
5284 cx.update_editor(|editor, window, cx| {
5285 editor.add_selection_below(&Default::default(), window, cx);
5286 });
5287
5288 cx.assert_editor_state(indoc!(
5289 r#"abc
5290 defˇghi
5291
5292 jk
5293 nlmo
5294 "#
5295 ));
5296
5297 cx.update_editor(|editor, window, cx| {
5298 editor.undo_selection(&Default::default(), window, cx);
5299 });
5300
5301 cx.assert_editor_state(indoc!(
5302 r#"abcˇ
5303 defˇghi
5304
5305 jk
5306 nlmo
5307 "#
5308 ));
5309
5310 cx.update_editor(|editor, window, cx| {
5311 editor.redo_selection(&Default::default(), window, cx);
5312 });
5313
5314 cx.assert_editor_state(indoc!(
5315 r#"abc
5316 defˇghi
5317
5318 jk
5319 nlmo
5320 "#
5321 ));
5322
5323 cx.update_editor(|editor, window, cx| {
5324 editor.add_selection_below(&Default::default(), window, cx);
5325 });
5326
5327 cx.assert_editor_state(indoc!(
5328 r#"abc
5329 defˇghi
5330
5331 jk
5332 nlmˇo
5333 "#
5334 ));
5335
5336 cx.update_editor(|editor, window, cx| {
5337 editor.add_selection_below(&Default::default(), window, cx);
5338 });
5339
5340 cx.assert_editor_state(indoc!(
5341 r#"abc
5342 defˇghi
5343
5344 jk
5345 nlmˇo
5346 "#
5347 ));
5348
5349 // change selections
5350 cx.set_state(indoc!(
5351 r#"abc
5352 def«ˇg»hi
5353
5354 jk
5355 nlmo
5356 "#
5357 ));
5358
5359 cx.update_editor(|editor, window, cx| {
5360 editor.add_selection_below(&Default::default(), window, cx);
5361 });
5362
5363 cx.assert_editor_state(indoc!(
5364 r#"abc
5365 def«ˇg»hi
5366
5367 jk
5368 nlm«ˇo»
5369 "#
5370 ));
5371
5372 cx.update_editor(|editor, window, cx| {
5373 editor.add_selection_below(&Default::default(), window, cx);
5374 });
5375
5376 cx.assert_editor_state(indoc!(
5377 r#"abc
5378 def«ˇg»hi
5379
5380 jk
5381 nlm«ˇo»
5382 "#
5383 ));
5384
5385 cx.update_editor(|editor, window, cx| {
5386 editor.add_selection_above(&Default::default(), window, cx);
5387 });
5388
5389 cx.assert_editor_state(indoc!(
5390 r#"abc
5391 def«ˇg»hi
5392
5393 jk
5394 nlmo
5395 "#
5396 ));
5397
5398 cx.update_editor(|editor, window, cx| {
5399 editor.add_selection_above(&Default::default(), window, cx);
5400 });
5401
5402 cx.assert_editor_state(indoc!(
5403 r#"abc
5404 def«ˇg»hi
5405
5406 jk
5407 nlmo
5408 "#
5409 ));
5410
5411 // Change selections again
5412 cx.set_state(indoc!(
5413 r#"a«bc
5414 defgˇ»hi
5415
5416 jk
5417 nlmo
5418 "#
5419 ));
5420
5421 cx.update_editor(|editor, window, cx| {
5422 editor.add_selection_below(&Default::default(), window, cx);
5423 });
5424
5425 cx.assert_editor_state(indoc!(
5426 r#"a«bcˇ»
5427 d«efgˇ»hi
5428
5429 j«kˇ»
5430 nlmo
5431 "#
5432 ));
5433
5434 cx.update_editor(|editor, window, cx| {
5435 editor.add_selection_below(&Default::default(), window, cx);
5436 });
5437 cx.assert_editor_state(indoc!(
5438 r#"a«bcˇ»
5439 d«efgˇ»hi
5440
5441 j«kˇ»
5442 n«lmoˇ»
5443 "#
5444 ));
5445 cx.update_editor(|editor, window, cx| {
5446 editor.add_selection_above(&Default::default(), window, cx);
5447 });
5448
5449 cx.assert_editor_state(indoc!(
5450 r#"a«bcˇ»
5451 d«efgˇ»hi
5452
5453 j«kˇ»
5454 nlmo
5455 "#
5456 ));
5457
5458 // Change selections again
5459 cx.set_state(indoc!(
5460 r#"abc
5461 d«ˇefghi
5462
5463 jk
5464 nlm»o
5465 "#
5466 ));
5467
5468 cx.update_editor(|editor, window, cx| {
5469 editor.add_selection_above(&Default::default(), window, cx);
5470 });
5471
5472 cx.assert_editor_state(indoc!(
5473 r#"a«ˇbc»
5474 d«ˇef»ghi
5475
5476 j«ˇk»
5477 n«ˇlm»o
5478 "#
5479 ));
5480
5481 cx.update_editor(|editor, window, cx| {
5482 editor.add_selection_below(&Default::default(), window, cx);
5483 });
5484
5485 cx.assert_editor_state(indoc!(
5486 r#"abc
5487 d«ˇef»ghi
5488
5489 j«ˇk»
5490 n«ˇlm»o
5491 "#
5492 ));
5493}
5494
5495#[gpui::test]
5496async fn test_select_next(cx: &mut TestAppContext) {
5497 init_test(cx, |_| {});
5498
5499 let mut cx = EditorTestContext::new(cx).await;
5500 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5501
5502 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5503 .unwrap();
5504 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5505
5506 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5507 .unwrap();
5508 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5509
5510 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5511 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5512
5513 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5514 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5515
5516 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5517 .unwrap();
5518 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5519
5520 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5521 .unwrap();
5522 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5523}
5524
5525#[gpui::test]
5526async fn test_select_all_matches(cx: &mut TestAppContext) {
5527 init_test(cx, |_| {});
5528
5529 let mut cx = EditorTestContext::new(cx).await;
5530
5531 // Test caret-only selections
5532 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5533 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5534 .unwrap();
5535 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5536
5537 // Test left-to-right selections
5538 cx.set_state("abc\n«abcˇ»\nabc");
5539 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5540 .unwrap();
5541 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5542
5543 // Test right-to-left selections
5544 cx.set_state("abc\n«ˇabc»\nabc");
5545 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5546 .unwrap();
5547 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5548
5549 // Test selecting whitespace with caret selection
5550 cx.set_state("abc\nˇ abc\nabc");
5551 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5552 .unwrap();
5553 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5554
5555 // Test selecting whitespace with left-to-right selection
5556 cx.set_state("abc\n«ˇ »abc\nabc");
5557 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5558 .unwrap();
5559 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5560
5561 // Test no matches with right-to-left selection
5562 cx.set_state("abc\n« ˇ»abc\nabc");
5563 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5564 .unwrap();
5565 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5566}
5567
5568#[gpui::test]
5569async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5570 init_test(cx, |_| {});
5571
5572 let mut cx = EditorTestContext::new(cx).await;
5573 cx.set_state(
5574 r#"let foo = 2;
5575lˇet foo = 2;
5576let fooˇ = 2;
5577let foo = 2;
5578let foo = ˇ2;"#,
5579 );
5580
5581 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5582 .unwrap();
5583 cx.assert_editor_state(
5584 r#"let foo = 2;
5585«letˇ» foo = 2;
5586let «fooˇ» = 2;
5587let foo = 2;
5588let foo = «2ˇ»;"#,
5589 );
5590
5591 // noop for multiple selections with different contents
5592 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5593 .unwrap();
5594 cx.assert_editor_state(
5595 r#"let foo = 2;
5596«letˇ» foo = 2;
5597let «fooˇ» = 2;
5598let foo = 2;
5599let foo = «2ˇ»;"#,
5600 );
5601}
5602
5603#[gpui::test]
5604async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5605 init_test(cx, |_| {});
5606
5607 let mut cx =
5608 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5609
5610 cx.assert_editor_state(indoc! {"
5611 ˇbbb
5612 ccc
5613
5614 bbb
5615 ccc
5616 "});
5617 cx.dispatch_action(SelectPrevious::default());
5618 cx.assert_editor_state(indoc! {"
5619 «bbbˇ»
5620 ccc
5621
5622 bbb
5623 ccc
5624 "});
5625 cx.dispatch_action(SelectPrevious::default());
5626 cx.assert_editor_state(indoc! {"
5627 «bbbˇ»
5628 ccc
5629
5630 «bbbˇ»
5631 ccc
5632 "});
5633}
5634
5635#[gpui::test]
5636async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5637 init_test(cx, |_| {});
5638
5639 let mut cx = EditorTestContext::new(cx).await;
5640 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5641
5642 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5643 .unwrap();
5644 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5645
5646 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5647 .unwrap();
5648 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5649
5650 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5651 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5652
5653 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5654 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5655
5656 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5657 .unwrap();
5658 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5659
5660 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5661 .unwrap();
5662 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5663
5664 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5665 .unwrap();
5666 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5667}
5668
5669#[gpui::test]
5670async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5671 init_test(cx, |_| {});
5672
5673 let mut cx = EditorTestContext::new(cx).await;
5674 cx.set_state("aˇ");
5675
5676 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5677 .unwrap();
5678 cx.assert_editor_state("«aˇ»");
5679 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5680 .unwrap();
5681 cx.assert_editor_state("«aˇ»");
5682}
5683
5684#[gpui::test]
5685async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5686 init_test(cx, |_| {});
5687
5688 let mut cx = EditorTestContext::new(cx).await;
5689 cx.set_state(
5690 r#"let foo = 2;
5691lˇet foo = 2;
5692let fooˇ = 2;
5693let foo = 2;
5694let foo = ˇ2;"#,
5695 );
5696
5697 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5698 .unwrap();
5699 cx.assert_editor_state(
5700 r#"let foo = 2;
5701«letˇ» foo = 2;
5702let «fooˇ» = 2;
5703let foo = 2;
5704let foo = «2ˇ»;"#,
5705 );
5706
5707 // noop for multiple selections with different contents
5708 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5709 .unwrap();
5710 cx.assert_editor_state(
5711 r#"let foo = 2;
5712«letˇ» foo = 2;
5713let «fooˇ» = 2;
5714let foo = 2;
5715let foo = «2ˇ»;"#,
5716 );
5717}
5718
5719#[gpui::test]
5720async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5721 init_test(cx, |_| {});
5722
5723 let mut cx = EditorTestContext::new(cx).await;
5724 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5725
5726 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5727 .unwrap();
5728 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5729
5730 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5731 .unwrap();
5732 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5733
5734 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5735 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5736
5737 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5738 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5739
5740 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5741 .unwrap();
5742 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5743
5744 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5745 .unwrap();
5746 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5747}
5748
5749#[gpui::test]
5750async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5751 init_test(cx, |_| {});
5752
5753 let language = Arc::new(Language::new(
5754 LanguageConfig::default(),
5755 Some(tree_sitter_rust::LANGUAGE.into()),
5756 ));
5757
5758 let text = r#"
5759 use mod1::mod2::{mod3, mod4};
5760
5761 fn fn_1(param1: bool, param2: &str) {
5762 let var1 = "text";
5763 }
5764 "#
5765 .unindent();
5766
5767 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5768 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5769 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5770
5771 editor
5772 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5773 .await;
5774
5775 editor.update_in(cx, |editor, window, cx| {
5776 editor.change_selections(None, window, cx, |s| {
5777 s.select_display_ranges([
5778 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5779 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5780 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5781 ]);
5782 });
5783 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5784 });
5785 editor.update(cx, |editor, cx| {
5786 assert_text_with_selections(
5787 editor,
5788 indoc! {r#"
5789 use mod1::mod2::{mod3, «mod4ˇ»};
5790
5791 fn fn_1«ˇ(param1: bool, param2: &str)» {
5792 let var1 = "«textˇ»";
5793 }
5794 "#},
5795 cx,
5796 );
5797 });
5798
5799 editor.update_in(cx, |editor, window, cx| {
5800 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5801 });
5802 editor.update(cx, |editor, cx| {
5803 assert_text_with_selections(
5804 editor,
5805 indoc! {r#"
5806 use mod1::mod2::«{mod3, mod4}ˇ»;
5807
5808 «ˇfn fn_1(param1: bool, param2: &str) {
5809 let var1 = "text";
5810 }»
5811 "#},
5812 cx,
5813 );
5814 });
5815
5816 editor.update_in(cx, |editor, window, cx| {
5817 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5818 });
5819 assert_eq!(
5820 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5821 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5822 );
5823
5824 // Trying to expand the selected syntax node one more time has no effect.
5825 editor.update_in(cx, |editor, window, cx| {
5826 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5827 });
5828 assert_eq!(
5829 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5830 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5831 );
5832
5833 editor.update_in(cx, |editor, window, cx| {
5834 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5835 });
5836 editor.update(cx, |editor, cx| {
5837 assert_text_with_selections(
5838 editor,
5839 indoc! {r#"
5840 use mod1::mod2::«{mod3, mod4}ˇ»;
5841
5842 «ˇfn fn_1(param1: bool, param2: &str) {
5843 let var1 = "text";
5844 }»
5845 "#},
5846 cx,
5847 );
5848 });
5849
5850 editor.update_in(cx, |editor, window, cx| {
5851 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5852 });
5853 editor.update(cx, |editor, cx| {
5854 assert_text_with_selections(
5855 editor,
5856 indoc! {r#"
5857 use mod1::mod2::{mod3, «mod4ˇ»};
5858
5859 fn fn_1«ˇ(param1: bool, param2: &str)» {
5860 let var1 = "«textˇ»";
5861 }
5862 "#},
5863 cx,
5864 );
5865 });
5866
5867 editor.update_in(cx, |editor, window, cx| {
5868 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5869 });
5870 editor.update(cx, |editor, cx| {
5871 assert_text_with_selections(
5872 editor,
5873 indoc! {r#"
5874 use mod1::mod2::{mod3, mo«ˇ»d4};
5875
5876 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5877 let var1 = "te«ˇ»xt";
5878 }
5879 "#},
5880 cx,
5881 );
5882 });
5883
5884 // Trying to shrink the selected syntax node one more time has no effect.
5885 editor.update_in(cx, |editor, window, cx| {
5886 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5887 });
5888 editor.update_in(cx, |editor, _, cx| {
5889 assert_text_with_selections(
5890 editor,
5891 indoc! {r#"
5892 use mod1::mod2::{mod3, mo«ˇ»d4};
5893
5894 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5895 let var1 = "te«ˇ»xt";
5896 }
5897 "#},
5898 cx,
5899 );
5900 });
5901
5902 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5903 // a fold.
5904 editor.update_in(cx, |editor, window, cx| {
5905 editor.fold_creases(
5906 vec![
5907 Crease::simple(
5908 Point::new(0, 21)..Point::new(0, 24),
5909 FoldPlaceholder::test(),
5910 ),
5911 Crease::simple(
5912 Point::new(3, 20)..Point::new(3, 22),
5913 FoldPlaceholder::test(),
5914 ),
5915 ],
5916 true,
5917 window,
5918 cx,
5919 );
5920 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5921 });
5922 editor.update(cx, |editor, cx| {
5923 assert_text_with_selections(
5924 editor,
5925 indoc! {r#"
5926 use mod1::mod2::«{mod3, mod4}ˇ»;
5927
5928 fn fn_1«ˇ(param1: bool, param2: &str)» {
5929 «let var1 = "text";ˇ»
5930 }
5931 "#},
5932 cx,
5933 );
5934 });
5935}
5936
5937#[gpui::test]
5938async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5939 init_test(cx, |_| {});
5940
5941 let base_text = r#"
5942 impl A {
5943 // this is an uncommitted comment
5944
5945 fn b() {
5946 c();
5947 }
5948
5949 // this is another uncommitted comment
5950
5951 fn d() {
5952 // e
5953 // f
5954 }
5955 }
5956
5957 fn g() {
5958 // h
5959 }
5960 "#
5961 .unindent();
5962
5963 let text = r#"
5964 ˇimpl A {
5965
5966 fn b() {
5967 c();
5968 }
5969
5970 fn d() {
5971 // e
5972 // f
5973 }
5974 }
5975
5976 fn g() {
5977 // h
5978 }
5979 "#
5980 .unindent();
5981
5982 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5983 cx.set_state(&text);
5984 cx.set_head_text(&base_text);
5985 cx.update_editor(|editor, window, cx| {
5986 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5987 });
5988
5989 cx.assert_state_with_diff(
5990 "
5991 ˇimpl A {
5992 - // this is an uncommitted comment
5993
5994 fn b() {
5995 c();
5996 }
5997
5998 - // this is another uncommitted comment
5999 -
6000 fn d() {
6001 // e
6002 // f
6003 }
6004 }
6005
6006 fn g() {
6007 // h
6008 }
6009 "
6010 .unindent(),
6011 );
6012
6013 let expected_display_text = "
6014 impl A {
6015 // this is an uncommitted comment
6016
6017 fn b() {
6018 ⋯
6019 }
6020
6021 // this is another uncommitted comment
6022
6023 fn d() {
6024 ⋯
6025 }
6026 }
6027
6028 fn g() {
6029 ⋯
6030 }
6031 "
6032 .unindent();
6033
6034 cx.update_editor(|editor, window, cx| {
6035 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6036 assert_eq!(editor.display_text(cx), expected_display_text);
6037 });
6038}
6039
6040#[gpui::test]
6041async fn test_autoindent(cx: &mut TestAppContext) {
6042 init_test(cx, |_| {});
6043
6044 let language = Arc::new(
6045 Language::new(
6046 LanguageConfig {
6047 brackets: BracketPairConfig {
6048 pairs: vec![
6049 BracketPair {
6050 start: "{".to_string(),
6051 end: "}".to_string(),
6052 close: false,
6053 surround: false,
6054 newline: true,
6055 },
6056 BracketPair {
6057 start: "(".to_string(),
6058 end: ")".to_string(),
6059 close: false,
6060 surround: false,
6061 newline: true,
6062 },
6063 ],
6064 ..Default::default()
6065 },
6066 ..Default::default()
6067 },
6068 Some(tree_sitter_rust::LANGUAGE.into()),
6069 )
6070 .with_indents_query(
6071 r#"
6072 (_ "(" ")" @end) @indent
6073 (_ "{" "}" @end) @indent
6074 "#,
6075 )
6076 .unwrap(),
6077 );
6078
6079 let text = "fn a() {}";
6080
6081 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6082 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6083 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6084 editor
6085 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6086 .await;
6087
6088 editor.update_in(cx, |editor, window, cx| {
6089 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6090 editor.newline(&Newline, window, cx);
6091 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6092 assert_eq!(
6093 editor.selections.ranges(cx),
6094 &[
6095 Point::new(1, 4)..Point::new(1, 4),
6096 Point::new(3, 4)..Point::new(3, 4),
6097 Point::new(5, 0)..Point::new(5, 0)
6098 ]
6099 );
6100 });
6101}
6102
6103#[gpui::test]
6104async fn test_autoindent_selections(cx: &mut TestAppContext) {
6105 init_test(cx, |_| {});
6106
6107 {
6108 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6109 cx.set_state(indoc! {"
6110 impl A {
6111
6112 fn b() {}
6113
6114 «fn c() {
6115
6116 }ˇ»
6117 }
6118 "});
6119
6120 cx.update_editor(|editor, window, cx| {
6121 editor.autoindent(&Default::default(), window, cx);
6122 });
6123
6124 cx.assert_editor_state(indoc! {"
6125 impl A {
6126
6127 fn b() {}
6128
6129 «fn c() {
6130
6131 }ˇ»
6132 }
6133 "});
6134 }
6135
6136 {
6137 let mut cx = EditorTestContext::new_multibuffer(
6138 cx,
6139 [indoc! { "
6140 impl A {
6141 «
6142 // a
6143 fn b(){}
6144 »
6145 «
6146 }
6147 fn c(){}
6148 »
6149 "}],
6150 );
6151
6152 let buffer = cx.update_editor(|editor, _, cx| {
6153 let buffer = editor.buffer().update(cx, |buffer, _| {
6154 buffer.all_buffers().iter().next().unwrap().clone()
6155 });
6156 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6157 buffer
6158 });
6159
6160 cx.run_until_parked();
6161 cx.update_editor(|editor, window, cx| {
6162 editor.select_all(&Default::default(), window, cx);
6163 editor.autoindent(&Default::default(), window, cx)
6164 });
6165 cx.run_until_parked();
6166
6167 cx.update(|_, cx| {
6168 pretty_assertions::assert_eq!(
6169 buffer.read(cx).text(),
6170 indoc! { "
6171 impl A {
6172
6173 // a
6174 fn b(){}
6175
6176
6177 }
6178 fn c(){}
6179
6180 " }
6181 )
6182 });
6183 }
6184}
6185
6186#[gpui::test]
6187async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6188 init_test(cx, |_| {});
6189
6190 let mut cx = EditorTestContext::new(cx).await;
6191
6192 let language = Arc::new(Language::new(
6193 LanguageConfig {
6194 brackets: BracketPairConfig {
6195 pairs: vec![
6196 BracketPair {
6197 start: "{".to_string(),
6198 end: "}".to_string(),
6199 close: true,
6200 surround: true,
6201 newline: true,
6202 },
6203 BracketPair {
6204 start: "(".to_string(),
6205 end: ")".to_string(),
6206 close: true,
6207 surround: true,
6208 newline: true,
6209 },
6210 BracketPair {
6211 start: "/*".to_string(),
6212 end: " */".to_string(),
6213 close: true,
6214 surround: true,
6215 newline: true,
6216 },
6217 BracketPair {
6218 start: "[".to_string(),
6219 end: "]".to_string(),
6220 close: false,
6221 surround: false,
6222 newline: true,
6223 },
6224 BracketPair {
6225 start: "\"".to_string(),
6226 end: "\"".to_string(),
6227 close: true,
6228 surround: true,
6229 newline: false,
6230 },
6231 BracketPair {
6232 start: "<".to_string(),
6233 end: ">".to_string(),
6234 close: false,
6235 surround: true,
6236 newline: true,
6237 },
6238 ],
6239 ..Default::default()
6240 },
6241 autoclose_before: "})]".to_string(),
6242 ..Default::default()
6243 },
6244 Some(tree_sitter_rust::LANGUAGE.into()),
6245 ));
6246
6247 cx.language_registry().add(language.clone());
6248 cx.update_buffer(|buffer, cx| {
6249 buffer.set_language(Some(language), cx);
6250 });
6251
6252 cx.set_state(
6253 &r#"
6254 🏀ˇ
6255 εˇ
6256 ❤️ˇ
6257 "#
6258 .unindent(),
6259 );
6260
6261 // autoclose multiple nested brackets at multiple cursors
6262 cx.update_editor(|editor, window, cx| {
6263 editor.handle_input("{", window, cx);
6264 editor.handle_input("{", window, cx);
6265 editor.handle_input("{", window, cx);
6266 });
6267 cx.assert_editor_state(
6268 &"
6269 🏀{{{ˇ}}}
6270 ε{{{ˇ}}}
6271 ❤️{{{ˇ}}}
6272 "
6273 .unindent(),
6274 );
6275
6276 // insert a different closing bracket
6277 cx.update_editor(|editor, window, cx| {
6278 editor.handle_input(")", window, cx);
6279 });
6280 cx.assert_editor_state(
6281 &"
6282 🏀{{{)ˇ}}}
6283 ε{{{)ˇ}}}
6284 ❤️{{{)ˇ}}}
6285 "
6286 .unindent(),
6287 );
6288
6289 // skip over the auto-closed brackets when typing a closing bracket
6290 cx.update_editor(|editor, window, cx| {
6291 editor.move_right(&MoveRight, window, cx);
6292 editor.handle_input("}", window, cx);
6293 editor.handle_input("}", window, cx);
6294 editor.handle_input("}", window, cx);
6295 });
6296 cx.assert_editor_state(
6297 &"
6298 🏀{{{)}}}}ˇ
6299 ε{{{)}}}}ˇ
6300 ❤️{{{)}}}}ˇ
6301 "
6302 .unindent(),
6303 );
6304
6305 // autoclose multi-character pairs
6306 cx.set_state(
6307 &"
6308 ˇ
6309 ˇ
6310 "
6311 .unindent(),
6312 );
6313 cx.update_editor(|editor, window, cx| {
6314 editor.handle_input("/", window, cx);
6315 editor.handle_input("*", window, cx);
6316 });
6317 cx.assert_editor_state(
6318 &"
6319 /*ˇ */
6320 /*ˇ */
6321 "
6322 .unindent(),
6323 );
6324
6325 // one cursor autocloses a multi-character pair, one cursor
6326 // does not autoclose.
6327 cx.set_state(
6328 &"
6329 /ˇ
6330 ˇ
6331 "
6332 .unindent(),
6333 );
6334 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6335 cx.assert_editor_state(
6336 &"
6337 /*ˇ */
6338 *ˇ
6339 "
6340 .unindent(),
6341 );
6342
6343 // Don't autoclose if the next character isn't whitespace and isn't
6344 // listed in the language's "autoclose_before" section.
6345 cx.set_state("ˇa b");
6346 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6347 cx.assert_editor_state("{ˇa b");
6348
6349 // Don't autoclose if `close` is false for the bracket pair
6350 cx.set_state("ˇ");
6351 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6352 cx.assert_editor_state("[ˇ");
6353
6354 // Surround with brackets if text is selected
6355 cx.set_state("«aˇ» b");
6356 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6357 cx.assert_editor_state("{«aˇ»} b");
6358
6359 // Autclose pair where the start and end characters are the same
6360 cx.set_state("aˇ");
6361 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6362 cx.assert_editor_state("a\"ˇ\"");
6363 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6364 cx.assert_editor_state("a\"\"ˇ");
6365
6366 // Don't autoclose pair if autoclose is disabled
6367 cx.set_state("ˇ");
6368 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6369 cx.assert_editor_state("<ˇ");
6370
6371 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6372 cx.set_state("«aˇ» b");
6373 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6374 cx.assert_editor_state("<«aˇ»> b");
6375}
6376
6377#[gpui::test]
6378async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6379 init_test(cx, |settings| {
6380 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6381 });
6382
6383 let mut cx = EditorTestContext::new(cx).await;
6384
6385 let language = Arc::new(Language::new(
6386 LanguageConfig {
6387 brackets: BracketPairConfig {
6388 pairs: vec![
6389 BracketPair {
6390 start: "{".to_string(),
6391 end: "}".to_string(),
6392 close: true,
6393 surround: true,
6394 newline: true,
6395 },
6396 BracketPair {
6397 start: "(".to_string(),
6398 end: ")".to_string(),
6399 close: true,
6400 surround: true,
6401 newline: true,
6402 },
6403 BracketPair {
6404 start: "[".to_string(),
6405 end: "]".to_string(),
6406 close: false,
6407 surround: false,
6408 newline: true,
6409 },
6410 ],
6411 ..Default::default()
6412 },
6413 autoclose_before: "})]".to_string(),
6414 ..Default::default()
6415 },
6416 Some(tree_sitter_rust::LANGUAGE.into()),
6417 ));
6418
6419 cx.language_registry().add(language.clone());
6420 cx.update_buffer(|buffer, cx| {
6421 buffer.set_language(Some(language), cx);
6422 });
6423
6424 cx.set_state(
6425 &"
6426 ˇ
6427 ˇ
6428 ˇ
6429 "
6430 .unindent(),
6431 );
6432
6433 // ensure only matching closing brackets are skipped over
6434 cx.update_editor(|editor, window, cx| {
6435 editor.handle_input("}", window, cx);
6436 editor.move_left(&MoveLeft, window, cx);
6437 editor.handle_input(")", window, cx);
6438 editor.move_left(&MoveLeft, window, cx);
6439 });
6440 cx.assert_editor_state(
6441 &"
6442 ˇ)}
6443 ˇ)}
6444 ˇ)}
6445 "
6446 .unindent(),
6447 );
6448
6449 // skip-over closing brackets at multiple cursors
6450 cx.update_editor(|editor, window, cx| {
6451 editor.handle_input(")", window, cx);
6452 editor.handle_input("}", window, cx);
6453 });
6454 cx.assert_editor_state(
6455 &"
6456 )}ˇ
6457 )}ˇ
6458 )}ˇ
6459 "
6460 .unindent(),
6461 );
6462
6463 // ignore non-close brackets
6464 cx.update_editor(|editor, window, cx| {
6465 editor.handle_input("]", window, cx);
6466 editor.move_left(&MoveLeft, window, cx);
6467 editor.handle_input("]", window, cx);
6468 });
6469 cx.assert_editor_state(
6470 &"
6471 )}]ˇ]
6472 )}]ˇ]
6473 )}]ˇ]
6474 "
6475 .unindent(),
6476 );
6477}
6478
6479#[gpui::test]
6480async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6481 init_test(cx, |_| {});
6482
6483 let mut cx = EditorTestContext::new(cx).await;
6484
6485 let html_language = Arc::new(
6486 Language::new(
6487 LanguageConfig {
6488 name: "HTML".into(),
6489 brackets: BracketPairConfig {
6490 pairs: vec![
6491 BracketPair {
6492 start: "<".into(),
6493 end: ">".into(),
6494 close: true,
6495 ..Default::default()
6496 },
6497 BracketPair {
6498 start: "{".into(),
6499 end: "}".into(),
6500 close: true,
6501 ..Default::default()
6502 },
6503 BracketPair {
6504 start: "(".into(),
6505 end: ")".into(),
6506 close: true,
6507 ..Default::default()
6508 },
6509 ],
6510 ..Default::default()
6511 },
6512 autoclose_before: "})]>".into(),
6513 ..Default::default()
6514 },
6515 Some(tree_sitter_html::LANGUAGE.into()),
6516 )
6517 .with_injection_query(
6518 r#"
6519 (script_element
6520 (raw_text) @injection.content
6521 (#set! injection.language "javascript"))
6522 "#,
6523 )
6524 .unwrap(),
6525 );
6526
6527 let javascript_language = Arc::new(Language::new(
6528 LanguageConfig {
6529 name: "JavaScript".into(),
6530 brackets: BracketPairConfig {
6531 pairs: vec![
6532 BracketPair {
6533 start: "/*".into(),
6534 end: " */".into(),
6535 close: true,
6536 ..Default::default()
6537 },
6538 BracketPair {
6539 start: "{".into(),
6540 end: "}".into(),
6541 close: true,
6542 ..Default::default()
6543 },
6544 BracketPair {
6545 start: "(".into(),
6546 end: ")".into(),
6547 close: true,
6548 ..Default::default()
6549 },
6550 ],
6551 ..Default::default()
6552 },
6553 autoclose_before: "})]>".into(),
6554 ..Default::default()
6555 },
6556 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6557 ));
6558
6559 cx.language_registry().add(html_language.clone());
6560 cx.language_registry().add(javascript_language.clone());
6561
6562 cx.update_buffer(|buffer, cx| {
6563 buffer.set_language(Some(html_language), cx);
6564 });
6565
6566 cx.set_state(
6567 &r#"
6568 <body>ˇ
6569 <script>
6570 var x = 1;ˇ
6571 </script>
6572 </body>ˇ
6573 "#
6574 .unindent(),
6575 );
6576
6577 // Precondition: different languages are active at different locations.
6578 cx.update_editor(|editor, window, cx| {
6579 let snapshot = editor.snapshot(window, cx);
6580 let cursors = editor.selections.ranges::<usize>(cx);
6581 let languages = cursors
6582 .iter()
6583 .map(|c| snapshot.language_at(c.start).unwrap().name())
6584 .collect::<Vec<_>>();
6585 assert_eq!(
6586 languages,
6587 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6588 );
6589 });
6590
6591 // Angle brackets autoclose in HTML, but not JavaScript.
6592 cx.update_editor(|editor, window, cx| {
6593 editor.handle_input("<", window, cx);
6594 editor.handle_input("a", window, cx);
6595 });
6596 cx.assert_editor_state(
6597 &r#"
6598 <body><aˇ>
6599 <script>
6600 var x = 1;<aˇ
6601 </script>
6602 </body><aˇ>
6603 "#
6604 .unindent(),
6605 );
6606
6607 // Curly braces and parens autoclose in both HTML and JavaScript.
6608 cx.update_editor(|editor, window, cx| {
6609 editor.handle_input(" b=", window, cx);
6610 editor.handle_input("{", window, cx);
6611 editor.handle_input("c", window, cx);
6612 editor.handle_input("(", window, cx);
6613 });
6614 cx.assert_editor_state(
6615 &r#"
6616 <body><a b={c(ˇ)}>
6617 <script>
6618 var x = 1;<a b={c(ˇ)}
6619 </script>
6620 </body><a b={c(ˇ)}>
6621 "#
6622 .unindent(),
6623 );
6624
6625 // Brackets that were already autoclosed are skipped.
6626 cx.update_editor(|editor, window, cx| {
6627 editor.handle_input(")", window, cx);
6628 editor.handle_input("d", window, cx);
6629 editor.handle_input("}", window, cx);
6630 });
6631 cx.assert_editor_state(
6632 &r#"
6633 <body><a b={c()d}ˇ>
6634 <script>
6635 var x = 1;<a b={c()d}ˇ
6636 </script>
6637 </body><a b={c()d}ˇ>
6638 "#
6639 .unindent(),
6640 );
6641 cx.update_editor(|editor, window, cx| {
6642 editor.handle_input(">", window, cx);
6643 });
6644 cx.assert_editor_state(
6645 &r#"
6646 <body><a b={c()d}>ˇ
6647 <script>
6648 var x = 1;<a b={c()d}>ˇ
6649 </script>
6650 </body><a b={c()d}>ˇ
6651 "#
6652 .unindent(),
6653 );
6654
6655 // Reset
6656 cx.set_state(
6657 &r#"
6658 <body>ˇ
6659 <script>
6660 var x = 1;ˇ
6661 </script>
6662 </body>ˇ
6663 "#
6664 .unindent(),
6665 );
6666
6667 cx.update_editor(|editor, window, cx| {
6668 editor.handle_input("<", window, cx);
6669 });
6670 cx.assert_editor_state(
6671 &r#"
6672 <body><ˇ>
6673 <script>
6674 var x = 1;<ˇ
6675 </script>
6676 </body><ˇ>
6677 "#
6678 .unindent(),
6679 );
6680
6681 // When backspacing, the closing angle brackets are removed.
6682 cx.update_editor(|editor, window, cx| {
6683 editor.backspace(&Backspace, window, cx);
6684 });
6685 cx.assert_editor_state(
6686 &r#"
6687 <body>ˇ
6688 <script>
6689 var x = 1;ˇ
6690 </script>
6691 </body>ˇ
6692 "#
6693 .unindent(),
6694 );
6695
6696 // Block comments autoclose in JavaScript, but not HTML.
6697 cx.update_editor(|editor, window, cx| {
6698 editor.handle_input("/", window, cx);
6699 editor.handle_input("*", window, cx);
6700 });
6701 cx.assert_editor_state(
6702 &r#"
6703 <body>/*ˇ
6704 <script>
6705 var x = 1;/*ˇ */
6706 </script>
6707 </body>/*ˇ
6708 "#
6709 .unindent(),
6710 );
6711}
6712
6713#[gpui::test]
6714async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6715 init_test(cx, |_| {});
6716
6717 let mut cx = EditorTestContext::new(cx).await;
6718
6719 let rust_language = Arc::new(
6720 Language::new(
6721 LanguageConfig {
6722 name: "Rust".into(),
6723 brackets: serde_json::from_value(json!([
6724 { "start": "{", "end": "}", "close": true, "newline": true },
6725 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6726 ]))
6727 .unwrap(),
6728 autoclose_before: "})]>".into(),
6729 ..Default::default()
6730 },
6731 Some(tree_sitter_rust::LANGUAGE.into()),
6732 )
6733 .with_override_query("(string_literal) @string")
6734 .unwrap(),
6735 );
6736
6737 cx.language_registry().add(rust_language.clone());
6738 cx.update_buffer(|buffer, cx| {
6739 buffer.set_language(Some(rust_language), cx);
6740 });
6741
6742 cx.set_state(
6743 &r#"
6744 let x = ˇ
6745 "#
6746 .unindent(),
6747 );
6748
6749 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6750 cx.update_editor(|editor, window, cx| {
6751 editor.handle_input("\"", window, cx);
6752 });
6753 cx.assert_editor_state(
6754 &r#"
6755 let x = "ˇ"
6756 "#
6757 .unindent(),
6758 );
6759
6760 // Inserting another quotation mark. The cursor moves across the existing
6761 // automatically-inserted quotation mark.
6762 cx.update_editor(|editor, window, cx| {
6763 editor.handle_input("\"", window, cx);
6764 });
6765 cx.assert_editor_state(
6766 &r#"
6767 let x = ""ˇ
6768 "#
6769 .unindent(),
6770 );
6771
6772 // Reset
6773 cx.set_state(
6774 &r#"
6775 let x = ˇ
6776 "#
6777 .unindent(),
6778 );
6779
6780 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6781 cx.update_editor(|editor, window, cx| {
6782 editor.handle_input("\"", window, cx);
6783 editor.handle_input(" ", window, cx);
6784 editor.move_left(&Default::default(), window, cx);
6785 editor.handle_input("\\", window, cx);
6786 editor.handle_input("\"", window, cx);
6787 });
6788 cx.assert_editor_state(
6789 &r#"
6790 let x = "\"ˇ "
6791 "#
6792 .unindent(),
6793 );
6794
6795 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6796 // mark. Nothing is inserted.
6797 cx.update_editor(|editor, window, cx| {
6798 editor.move_right(&Default::default(), window, cx);
6799 editor.handle_input("\"", window, cx);
6800 });
6801 cx.assert_editor_state(
6802 &r#"
6803 let x = "\" "ˇ
6804 "#
6805 .unindent(),
6806 );
6807}
6808
6809#[gpui::test]
6810async fn test_surround_with_pair(cx: &mut TestAppContext) {
6811 init_test(cx, |_| {});
6812
6813 let language = Arc::new(Language::new(
6814 LanguageConfig {
6815 brackets: BracketPairConfig {
6816 pairs: vec![
6817 BracketPair {
6818 start: "{".to_string(),
6819 end: "}".to_string(),
6820 close: true,
6821 surround: true,
6822 newline: true,
6823 },
6824 BracketPair {
6825 start: "/* ".to_string(),
6826 end: "*/".to_string(),
6827 close: true,
6828 surround: true,
6829 ..Default::default()
6830 },
6831 ],
6832 ..Default::default()
6833 },
6834 ..Default::default()
6835 },
6836 Some(tree_sitter_rust::LANGUAGE.into()),
6837 ));
6838
6839 let text = r#"
6840 a
6841 b
6842 c
6843 "#
6844 .unindent();
6845
6846 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6847 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6848 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6849 editor
6850 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6851 .await;
6852
6853 editor.update_in(cx, |editor, window, cx| {
6854 editor.change_selections(None, window, cx, |s| {
6855 s.select_display_ranges([
6856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6857 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6858 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6859 ])
6860 });
6861
6862 editor.handle_input("{", window, cx);
6863 editor.handle_input("{", window, cx);
6864 editor.handle_input("{", window, cx);
6865 assert_eq!(
6866 editor.text(cx),
6867 "
6868 {{{a}}}
6869 {{{b}}}
6870 {{{c}}}
6871 "
6872 .unindent()
6873 );
6874 assert_eq!(
6875 editor.selections.display_ranges(cx),
6876 [
6877 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6878 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6879 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6880 ]
6881 );
6882
6883 editor.undo(&Undo, window, cx);
6884 editor.undo(&Undo, window, cx);
6885 editor.undo(&Undo, window, cx);
6886 assert_eq!(
6887 editor.text(cx),
6888 "
6889 a
6890 b
6891 c
6892 "
6893 .unindent()
6894 );
6895 assert_eq!(
6896 editor.selections.display_ranges(cx),
6897 [
6898 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6899 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6900 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6901 ]
6902 );
6903
6904 // Ensure inserting the first character of a multi-byte bracket pair
6905 // doesn't surround the selections with the bracket.
6906 editor.handle_input("/", window, cx);
6907 assert_eq!(
6908 editor.text(cx),
6909 "
6910 /
6911 /
6912 /
6913 "
6914 .unindent()
6915 );
6916 assert_eq!(
6917 editor.selections.display_ranges(cx),
6918 [
6919 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6920 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6921 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6922 ]
6923 );
6924
6925 editor.undo(&Undo, window, cx);
6926 assert_eq!(
6927 editor.text(cx),
6928 "
6929 a
6930 b
6931 c
6932 "
6933 .unindent()
6934 );
6935 assert_eq!(
6936 editor.selections.display_ranges(cx),
6937 [
6938 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6939 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6940 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6941 ]
6942 );
6943
6944 // Ensure inserting the last character of a multi-byte bracket pair
6945 // doesn't surround the selections with the bracket.
6946 editor.handle_input("*", window, cx);
6947 assert_eq!(
6948 editor.text(cx),
6949 "
6950 *
6951 *
6952 *
6953 "
6954 .unindent()
6955 );
6956 assert_eq!(
6957 editor.selections.display_ranges(cx),
6958 [
6959 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6960 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6961 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6962 ]
6963 );
6964 });
6965}
6966
6967#[gpui::test]
6968async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6969 init_test(cx, |_| {});
6970
6971 let language = Arc::new(Language::new(
6972 LanguageConfig {
6973 brackets: BracketPairConfig {
6974 pairs: vec![BracketPair {
6975 start: "{".to_string(),
6976 end: "}".to_string(),
6977 close: true,
6978 surround: true,
6979 newline: true,
6980 }],
6981 ..Default::default()
6982 },
6983 autoclose_before: "}".to_string(),
6984 ..Default::default()
6985 },
6986 Some(tree_sitter_rust::LANGUAGE.into()),
6987 ));
6988
6989 let text = r#"
6990 a
6991 b
6992 c
6993 "#
6994 .unindent();
6995
6996 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6997 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6998 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6999 editor
7000 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7001 .await;
7002
7003 editor.update_in(cx, |editor, window, cx| {
7004 editor.change_selections(None, window, cx, |s| {
7005 s.select_ranges([
7006 Point::new(0, 1)..Point::new(0, 1),
7007 Point::new(1, 1)..Point::new(1, 1),
7008 Point::new(2, 1)..Point::new(2, 1),
7009 ])
7010 });
7011
7012 editor.handle_input("{", window, cx);
7013 editor.handle_input("{", window, cx);
7014 editor.handle_input("_", window, cx);
7015 assert_eq!(
7016 editor.text(cx),
7017 "
7018 a{{_}}
7019 b{{_}}
7020 c{{_}}
7021 "
7022 .unindent()
7023 );
7024 assert_eq!(
7025 editor.selections.ranges::<Point>(cx),
7026 [
7027 Point::new(0, 4)..Point::new(0, 4),
7028 Point::new(1, 4)..Point::new(1, 4),
7029 Point::new(2, 4)..Point::new(2, 4)
7030 ]
7031 );
7032
7033 editor.backspace(&Default::default(), window, cx);
7034 editor.backspace(&Default::default(), window, cx);
7035 assert_eq!(
7036 editor.text(cx),
7037 "
7038 a{}
7039 b{}
7040 c{}
7041 "
7042 .unindent()
7043 );
7044 assert_eq!(
7045 editor.selections.ranges::<Point>(cx),
7046 [
7047 Point::new(0, 2)..Point::new(0, 2),
7048 Point::new(1, 2)..Point::new(1, 2),
7049 Point::new(2, 2)..Point::new(2, 2)
7050 ]
7051 );
7052
7053 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7054 assert_eq!(
7055 editor.text(cx),
7056 "
7057 a
7058 b
7059 c
7060 "
7061 .unindent()
7062 );
7063 assert_eq!(
7064 editor.selections.ranges::<Point>(cx),
7065 [
7066 Point::new(0, 1)..Point::new(0, 1),
7067 Point::new(1, 1)..Point::new(1, 1),
7068 Point::new(2, 1)..Point::new(2, 1)
7069 ]
7070 );
7071 });
7072}
7073
7074#[gpui::test]
7075async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7076 init_test(cx, |settings| {
7077 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7078 });
7079
7080 let mut cx = EditorTestContext::new(cx).await;
7081
7082 let language = Arc::new(Language::new(
7083 LanguageConfig {
7084 brackets: BracketPairConfig {
7085 pairs: vec![
7086 BracketPair {
7087 start: "{".to_string(),
7088 end: "}".to_string(),
7089 close: true,
7090 surround: true,
7091 newline: true,
7092 },
7093 BracketPair {
7094 start: "(".to_string(),
7095 end: ")".to_string(),
7096 close: true,
7097 surround: true,
7098 newline: true,
7099 },
7100 BracketPair {
7101 start: "[".to_string(),
7102 end: "]".to_string(),
7103 close: false,
7104 surround: true,
7105 newline: true,
7106 },
7107 ],
7108 ..Default::default()
7109 },
7110 autoclose_before: "})]".to_string(),
7111 ..Default::default()
7112 },
7113 Some(tree_sitter_rust::LANGUAGE.into()),
7114 ));
7115
7116 cx.language_registry().add(language.clone());
7117 cx.update_buffer(|buffer, cx| {
7118 buffer.set_language(Some(language), cx);
7119 });
7120
7121 cx.set_state(
7122 &"
7123 {(ˇ)}
7124 [[ˇ]]
7125 {(ˇ)}
7126 "
7127 .unindent(),
7128 );
7129
7130 cx.update_editor(|editor, window, cx| {
7131 editor.backspace(&Default::default(), window, cx);
7132 editor.backspace(&Default::default(), window, cx);
7133 });
7134
7135 cx.assert_editor_state(
7136 &"
7137 ˇ
7138 ˇ]]
7139 ˇ
7140 "
7141 .unindent(),
7142 );
7143
7144 cx.update_editor(|editor, window, cx| {
7145 editor.handle_input("{", window, cx);
7146 editor.handle_input("{", window, cx);
7147 editor.move_right(&MoveRight, window, cx);
7148 editor.move_right(&MoveRight, window, cx);
7149 editor.move_left(&MoveLeft, window, cx);
7150 editor.move_left(&MoveLeft, window, cx);
7151 editor.backspace(&Default::default(), window, cx);
7152 });
7153
7154 cx.assert_editor_state(
7155 &"
7156 {ˇ}
7157 {ˇ}]]
7158 {ˇ}
7159 "
7160 .unindent(),
7161 );
7162
7163 cx.update_editor(|editor, window, cx| {
7164 editor.backspace(&Default::default(), window, cx);
7165 });
7166
7167 cx.assert_editor_state(
7168 &"
7169 ˇ
7170 ˇ]]
7171 ˇ
7172 "
7173 .unindent(),
7174 );
7175}
7176
7177#[gpui::test]
7178async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7179 init_test(cx, |_| {});
7180
7181 let language = Arc::new(Language::new(
7182 LanguageConfig::default(),
7183 Some(tree_sitter_rust::LANGUAGE.into()),
7184 ));
7185
7186 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7187 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7188 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7189 editor
7190 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7191 .await;
7192
7193 editor.update_in(cx, |editor, window, cx| {
7194 editor.set_auto_replace_emoji_shortcode(true);
7195
7196 editor.handle_input("Hello ", window, cx);
7197 editor.handle_input(":wave", window, cx);
7198 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7199
7200 editor.handle_input(":", window, cx);
7201 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7202
7203 editor.handle_input(" :smile", window, cx);
7204 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7205
7206 editor.handle_input(":", window, cx);
7207 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7208
7209 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7210 editor.handle_input(":wave", window, cx);
7211 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7212
7213 editor.handle_input(":", window, cx);
7214 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7215
7216 editor.handle_input(":1", window, cx);
7217 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7218
7219 editor.handle_input(":", window, cx);
7220 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7221
7222 // Ensure shortcode does not get replaced when it is part of a word
7223 editor.handle_input(" Test:wave", window, cx);
7224 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7225
7226 editor.handle_input(":", window, cx);
7227 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7228
7229 editor.set_auto_replace_emoji_shortcode(false);
7230
7231 // Ensure shortcode does not get replaced when auto replace is off
7232 editor.handle_input(" :wave", window, cx);
7233 assert_eq!(
7234 editor.text(cx),
7235 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7236 );
7237
7238 editor.handle_input(":", window, cx);
7239 assert_eq!(
7240 editor.text(cx),
7241 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7242 );
7243 });
7244}
7245
7246#[gpui::test]
7247async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7248 init_test(cx, |_| {});
7249
7250 let (text, insertion_ranges) = marked_text_ranges(
7251 indoc! {"
7252 ˇ
7253 "},
7254 false,
7255 );
7256
7257 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7258 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7259
7260 _ = editor.update_in(cx, |editor, window, cx| {
7261 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7262
7263 editor
7264 .insert_snippet(&insertion_ranges, snippet, window, cx)
7265 .unwrap();
7266
7267 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7268 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7269 assert_eq!(editor.text(cx), expected_text);
7270 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7271 }
7272
7273 assert(
7274 editor,
7275 cx,
7276 indoc! {"
7277 type «» =•
7278 "},
7279 );
7280
7281 assert!(editor.context_menu_visible(), "There should be a matches");
7282 });
7283}
7284
7285#[gpui::test]
7286async fn test_snippets(cx: &mut TestAppContext) {
7287 init_test(cx, |_| {});
7288
7289 let (text, insertion_ranges) = marked_text_ranges(
7290 indoc! {"
7291 a.ˇ b
7292 a.ˇ b
7293 a.ˇ b
7294 "},
7295 false,
7296 );
7297
7298 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7299 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7300
7301 editor.update_in(cx, |editor, window, cx| {
7302 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7303
7304 editor
7305 .insert_snippet(&insertion_ranges, snippet, window, cx)
7306 .unwrap();
7307
7308 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7309 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7310 assert_eq!(editor.text(cx), expected_text);
7311 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7312 }
7313
7314 assert(
7315 editor,
7316 cx,
7317 indoc! {"
7318 a.f(«one», two, «three») b
7319 a.f(«one», two, «three») b
7320 a.f(«one», two, «three») b
7321 "},
7322 );
7323
7324 // Can't move earlier than the first tab stop
7325 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7326 assert(
7327 editor,
7328 cx,
7329 indoc! {"
7330 a.f(«one», two, «three») b
7331 a.f(«one», two, «three») b
7332 a.f(«one», two, «three») b
7333 "},
7334 );
7335
7336 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7337 assert(
7338 editor,
7339 cx,
7340 indoc! {"
7341 a.f(one, «two», three) b
7342 a.f(one, «two», three) b
7343 a.f(one, «two», three) b
7344 "},
7345 );
7346
7347 editor.move_to_prev_snippet_tabstop(window, cx);
7348 assert(
7349 editor,
7350 cx,
7351 indoc! {"
7352 a.f(«one», two, «three») b
7353 a.f(«one», two, «three») b
7354 a.f(«one», two, «three») b
7355 "},
7356 );
7357
7358 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7359 assert(
7360 editor,
7361 cx,
7362 indoc! {"
7363 a.f(one, «two», three) b
7364 a.f(one, «two», three) b
7365 a.f(one, «two», three) b
7366 "},
7367 );
7368 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7369 assert(
7370 editor,
7371 cx,
7372 indoc! {"
7373 a.f(one, two, three)ˇ b
7374 a.f(one, two, three)ˇ b
7375 a.f(one, two, three)ˇ b
7376 "},
7377 );
7378
7379 // As soon as the last tab stop is reached, snippet state is gone
7380 editor.move_to_prev_snippet_tabstop(window, cx);
7381 assert(
7382 editor,
7383 cx,
7384 indoc! {"
7385 a.f(one, two, three)ˇ b
7386 a.f(one, two, three)ˇ b
7387 a.f(one, two, three)ˇ b
7388 "},
7389 );
7390 });
7391}
7392
7393#[gpui::test]
7394async fn test_document_format_during_save(cx: &mut TestAppContext) {
7395 init_test(cx, |_| {});
7396
7397 let fs = FakeFs::new(cx.executor());
7398 fs.insert_file(path!("/file.rs"), Default::default()).await;
7399
7400 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7401
7402 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7403 language_registry.add(rust_lang());
7404 let mut fake_servers = language_registry.register_fake_lsp(
7405 "Rust",
7406 FakeLspAdapter {
7407 capabilities: lsp::ServerCapabilities {
7408 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7409 ..Default::default()
7410 },
7411 ..Default::default()
7412 },
7413 );
7414
7415 let buffer = project
7416 .update(cx, |project, cx| {
7417 project.open_local_buffer(path!("/file.rs"), cx)
7418 })
7419 .await
7420 .unwrap();
7421
7422 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7423 let (editor, cx) = cx.add_window_view(|window, cx| {
7424 build_editor_with_project(project.clone(), buffer, window, cx)
7425 });
7426 editor.update_in(cx, |editor, window, cx| {
7427 editor.set_text("one\ntwo\nthree\n", window, cx)
7428 });
7429 assert!(cx.read(|cx| editor.is_dirty(cx)));
7430
7431 cx.executor().start_waiting();
7432 let fake_server = fake_servers.next().await.unwrap();
7433
7434 let save = editor
7435 .update_in(cx, |editor, window, cx| {
7436 editor.save(true, project.clone(), window, cx)
7437 })
7438 .unwrap();
7439 fake_server
7440 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7441 assert_eq!(
7442 params.text_document.uri,
7443 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7444 );
7445 assert_eq!(params.options.tab_size, 4);
7446 Ok(Some(vec![lsp::TextEdit::new(
7447 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7448 ", ".to_string(),
7449 )]))
7450 })
7451 .next()
7452 .await;
7453 cx.executor().start_waiting();
7454 save.await;
7455
7456 assert_eq!(
7457 editor.update(cx, |editor, cx| editor.text(cx)),
7458 "one, two\nthree\n"
7459 );
7460 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7461
7462 editor.update_in(cx, |editor, window, cx| {
7463 editor.set_text("one\ntwo\nthree\n", window, cx)
7464 });
7465 assert!(cx.read(|cx| editor.is_dirty(cx)));
7466
7467 // Ensure we can still save even if formatting hangs.
7468 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7469 assert_eq!(
7470 params.text_document.uri,
7471 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7472 );
7473 futures::future::pending::<()>().await;
7474 unreachable!()
7475 });
7476 let save = editor
7477 .update_in(cx, |editor, window, cx| {
7478 editor.save(true, project.clone(), window, cx)
7479 })
7480 .unwrap();
7481 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7482 cx.executor().start_waiting();
7483 save.await;
7484 assert_eq!(
7485 editor.update(cx, |editor, cx| editor.text(cx)),
7486 "one\ntwo\nthree\n"
7487 );
7488 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7489
7490 // For non-dirty buffer, no formatting request should be sent
7491 let save = editor
7492 .update_in(cx, |editor, window, cx| {
7493 editor.save(true, project.clone(), window, cx)
7494 })
7495 .unwrap();
7496 let _pending_format_request = fake_server
7497 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7498 panic!("Should not be invoked on non-dirty buffer");
7499 })
7500 .next();
7501 cx.executor().start_waiting();
7502 save.await;
7503
7504 // Set rust language override and assert overridden tabsize is sent to language server
7505 update_test_language_settings(cx, |settings| {
7506 settings.languages.insert(
7507 "Rust".into(),
7508 LanguageSettingsContent {
7509 tab_size: NonZeroU32::new(8),
7510 ..Default::default()
7511 },
7512 );
7513 });
7514
7515 editor.update_in(cx, |editor, window, cx| {
7516 editor.set_text("somehting_new\n", window, cx)
7517 });
7518 assert!(cx.read(|cx| editor.is_dirty(cx)));
7519 let save = editor
7520 .update_in(cx, |editor, window, cx| {
7521 editor.save(true, project.clone(), window, cx)
7522 })
7523 .unwrap();
7524 fake_server
7525 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7526 assert_eq!(
7527 params.text_document.uri,
7528 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7529 );
7530 assert_eq!(params.options.tab_size, 8);
7531 Ok(Some(vec![]))
7532 })
7533 .next()
7534 .await;
7535 cx.executor().start_waiting();
7536 save.await;
7537}
7538
7539#[gpui::test]
7540async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7541 init_test(cx, |_| {});
7542
7543 let cols = 4;
7544 let rows = 10;
7545 let sample_text_1 = sample_text(rows, cols, 'a');
7546 assert_eq!(
7547 sample_text_1,
7548 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7549 );
7550 let sample_text_2 = sample_text(rows, cols, 'l');
7551 assert_eq!(
7552 sample_text_2,
7553 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7554 );
7555 let sample_text_3 = sample_text(rows, cols, 'v');
7556 assert_eq!(
7557 sample_text_3,
7558 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7559 );
7560
7561 let fs = FakeFs::new(cx.executor());
7562 fs.insert_tree(
7563 path!("/a"),
7564 json!({
7565 "main.rs": sample_text_1,
7566 "other.rs": sample_text_2,
7567 "lib.rs": sample_text_3,
7568 }),
7569 )
7570 .await;
7571
7572 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7573 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7574 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7575
7576 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7577 language_registry.add(rust_lang());
7578 let mut fake_servers = language_registry.register_fake_lsp(
7579 "Rust",
7580 FakeLspAdapter {
7581 capabilities: lsp::ServerCapabilities {
7582 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7583 ..Default::default()
7584 },
7585 ..Default::default()
7586 },
7587 );
7588
7589 let worktree = project.update(cx, |project, cx| {
7590 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7591 assert_eq!(worktrees.len(), 1);
7592 worktrees.pop().unwrap()
7593 });
7594 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7595
7596 let buffer_1 = project
7597 .update(cx, |project, cx| {
7598 project.open_buffer((worktree_id, "main.rs"), cx)
7599 })
7600 .await
7601 .unwrap();
7602 let buffer_2 = project
7603 .update(cx, |project, cx| {
7604 project.open_buffer((worktree_id, "other.rs"), cx)
7605 })
7606 .await
7607 .unwrap();
7608 let buffer_3 = project
7609 .update(cx, |project, cx| {
7610 project.open_buffer((worktree_id, "lib.rs"), cx)
7611 })
7612 .await
7613 .unwrap();
7614
7615 let multi_buffer = cx.new(|cx| {
7616 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7617 multi_buffer.push_excerpts(
7618 buffer_1.clone(),
7619 [
7620 ExcerptRange {
7621 context: Point::new(0, 0)..Point::new(3, 0),
7622 primary: None,
7623 },
7624 ExcerptRange {
7625 context: Point::new(5, 0)..Point::new(7, 0),
7626 primary: None,
7627 },
7628 ExcerptRange {
7629 context: Point::new(9, 0)..Point::new(10, 4),
7630 primary: None,
7631 },
7632 ],
7633 cx,
7634 );
7635 multi_buffer.push_excerpts(
7636 buffer_2.clone(),
7637 [
7638 ExcerptRange {
7639 context: Point::new(0, 0)..Point::new(3, 0),
7640 primary: None,
7641 },
7642 ExcerptRange {
7643 context: Point::new(5, 0)..Point::new(7, 0),
7644 primary: None,
7645 },
7646 ExcerptRange {
7647 context: Point::new(9, 0)..Point::new(10, 4),
7648 primary: None,
7649 },
7650 ],
7651 cx,
7652 );
7653 multi_buffer.push_excerpts(
7654 buffer_3.clone(),
7655 [
7656 ExcerptRange {
7657 context: Point::new(0, 0)..Point::new(3, 0),
7658 primary: None,
7659 },
7660 ExcerptRange {
7661 context: Point::new(5, 0)..Point::new(7, 0),
7662 primary: None,
7663 },
7664 ExcerptRange {
7665 context: Point::new(9, 0)..Point::new(10, 4),
7666 primary: None,
7667 },
7668 ],
7669 cx,
7670 );
7671 multi_buffer
7672 });
7673 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7674 Editor::new(
7675 EditorMode::Full,
7676 multi_buffer,
7677 Some(project.clone()),
7678 true,
7679 window,
7680 cx,
7681 )
7682 });
7683
7684 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7685 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7686 s.select_ranges(Some(1..2))
7687 });
7688 editor.insert("|one|two|three|", window, cx);
7689 });
7690 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7691 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7692 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7693 s.select_ranges(Some(60..70))
7694 });
7695 editor.insert("|four|five|six|", window, cx);
7696 });
7697 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7698
7699 // First two buffers should be edited, but not the third one.
7700 assert_eq!(
7701 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7702 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7703 );
7704 buffer_1.update(cx, |buffer, _| {
7705 assert!(buffer.is_dirty());
7706 assert_eq!(
7707 buffer.text(),
7708 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7709 )
7710 });
7711 buffer_2.update(cx, |buffer, _| {
7712 assert!(buffer.is_dirty());
7713 assert_eq!(
7714 buffer.text(),
7715 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7716 )
7717 });
7718 buffer_3.update(cx, |buffer, _| {
7719 assert!(!buffer.is_dirty());
7720 assert_eq!(buffer.text(), sample_text_3,)
7721 });
7722 cx.executor().run_until_parked();
7723
7724 cx.executor().start_waiting();
7725 let save = multi_buffer_editor
7726 .update_in(cx, |editor, window, cx| {
7727 editor.save(true, project.clone(), window, cx)
7728 })
7729 .unwrap();
7730
7731 let fake_server = fake_servers.next().await.unwrap();
7732 fake_server
7733 .server
7734 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7735 Ok(Some(vec![lsp::TextEdit::new(
7736 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7737 format!("[{} formatted]", params.text_document.uri),
7738 )]))
7739 })
7740 .detach();
7741 save.await;
7742
7743 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7744 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7745 assert_eq!(
7746 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7747 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"),
7748 );
7749 buffer_1.update(cx, |buffer, _| {
7750 assert!(!buffer.is_dirty());
7751 assert_eq!(
7752 buffer.text(),
7753 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7754 )
7755 });
7756 buffer_2.update(cx, |buffer, _| {
7757 assert!(!buffer.is_dirty());
7758 assert_eq!(
7759 buffer.text(),
7760 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7761 )
7762 });
7763 buffer_3.update(cx, |buffer, _| {
7764 assert!(!buffer.is_dirty());
7765 assert_eq!(buffer.text(), sample_text_3,)
7766 });
7767}
7768
7769#[gpui::test]
7770async fn test_range_format_during_save(cx: &mut TestAppContext) {
7771 init_test(cx, |_| {});
7772
7773 let fs = FakeFs::new(cx.executor());
7774 fs.insert_file(path!("/file.rs"), Default::default()).await;
7775
7776 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7777
7778 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7779 language_registry.add(rust_lang());
7780 let mut fake_servers = language_registry.register_fake_lsp(
7781 "Rust",
7782 FakeLspAdapter {
7783 capabilities: lsp::ServerCapabilities {
7784 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7785 ..Default::default()
7786 },
7787 ..Default::default()
7788 },
7789 );
7790
7791 let buffer = project
7792 .update(cx, |project, cx| {
7793 project.open_local_buffer(path!("/file.rs"), cx)
7794 })
7795 .await
7796 .unwrap();
7797
7798 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7799 let (editor, cx) = cx.add_window_view(|window, cx| {
7800 build_editor_with_project(project.clone(), buffer, window, cx)
7801 });
7802 editor.update_in(cx, |editor, window, cx| {
7803 editor.set_text("one\ntwo\nthree\n", window, cx)
7804 });
7805 assert!(cx.read(|cx| editor.is_dirty(cx)));
7806
7807 cx.executor().start_waiting();
7808 let fake_server = fake_servers.next().await.unwrap();
7809
7810 let save = editor
7811 .update_in(cx, |editor, window, cx| {
7812 editor.save(true, project.clone(), window, cx)
7813 })
7814 .unwrap();
7815 fake_server
7816 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7817 assert_eq!(
7818 params.text_document.uri,
7819 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7820 );
7821 assert_eq!(params.options.tab_size, 4);
7822 Ok(Some(vec![lsp::TextEdit::new(
7823 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7824 ", ".to_string(),
7825 )]))
7826 })
7827 .next()
7828 .await;
7829 cx.executor().start_waiting();
7830 save.await;
7831 assert_eq!(
7832 editor.update(cx, |editor, cx| editor.text(cx)),
7833 "one, two\nthree\n"
7834 );
7835 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7836
7837 editor.update_in(cx, |editor, window, cx| {
7838 editor.set_text("one\ntwo\nthree\n", window, cx)
7839 });
7840 assert!(cx.read(|cx| editor.is_dirty(cx)));
7841
7842 // Ensure we can still save even if formatting hangs.
7843 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7844 move |params, _| async move {
7845 assert_eq!(
7846 params.text_document.uri,
7847 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7848 );
7849 futures::future::pending::<()>().await;
7850 unreachable!()
7851 },
7852 );
7853 let save = editor
7854 .update_in(cx, |editor, window, cx| {
7855 editor.save(true, project.clone(), window, cx)
7856 })
7857 .unwrap();
7858 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7859 cx.executor().start_waiting();
7860 save.await;
7861 assert_eq!(
7862 editor.update(cx, |editor, cx| editor.text(cx)),
7863 "one\ntwo\nthree\n"
7864 );
7865 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7866
7867 // For non-dirty buffer, no formatting request should be sent
7868 let save = editor
7869 .update_in(cx, |editor, window, cx| {
7870 editor.save(true, project.clone(), window, cx)
7871 })
7872 .unwrap();
7873 let _pending_format_request = fake_server
7874 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7875 panic!("Should not be invoked on non-dirty buffer");
7876 })
7877 .next();
7878 cx.executor().start_waiting();
7879 save.await;
7880
7881 // Set Rust language override and assert overridden tabsize is sent to language server
7882 update_test_language_settings(cx, |settings| {
7883 settings.languages.insert(
7884 "Rust".into(),
7885 LanguageSettingsContent {
7886 tab_size: NonZeroU32::new(8),
7887 ..Default::default()
7888 },
7889 );
7890 });
7891
7892 editor.update_in(cx, |editor, window, cx| {
7893 editor.set_text("somehting_new\n", window, cx)
7894 });
7895 assert!(cx.read(|cx| editor.is_dirty(cx)));
7896 let save = editor
7897 .update_in(cx, |editor, window, cx| {
7898 editor.save(true, project.clone(), window, cx)
7899 })
7900 .unwrap();
7901 fake_server
7902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7903 assert_eq!(
7904 params.text_document.uri,
7905 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7906 );
7907 assert_eq!(params.options.tab_size, 8);
7908 Ok(Some(vec![]))
7909 })
7910 .next()
7911 .await;
7912 cx.executor().start_waiting();
7913 save.await;
7914}
7915
7916#[gpui::test]
7917async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7918 init_test(cx, |settings| {
7919 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7920 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7921 ))
7922 });
7923
7924 let fs = FakeFs::new(cx.executor());
7925 fs.insert_file(path!("/file.rs"), Default::default()).await;
7926
7927 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7928
7929 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7930 language_registry.add(Arc::new(Language::new(
7931 LanguageConfig {
7932 name: "Rust".into(),
7933 matcher: LanguageMatcher {
7934 path_suffixes: vec!["rs".to_string()],
7935 ..Default::default()
7936 },
7937 ..LanguageConfig::default()
7938 },
7939 Some(tree_sitter_rust::LANGUAGE.into()),
7940 )));
7941 update_test_language_settings(cx, |settings| {
7942 // Enable Prettier formatting for the same buffer, and ensure
7943 // LSP is called instead of Prettier.
7944 settings.defaults.prettier = Some(PrettierSettings {
7945 allowed: true,
7946 ..PrettierSettings::default()
7947 });
7948 });
7949 let mut fake_servers = language_registry.register_fake_lsp(
7950 "Rust",
7951 FakeLspAdapter {
7952 capabilities: lsp::ServerCapabilities {
7953 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7954 ..Default::default()
7955 },
7956 ..Default::default()
7957 },
7958 );
7959
7960 let buffer = project
7961 .update(cx, |project, cx| {
7962 project.open_local_buffer(path!("/file.rs"), cx)
7963 })
7964 .await
7965 .unwrap();
7966
7967 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7968 let (editor, cx) = cx.add_window_view(|window, cx| {
7969 build_editor_with_project(project.clone(), buffer, window, cx)
7970 });
7971 editor.update_in(cx, |editor, window, cx| {
7972 editor.set_text("one\ntwo\nthree\n", window, cx)
7973 });
7974
7975 cx.executor().start_waiting();
7976 let fake_server = fake_servers.next().await.unwrap();
7977
7978 let format = editor
7979 .update_in(cx, |editor, window, cx| {
7980 editor.perform_format(
7981 project.clone(),
7982 FormatTrigger::Manual,
7983 FormatTarget::Buffers,
7984 window,
7985 cx,
7986 )
7987 })
7988 .unwrap();
7989 fake_server
7990 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7991 assert_eq!(
7992 params.text_document.uri,
7993 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7994 );
7995 assert_eq!(params.options.tab_size, 4);
7996 Ok(Some(vec![lsp::TextEdit::new(
7997 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7998 ", ".to_string(),
7999 )]))
8000 })
8001 .next()
8002 .await;
8003 cx.executor().start_waiting();
8004 format.await;
8005 assert_eq!(
8006 editor.update(cx, |editor, cx| editor.text(cx)),
8007 "one, two\nthree\n"
8008 );
8009
8010 editor.update_in(cx, |editor, window, cx| {
8011 editor.set_text("one\ntwo\nthree\n", window, cx)
8012 });
8013 // Ensure we don't lock if formatting hangs.
8014 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8015 assert_eq!(
8016 params.text_document.uri,
8017 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8018 );
8019 futures::future::pending::<()>().await;
8020 unreachable!()
8021 });
8022 let format = editor
8023 .update_in(cx, |editor, window, cx| {
8024 editor.perform_format(
8025 project,
8026 FormatTrigger::Manual,
8027 FormatTarget::Buffers,
8028 window,
8029 cx,
8030 )
8031 })
8032 .unwrap();
8033 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8034 cx.executor().start_waiting();
8035 format.await;
8036 assert_eq!(
8037 editor.update(cx, |editor, cx| editor.text(cx)),
8038 "one\ntwo\nthree\n"
8039 );
8040}
8041
8042#[gpui::test]
8043async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8044 init_test(cx, |settings| {
8045 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8046 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8047 ))
8048 });
8049
8050 let fs = FakeFs::new(cx.executor());
8051 fs.insert_file(path!("/file.ts"), Default::default()).await;
8052
8053 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8054
8055 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8056 language_registry.add(Arc::new(Language::new(
8057 LanguageConfig {
8058 name: "TypeScript".into(),
8059 matcher: LanguageMatcher {
8060 path_suffixes: vec!["ts".to_string()],
8061 ..Default::default()
8062 },
8063 ..LanguageConfig::default()
8064 },
8065 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8066 )));
8067 update_test_language_settings(cx, |settings| {
8068 settings.defaults.prettier = Some(PrettierSettings {
8069 allowed: true,
8070 ..PrettierSettings::default()
8071 });
8072 });
8073 let mut fake_servers = language_registry.register_fake_lsp(
8074 "TypeScript",
8075 FakeLspAdapter {
8076 capabilities: lsp::ServerCapabilities {
8077 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8078 ..Default::default()
8079 },
8080 ..Default::default()
8081 },
8082 );
8083
8084 let buffer = project
8085 .update(cx, |project, cx| {
8086 project.open_local_buffer(path!("/file.ts"), cx)
8087 })
8088 .await
8089 .unwrap();
8090
8091 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8092 let (editor, cx) = cx.add_window_view(|window, cx| {
8093 build_editor_with_project(project.clone(), buffer, window, cx)
8094 });
8095 editor.update_in(cx, |editor, window, cx| {
8096 editor.set_text(
8097 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8098 window,
8099 cx,
8100 )
8101 });
8102
8103 cx.executor().start_waiting();
8104 let fake_server = fake_servers.next().await.unwrap();
8105
8106 let format = editor
8107 .update_in(cx, |editor, window, cx| {
8108 editor.perform_code_action_kind(
8109 project.clone(),
8110 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8111 window,
8112 cx,
8113 )
8114 })
8115 .unwrap();
8116 fake_server
8117 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8118 assert_eq!(
8119 params.text_document.uri,
8120 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8121 );
8122 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8123 lsp::CodeAction {
8124 title: "Organize Imports".to_string(),
8125 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8126 edit: Some(lsp::WorkspaceEdit {
8127 changes: Some(
8128 [(
8129 params.text_document.uri.clone(),
8130 vec![lsp::TextEdit::new(
8131 lsp::Range::new(
8132 lsp::Position::new(1, 0),
8133 lsp::Position::new(2, 0),
8134 ),
8135 "".to_string(),
8136 )],
8137 )]
8138 .into_iter()
8139 .collect(),
8140 ),
8141 ..Default::default()
8142 }),
8143 ..Default::default()
8144 },
8145 )]))
8146 })
8147 .next()
8148 .await;
8149 cx.executor().start_waiting();
8150 format.await;
8151 assert_eq!(
8152 editor.update(cx, |editor, cx| editor.text(cx)),
8153 "import { a } from 'module';\n\nconst x = a;\n"
8154 );
8155
8156 editor.update_in(cx, |editor, window, cx| {
8157 editor.set_text(
8158 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8159 window,
8160 cx,
8161 )
8162 });
8163 // Ensure we don't lock if code action hangs.
8164 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8165 move |params, _| async move {
8166 assert_eq!(
8167 params.text_document.uri,
8168 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8169 );
8170 futures::future::pending::<()>().await;
8171 unreachable!()
8172 },
8173 );
8174 let format = editor
8175 .update_in(cx, |editor, window, cx| {
8176 editor.perform_code_action_kind(
8177 project,
8178 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8179 window,
8180 cx,
8181 )
8182 })
8183 .unwrap();
8184 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8185 cx.executor().start_waiting();
8186 format.await;
8187 assert_eq!(
8188 editor.update(cx, |editor, cx| editor.text(cx)),
8189 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8190 );
8191}
8192
8193#[gpui::test]
8194async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8195 init_test(cx, |_| {});
8196
8197 let mut cx = EditorLspTestContext::new_rust(
8198 lsp::ServerCapabilities {
8199 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8200 ..Default::default()
8201 },
8202 cx,
8203 )
8204 .await;
8205
8206 cx.set_state(indoc! {"
8207 one.twoˇ
8208 "});
8209
8210 // The format request takes a long time. When it completes, it inserts
8211 // a newline and an indent before the `.`
8212 cx.lsp
8213 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8214 let executor = cx.background_executor().clone();
8215 async move {
8216 executor.timer(Duration::from_millis(100)).await;
8217 Ok(Some(vec![lsp::TextEdit {
8218 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8219 new_text: "\n ".into(),
8220 }]))
8221 }
8222 });
8223
8224 // Submit a format request.
8225 let format_1 = cx
8226 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8227 .unwrap();
8228 cx.executor().run_until_parked();
8229
8230 // Submit a second format request.
8231 let format_2 = cx
8232 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8233 .unwrap();
8234 cx.executor().run_until_parked();
8235
8236 // Wait for both format requests to complete
8237 cx.executor().advance_clock(Duration::from_millis(200));
8238 cx.executor().start_waiting();
8239 format_1.await.unwrap();
8240 cx.executor().start_waiting();
8241 format_2.await.unwrap();
8242
8243 // The formatting edits only happens once.
8244 cx.assert_editor_state(indoc! {"
8245 one
8246 .twoˇ
8247 "});
8248}
8249
8250#[gpui::test]
8251async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8252 init_test(cx, |settings| {
8253 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8254 });
8255
8256 let mut cx = EditorLspTestContext::new_rust(
8257 lsp::ServerCapabilities {
8258 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8259 ..Default::default()
8260 },
8261 cx,
8262 )
8263 .await;
8264
8265 // Set up a buffer white some trailing whitespace and no trailing newline.
8266 cx.set_state(
8267 &[
8268 "one ", //
8269 "twoˇ", //
8270 "three ", //
8271 "four", //
8272 ]
8273 .join("\n"),
8274 );
8275
8276 // Submit a format request.
8277 let format = cx
8278 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8279 .unwrap();
8280
8281 // Record which buffer changes have been sent to the language server
8282 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8283 cx.lsp
8284 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8285 let buffer_changes = buffer_changes.clone();
8286 move |params, _| {
8287 buffer_changes.lock().extend(
8288 params
8289 .content_changes
8290 .into_iter()
8291 .map(|e| (e.range.unwrap(), e.text)),
8292 );
8293 }
8294 });
8295
8296 // Handle formatting requests to the language server.
8297 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8298 let buffer_changes = buffer_changes.clone();
8299 move |_, _| {
8300 // When formatting is requested, trailing whitespace has already been stripped,
8301 // and the trailing newline has already been added.
8302 assert_eq!(
8303 &buffer_changes.lock()[1..],
8304 &[
8305 (
8306 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8307 "".into()
8308 ),
8309 (
8310 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8311 "".into()
8312 ),
8313 (
8314 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8315 "\n".into()
8316 ),
8317 ]
8318 );
8319
8320 // Insert blank lines between each line of the buffer.
8321 async move {
8322 Ok(Some(vec![
8323 lsp::TextEdit {
8324 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8325 new_text: "\n".into(),
8326 },
8327 lsp::TextEdit {
8328 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8329 new_text: "\n".into(),
8330 },
8331 ]))
8332 }
8333 }
8334 });
8335
8336 // After formatting the buffer, the trailing whitespace is stripped,
8337 // a newline is appended, and the edits provided by the language server
8338 // have been applied.
8339 format.await.unwrap();
8340 cx.assert_editor_state(
8341 &[
8342 "one", //
8343 "", //
8344 "twoˇ", //
8345 "", //
8346 "three", //
8347 "four", //
8348 "", //
8349 ]
8350 .join("\n"),
8351 );
8352
8353 // Undoing the formatting undoes the trailing whitespace removal, the
8354 // trailing newline, and the LSP edits.
8355 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8356 cx.assert_editor_state(
8357 &[
8358 "one ", //
8359 "twoˇ", //
8360 "three ", //
8361 "four", //
8362 ]
8363 .join("\n"),
8364 );
8365}
8366
8367#[gpui::test]
8368async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8369 cx: &mut TestAppContext,
8370) {
8371 init_test(cx, |_| {});
8372
8373 cx.update(|cx| {
8374 cx.update_global::<SettingsStore, _>(|settings, cx| {
8375 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8376 settings.auto_signature_help = Some(true);
8377 });
8378 });
8379 });
8380
8381 let mut cx = EditorLspTestContext::new_rust(
8382 lsp::ServerCapabilities {
8383 signature_help_provider: Some(lsp::SignatureHelpOptions {
8384 ..Default::default()
8385 }),
8386 ..Default::default()
8387 },
8388 cx,
8389 )
8390 .await;
8391
8392 let language = Language::new(
8393 LanguageConfig {
8394 name: "Rust".into(),
8395 brackets: BracketPairConfig {
8396 pairs: vec![
8397 BracketPair {
8398 start: "{".to_string(),
8399 end: "}".to_string(),
8400 close: true,
8401 surround: true,
8402 newline: true,
8403 },
8404 BracketPair {
8405 start: "(".to_string(),
8406 end: ")".to_string(),
8407 close: true,
8408 surround: true,
8409 newline: true,
8410 },
8411 BracketPair {
8412 start: "/*".to_string(),
8413 end: " */".to_string(),
8414 close: true,
8415 surround: true,
8416 newline: true,
8417 },
8418 BracketPair {
8419 start: "[".to_string(),
8420 end: "]".to_string(),
8421 close: false,
8422 surround: false,
8423 newline: true,
8424 },
8425 BracketPair {
8426 start: "\"".to_string(),
8427 end: "\"".to_string(),
8428 close: true,
8429 surround: true,
8430 newline: false,
8431 },
8432 BracketPair {
8433 start: "<".to_string(),
8434 end: ">".to_string(),
8435 close: false,
8436 surround: true,
8437 newline: true,
8438 },
8439 ],
8440 ..Default::default()
8441 },
8442 autoclose_before: "})]".to_string(),
8443 ..Default::default()
8444 },
8445 Some(tree_sitter_rust::LANGUAGE.into()),
8446 );
8447 let language = Arc::new(language);
8448
8449 cx.language_registry().add(language.clone());
8450 cx.update_buffer(|buffer, cx| {
8451 buffer.set_language(Some(language), cx);
8452 });
8453
8454 cx.set_state(
8455 &r#"
8456 fn main() {
8457 sampleˇ
8458 }
8459 "#
8460 .unindent(),
8461 );
8462
8463 cx.update_editor(|editor, window, cx| {
8464 editor.handle_input("(", window, cx);
8465 });
8466 cx.assert_editor_state(
8467 &"
8468 fn main() {
8469 sample(ˇ)
8470 }
8471 "
8472 .unindent(),
8473 );
8474
8475 let mocked_response = lsp::SignatureHelp {
8476 signatures: vec![lsp::SignatureInformation {
8477 label: "fn sample(param1: u8, param2: u8)".to_string(),
8478 documentation: None,
8479 parameters: Some(vec![
8480 lsp::ParameterInformation {
8481 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8482 documentation: None,
8483 },
8484 lsp::ParameterInformation {
8485 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8486 documentation: None,
8487 },
8488 ]),
8489 active_parameter: None,
8490 }],
8491 active_signature: Some(0),
8492 active_parameter: Some(0),
8493 };
8494 handle_signature_help_request(&mut cx, mocked_response).await;
8495
8496 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8497 .await;
8498
8499 cx.editor(|editor, _, _| {
8500 let signature_help_state = editor.signature_help_state.popover().cloned();
8501 assert_eq!(
8502 signature_help_state.unwrap().label,
8503 "param1: u8, param2: u8"
8504 );
8505 });
8506}
8507
8508#[gpui::test]
8509async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8510 init_test(cx, |_| {});
8511
8512 cx.update(|cx| {
8513 cx.update_global::<SettingsStore, _>(|settings, cx| {
8514 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8515 settings.auto_signature_help = Some(false);
8516 settings.show_signature_help_after_edits = Some(false);
8517 });
8518 });
8519 });
8520
8521 let mut cx = EditorLspTestContext::new_rust(
8522 lsp::ServerCapabilities {
8523 signature_help_provider: Some(lsp::SignatureHelpOptions {
8524 ..Default::default()
8525 }),
8526 ..Default::default()
8527 },
8528 cx,
8529 )
8530 .await;
8531
8532 let language = Language::new(
8533 LanguageConfig {
8534 name: "Rust".into(),
8535 brackets: BracketPairConfig {
8536 pairs: vec![
8537 BracketPair {
8538 start: "{".to_string(),
8539 end: "}".to_string(),
8540 close: true,
8541 surround: true,
8542 newline: true,
8543 },
8544 BracketPair {
8545 start: "(".to_string(),
8546 end: ")".to_string(),
8547 close: true,
8548 surround: true,
8549 newline: true,
8550 },
8551 BracketPair {
8552 start: "/*".to_string(),
8553 end: " */".to_string(),
8554 close: true,
8555 surround: true,
8556 newline: true,
8557 },
8558 BracketPair {
8559 start: "[".to_string(),
8560 end: "]".to_string(),
8561 close: false,
8562 surround: false,
8563 newline: true,
8564 },
8565 BracketPair {
8566 start: "\"".to_string(),
8567 end: "\"".to_string(),
8568 close: true,
8569 surround: true,
8570 newline: false,
8571 },
8572 BracketPair {
8573 start: "<".to_string(),
8574 end: ">".to_string(),
8575 close: false,
8576 surround: true,
8577 newline: true,
8578 },
8579 ],
8580 ..Default::default()
8581 },
8582 autoclose_before: "})]".to_string(),
8583 ..Default::default()
8584 },
8585 Some(tree_sitter_rust::LANGUAGE.into()),
8586 );
8587 let language = Arc::new(language);
8588
8589 cx.language_registry().add(language.clone());
8590 cx.update_buffer(|buffer, cx| {
8591 buffer.set_language(Some(language), cx);
8592 });
8593
8594 // Ensure that signature_help is not called when no signature help is enabled.
8595 cx.set_state(
8596 &r#"
8597 fn main() {
8598 sampleˇ
8599 }
8600 "#
8601 .unindent(),
8602 );
8603 cx.update_editor(|editor, window, cx| {
8604 editor.handle_input("(", window, cx);
8605 });
8606 cx.assert_editor_state(
8607 &"
8608 fn main() {
8609 sample(ˇ)
8610 }
8611 "
8612 .unindent(),
8613 );
8614 cx.editor(|editor, _, _| {
8615 assert!(editor.signature_help_state.task().is_none());
8616 });
8617
8618 let mocked_response = lsp::SignatureHelp {
8619 signatures: vec![lsp::SignatureInformation {
8620 label: "fn sample(param1: u8, param2: u8)".to_string(),
8621 documentation: None,
8622 parameters: Some(vec![
8623 lsp::ParameterInformation {
8624 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8625 documentation: None,
8626 },
8627 lsp::ParameterInformation {
8628 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8629 documentation: None,
8630 },
8631 ]),
8632 active_parameter: None,
8633 }],
8634 active_signature: Some(0),
8635 active_parameter: Some(0),
8636 };
8637
8638 // Ensure that signature_help is called when enabled afte edits
8639 cx.update(|_, cx| {
8640 cx.update_global::<SettingsStore, _>(|settings, cx| {
8641 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8642 settings.auto_signature_help = Some(false);
8643 settings.show_signature_help_after_edits = Some(true);
8644 });
8645 });
8646 });
8647 cx.set_state(
8648 &r#"
8649 fn main() {
8650 sampleˇ
8651 }
8652 "#
8653 .unindent(),
8654 );
8655 cx.update_editor(|editor, window, cx| {
8656 editor.handle_input("(", window, cx);
8657 });
8658 cx.assert_editor_state(
8659 &"
8660 fn main() {
8661 sample(ˇ)
8662 }
8663 "
8664 .unindent(),
8665 );
8666 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8667 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8668 .await;
8669 cx.update_editor(|editor, _, _| {
8670 let signature_help_state = editor.signature_help_state.popover().cloned();
8671 assert!(signature_help_state.is_some());
8672 assert_eq!(
8673 signature_help_state.unwrap().label,
8674 "param1: u8, param2: u8"
8675 );
8676 editor.signature_help_state = SignatureHelpState::default();
8677 });
8678
8679 // Ensure that signature_help is called when auto signature help override is enabled
8680 cx.update(|_, cx| {
8681 cx.update_global::<SettingsStore, _>(|settings, cx| {
8682 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8683 settings.auto_signature_help = Some(true);
8684 settings.show_signature_help_after_edits = Some(false);
8685 });
8686 });
8687 });
8688 cx.set_state(
8689 &r#"
8690 fn main() {
8691 sampleˇ
8692 }
8693 "#
8694 .unindent(),
8695 );
8696 cx.update_editor(|editor, window, cx| {
8697 editor.handle_input("(", window, cx);
8698 });
8699 cx.assert_editor_state(
8700 &"
8701 fn main() {
8702 sample(ˇ)
8703 }
8704 "
8705 .unindent(),
8706 );
8707 handle_signature_help_request(&mut cx, mocked_response).await;
8708 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8709 .await;
8710 cx.editor(|editor, _, _| {
8711 let signature_help_state = editor.signature_help_state.popover().cloned();
8712 assert!(signature_help_state.is_some());
8713 assert_eq!(
8714 signature_help_state.unwrap().label,
8715 "param1: u8, param2: u8"
8716 );
8717 });
8718}
8719
8720#[gpui::test]
8721async fn test_signature_help(cx: &mut TestAppContext) {
8722 init_test(cx, |_| {});
8723 cx.update(|cx| {
8724 cx.update_global::<SettingsStore, _>(|settings, cx| {
8725 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8726 settings.auto_signature_help = Some(true);
8727 });
8728 });
8729 });
8730
8731 let mut cx = EditorLspTestContext::new_rust(
8732 lsp::ServerCapabilities {
8733 signature_help_provider: Some(lsp::SignatureHelpOptions {
8734 ..Default::default()
8735 }),
8736 ..Default::default()
8737 },
8738 cx,
8739 )
8740 .await;
8741
8742 // A test that directly calls `show_signature_help`
8743 cx.update_editor(|editor, window, cx| {
8744 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8745 });
8746
8747 let mocked_response = lsp::SignatureHelp {
8748 signatures: vec![lsp::SignatureInformation {
8749 label: "fn sample(param1: u8, param2: u8)".to_string(),
8750 documentation: None,
8751 parameters: Some(vec![
8752 lsp::ParameterInformation {
8753 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8754 documentation: None,
8755 },
8756 lsp::ParameterInformation {
8757 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8758 documentation: None,
8759 },
8760 ]),
8761 active_parameter: None,
8762 }],
8763 active_signature: Some(0),
8764 active_parameter: Some(0),
8765 };
8766 handle_signature_help_request(&mut cx, mocked_response).await;
8767
8768 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8769 .await;
8770
8771 cx.editor(|editor, _, _| {
8772 let signature_help_state = editor.signature_help_state.popover().cloned();
8773 assert!(signature_help_state.is_some());
8774 assert_eq!(
8775 signature_help_state.unwrap().label,
8776 "param1: u8, param2: u8"
8777 );
8778 });
8779
8780 // When exiting outside from inside the brackets, `signature_help` is closed.
8781 cx.set_state(indoc! {"
8782 fn main() {
8783 sample(ˇ);
8784 }
8785
8786 fn sample(param1: u8, param2: u8) {}
8787 "});
8788
8789 cx.update_editor(|editor, window, cx| {
8790 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8791 });
8792
8793 let mocked_response = lsp::SignatureHelp {
8794 signatures: Vec::new(),
8795 active_signature: None,
8796 active_parameter: None,
8797 };
8798 handle_signature_help_request(&mut cx, mocked_response).await;
8799
8800 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8801 .await;
8802
8803 cx.editor(|editor, _, _| {
8804 assert!(!editor.signature_help_state.is_shown());
8805 });
8806
8807 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8808 cx.set_state(indoc! {"
8809 fn main() {
8810 sample(ˇ);
8811 }
8812
8813 fn sample(param1: u8, param2: u8) {}
8814 "});
8815
8816 let mocked_response = lsp::SignatureHelp {
8817 signatures: vec![lsp::SignatureInformation {
8818 label: "fn sample(param1: u8, param2: u8)".to_string(),
8819 documentation: None,
8820 parameters: Some(vec![
8821 lsp::ParameterInformation {
8822 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8823 documentation: None,
8824 },
8825 lsp::ParameterInformation {
8826 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8827 documentation: None,
8828 },
8829 ]),
8830 active_parameter: None,
8831 }],
8832 active_signature: Some(0),
8833 active_parameter: Some(0),
8834 };
8835 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8836 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8837 .await;
8838 cx.editor(|editor, _, _| {
8839 assert!(editor.signature_help_state.is_shown());
8840 });
8841
8842 // Restore the popover with more parameter input
8843 cx.set_state(indoc! {"
8844 fn main() {
8845 sample(param1, param2ˇ);
8846 }
8847
8848 fn sample(param1: u8, param2: u8) {}
8849 "});
8850
8851 let mocked_response = lsp::SignatureHelp {
8852 signatures: vec![lsp::SignatureInformation {
8853 label: "fn sample(param1: u8, param2: u8)".to_string(),
8854 documentation: None,
8855 parameters: Some(vec![
8856 lsp::ParameterInformation {
8857 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8858 documentation: None,
8859 },
8860 lsp::ParameterInformation {
8861 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8862 documentation: None,
8863 },
8864 ]),
8865 active_parameter: None,
8866 }],
8867 active_signature: Some(0),
8868 active_parameter: Some(1),
8869 };
8870 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8871 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8872 .await;
8873
8874 // When selecting a range, the popover is gone.
8875 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8876 cx.update_editor(|editor, window, cx| {
8877 editor.change_selections(None, window, cx, |s| {
8878 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8879 })
8880 });
8881 cx.assert_editor_state(indoc! {"
8882 fn main() {
8883 sample(param1, «ˇparam2»);
8884 }
8885
8886 fn sample(param1: u8, param2: u8) {}
8887 "});
8888 cx.editor(|editor, _, _| {
8889 assert!(!editor.signature_help_state.is_shown());
8890 });
8891
8892 // When unselecting again, the popover is back if within the brackets.
8893 cx.update_editor(|editor, window, cx| {
8894 editor.change_selections(None, window, cx, |s| {
8895 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8896 })
8897 });
8898 cx.assert_editor_state(indoc! {"
8899 fn main() {
8900 sample(param1, ˇparam2);
8901 }
8902
8903 fn sample(param1: u8, param2: u8) {}
8904 "});
8905 handle_signature_help_request(&mut cx, mocked_response).await;
8906 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8907 .await;
8908 cx.editor(|editor, _, _| {
8909 assert!(editor.signature_help_state.is_shown());
8910 });
8911
8912 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8913 cx.update_editor(|editor, window, cx| {
8914 editor.change_selections(None, window, cx, |s| {
8915 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8916 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8917 })
8918 });
8919 cx.assert_editor_state(indoc! {"
8920 fn main() {
8921 sample(param1, ˇparam2);
8922 }
8923
8924 fn sample(param1: u8, param2: u8) {}
8925 "});
8926
8927 let mocked_response = lsp::SignatureHelp {
8928 signatures: vec![lsp::SignatureInformation {
8929 label: "fn sample(param1: u8, param2: u8)".to_string(),
8930 documentation: None,
8931 parameters: Some(vec![
8932 lsp::ParameterInformation {
8933 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8934 documentation: None,
8935 },
8936 lsp::ParameterInformation {
8937 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8938 documentation: None,
8939 },
8940 ]),
8941 active_parameter: None,
8942 }],
8943 active_signature: Some(0),
8944 active_parameter: Some(1),
8945 };
8946 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8947 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8948 .await;
8949 cx.update_editor(|editor, _, cx| {
8950 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8951 });
8952 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8953 .await;
8954 cx.update_editor(|editor, window, cx| {
8955 editor.change_selections(None, window, cx, |s| {
8956 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8957 })
8958 });
8959 cx.assert_editor_state(indoc! {"
8960 fn main() {
8961 sample(param1, «ˇparam2»);
8962 }
8963
8964 fn sample(param1: u8, param2: u8) {}
8965 "});
8966 cx.update_editor(|editor, window, cx| {
8967 editor.change_selections(None, window, cx, |s| {
8968 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8969 })
8970 });
8971 cx.assert_editor_state(indoc! {"
8972 fn main() {
8973 sample(param1, ˇparam2);
8974 }
8975
8976 fn sample(param1: u8, param2: u8) {}
8977 "});
8978 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8979 .await;
8980}
8981
8982#[gpui::test]
8983async fn test_completion(cx: &mut TestAppContext) {
8984 init_test(cx, |_| {});
8985
8986 let mut cx = EditorLspTestContext::new_rust(
8987 lsp::ServerCapabilities {
8988 completion_provider: Some(lsp::CompletionOptions {
8989 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8990 resolve_provider: Some(true),
8991 ..Default::default()
8992 }),
8993 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8994 ..Default::default()
8995 },
8996 cx,
8997 )
8998 .await;
8999 let counter = Arc::new(AtomicUsize::new(0));
9000
9001 cx.set_state(indoc! {"
9002 oneˇ
9003 two
9004 three
9005 "});
9006 cx.simulate_keystroke(".");
9007 handle_completion_request(
9008 &mut cx,
9009 indoc! {"
9010 one.|<>
9011 two
9012 three
9013 "},
9014 vec!["first_completion", "second_completion"],
9015 counter.clone(),
9016 )
9017 .await;
9018 cx.condition(|editor, _| editor.context_menu_visible())
9019 .await;
9020 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9021
9022 let _handler = handle_signature_help_request(
9023 &mut cx,
9024 lsp::SignatureHelp {
9025 signatures: vec![lsp::SignatureInformation {
9026 label: "test signature".to_string(),
9027 documentation: None,
9028 parameters: Some(vec![lsp::ParameterInformation {
9029 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9030 documentation: None,
9031 }]),
9032 active_parameter: None,
9033 }],
9034 active_signature: None,
9035 active_parameter: None,
9036 },
9037 );
9038 cx.update_editor(|editor, window, cx| {
9039 assert!(
9040 !editor.signature_help_state.is_shown(),
9041 "No signature help was called for"
9042 );
9043 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9044 });
9045 cx.run_until_parked();
9046 cx.update_editor(|editor, _, _| {
9047 assert!(
9048 !editor.signature_help_state.is_shown(),
9049 "No signature help should be shown when completions menu is open"
9050 );
9051 });
9052
9053 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9054 editor.context_menu_next(&Default::default(), window, cx);
9055 editor
9056 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9057 .unwrap()
9058 });
9059 cx.assert_editor_state(indoc! {"
9060 one.second_completionˇ
9061 two
9062 three
9063 "});
9064
9065 handle_resolve_completion_request(
9066 &mut cx,
9067 Some(vec![
9068 (
9069 //This overlaps with the primary completion edit which is
9070 //misbehavior from the LSP spec, test that we filter it out
9071 indoc! {"
9072 one.second_ˇcompletion
9073 two
9074 threeˇ
9075 "},
9076 "overlapping additional edit",
9077 ),
9078 (
9079 indoc! {"
9080 one.second_completion
9081 two
9082 threeˇ
9083 "},
9084 "\nadditional edit",
9085 ),
9086 ]),
9087 )
9088 .await;
9089 apply_additional_edits.await.unwrap();
9090 cx.assert_editor_state(indoc! {"
9091 one.second_completionˇ
9092 two
9093 three
9094 additional edit
9095 "});
9096
9097 cx.set_state(indoc! {"
9098 one.second_completion
9099 twoˇ
9100 threeˇ
9101 additional edit
9102 "});
9103 cx.simulate_keystroke(" ");
9104 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9105 cx.simulate_keystroke("s");
9106 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9107
9108 cx.assert_editor_state(indoc! {"
9109 one.second_completion
9110 two sˇ
9111 three sˇ
9112 additional edit
9113 "});
9114 handle_completion_request(
9115 &mut cx,
9116 indoc! {"
9117 one.second_completion
9118 two s
9119 three <s|>
9120 additional edit
9121 "},
9122 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9123 counter.clone(),
9124 )
9125 .await;
9126 cx.condition(|editor, _| editor.context_menu_visible())
9127 .await;
9128 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9129
9130 cx.simulate_keystroke("i");
9131
9132 handle_completion_request(
9133 &mut cx,
9134 indoc! {"
9135 one.second_completion
9136 two si
9137 three <si|>
9138 additional edit
9139 "},
9140 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9141 counter.clone(),
9142 )
9143 .await;
9144 cx.condition(|editor, _| editor.context_menu_visible())
9145 .await;
9146 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9147
9148 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9149 editor
9150 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9151 .unwrap()
9152 });
9153 cx.assert_editor_state(indoc! {"
9154 one.second_completion
9155 two sixth_completionˇ
9156 three sixth_completionˇ
9157 additional edit
9158 "});
9159
9160 apply_additional_edits.await.unwrap();
9161
9162 update_test_language_settings(&mut cx, |settings| {
9163 settings.defaults.show_completions_on_input = Some(false);
9164 });
9165 cx.set_state("editorˇ");
9166 cx.simulate_keystroke(".");
9167 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9168 cx.simulate_keystroke("c");
9169 cx.simulate_keystroke("l");
9170 cx.simulate_keystroke("o");
9171 cx.assert_editor_state("editor.cloˇ");
9172 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9173 cx.update_editor(|editor, window, cx| {
9174 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9175 });
9176 handle_completion_request(
9177 &mut cx,
9178 "editor.<clo|>",
9179 vec!["close", "clobber"],
9180 counter.clone(),
9181 )
9182 .await;
9183 cx.condition(|editor, _| editor.context_menu_visible())
9184 .await;
9185 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9186
9187 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9188 editor
9189 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9190 .unwrap()
9191 });
9192 cx.assert_editor_state("editor.closeˇ");
9193 handle_resolve_completion_request(&mut cx, None).await;
9194 apply_additional_edits.await.unwrap();
9195}
9196
9197#[gpui::test]
9198async fn test_multiline_completion(cx: &mut TestAppContext) {
9199 init_test(cx, |_| {});
9200
9201 let fs = FakeFs::new(cx.executor());
9202 fs.insert_tree(
9203 path!("/a"),
9204 json!({
9205 "main.ts": "a",
9206 }),
9207 )
9208 .await;
9209
9210 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9211 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9212 let typescript_language = Arc::new(Language::new(
9213 LanguageConfig {
9214 name: "TypeScript".into(),
9215 matcher: LanguageMatcher {
9216 path_suffixes: vec!["ts".to_string()],
9217 ..LanguageMatcher::default()
9218 },
9219 line_comments: vec!["// ".into()],
9220 ..LanguageConfig::default()
9221 },
9222 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9223 ));
9224 language_registry.add(typescript_language.clone());
9225 let mut fake_servers = language_registry.register_fake_lsp(
9226 "TypeScript",
9227 FakeLspAdapter {
9228 capabilities: lsp::ServerCapabilities {
9229 completion_provider: Some(lsp::CompletionOptions {
9230 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9231 ..lsp::CompletionOptions::default()
9232 }),
9233 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9234 ..lsp::ServerCapabilities::default()
9235 },
9236 // Emulate vtsls label generation
9237 label_for_completion: Some(Box::new(|item, _| {
9238 let text = if let Some(description) = item
9239 .label_details
9240 .as_ref()
9241 .and_then(|label_details| label_details.description.as_ref())
9242 {
9243 format!("{} {}", item.label, description)
9244 } else if let Some(detail) = &item.detail {
9245 format!("{} {}", item.label, detail)
9246 } else {
9247 item.label.clone()
9248 };
9249 let len = text.len();
9250 Some(language::CodeLabel {
9251 text,
9252 runs: Vec::new(),
9253 filter_range: 0..len,
9254 })
9255 })),
9256 ..FakeLspAdapter::default()
9257 },
9258 );
9259 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9260 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9261 let worktree_id = workspace
9262 .update(cx, |workspace, _window, cx| {
9263 workspace.project().update(cx, |project, cx| {
9264 project.worktrees(cx).next().unwrap().read(cx).id()
9265 })
9266 })
9267 .unwrap();
9268 let _buffer = project
9269 .update(cx, |project, cx| {
9270 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9271 })
9272 .await
9273 .unwrap();
9274 let editor = workspace
9275 .update(cx, |workspace, window, cx| {
9276 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9277 })
9278 .unwrap()
9279 .await
9280 .unwrap()
9281 .downcast::<Editor>()
9282 .unwrap();
9283 let fake_server = fake_servers.next().await.unwrap();
9284
9285 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9286 let multiline_label_2 = "a\nb\nc\n";
9287 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9288 let multiline_description = "d\ne\nf\n";
9289 let multiline_detail_2 = "g\nh\ni\n";
9290
9291 let mut completion_handle =
9292 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9293 Ok(Some(lsp::CompletionResponse::Array(vec![
9294 lsp::CompletionItem {
9295 label: multiline_label.to_string(),
9296 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9297 range: lsp::Range {
9298 start: lsp::Position {
9299 line: params.text_document_position.position.line,
9300 character: params.text_document_position.position.character,
9301 },
9302 end: lsp::Position {
9303 line: params.text_document_position.position.line,
9304 character: params.text_document_position.position.character,
9305 },
9306 },
9307 new_text: "new_text_1".to_string(),
9308 })),
9309 ..lsp::CompletionItem::default()
9310 },
9311 lsp::CompletionItem {
9312 label: "single line label 1".to_string(),
9313 detail: Some(multiline_detail.to_string()),
9314 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9315 range: lsp::Range {
9316 start: lsp::Position {
9317 line: params.text_document_position.position.line,
9318 character: params.text_document_position.position.character,
9319 },
9320 end: lsp::Position {
9321 line: params.text_document_position.position.line,
9322 character: params.text_document_position.position.character,
9323 },
9324 },
9325 new_text: "new_text_2".to_string(),
9326 })),
9327 ..lsp::CompletionItem::default()
9328 },
9329 lsp::CompletionItem {
9330 label: "single line label 2".to_string(),
9331 label_details: Some(lsp::CompletionItemLabelDetails {
9332 description: Some(multiline_description.to_string()),
9333 detail: None,
9334 }),
9335 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9336 range: lsp::Range {
9337 start: lsp::Position {
9338 line: params.text_document_position.position.line,
9339 character: params.text_document_position.position.character,
9340 },
9341 end: lsp::Position {
9342 line: params.text_document_position.position.line,
9343 character: params.text_document_position.position.character,
9344 },
9345 },
9346 new_text: "new_text_2".to_string(),
9347 })),
9348 ..lsp::CompletionItem::default()
9349 },
9350 lsp::CompletionItem {
9351 label: multiline_label_2.to_string(),
9352 detail: Some(multiline_detail_2.to_string()),
9353 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9354 range: lsp::Range {
9355 start: lsp::Position {
9356 line: params.text_document_position.position.line,
9357 character: params.text_document_position.position.character,
9358 },
9359 end: lsp::Position {
9360 line: params.text_document_position.position.line,
9361 character: params.text_document_position.position.character,
9362 },
9363 },
9364 new_text: "new_text_3".to_string(),
9365 })),
9366 ..lsp::CompletionItem::default()
9367 },
9368 lsp::CompletionItem {
9369 label: "Label with many spaces and \t but without newlines".to_string(),
9370 detail: Some(
9371 "Details with many spaces and \t but without newlines".to_string(),
9372 ),
9373 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9374 range: lsp::Range {
9375 start: lsp::Position {
9376 line: params.text_document_position.position.line,
9377 character: params.text_document_position.position.character,
9378 },
9379 end: lsp::Position {
9380 line: params.text_document_position.position.line,
9381 character: params.text_document_position.position.character,
9382 },
9383 },
9384 new_text: "new_text_4".to_string(),
9385 })),
9386 ..lsp::CompletionItem::default()
9387 },
9388 ])))
9389 });
9390
9391 editor.update_in(cx, |editor, window, cx| {
9392 cx.focus_self(window);
9393 editor.move_to_end(&MoveToEnd, window, cx);
9394 editor.handle_input(".", window, cx);
9395 });
9396 cx.run_until_parked();
9397 completion_handle.next().await.unwrap();
9398
9399 editor.update(cx, |editor, _| {
9400 assert!(editor.context_menu_visible());
9401 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9402 {
9403 let completion_labels = menu
9404 .completions
9405 .borrow()
9406 .iter()
9407 .map(|c| c.label.text.clone())
9408 .collect::<Vec<_>>();
9409 assert_eq!(
9410 completion_labels,
9411 &[
9412 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9413 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9414 "single line label 2 d e f ",
9415 "a b c g h i ",
9416 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9417 ],
9418 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9419 );
9420
9421 for completion in menu
9422 .completions
9423 .borrow()
9424 .iter() {
9425 assert_eq!(
9426 completion.label.filter_range,
9427 0..completion.label.text.len(),
9428 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9429 );
9430 }
9431
9432 } else {
9433 panic!("expected completion menu to be open");
9434 }
9435 });
9436}
9437
9438#[gpui::test]
9439async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9440 init_test(cx, |_| {});
9441 let mut cx = EditorLspTestContext::new_rust(
9442 lsp::ServerCapabilities {
9443 completion_provider: Some(lsp::CompletionOptions {
9444 trigger_characters: Some(vec![".".to_string()]),
9445 ..Default::default()
9446 }),
9447 ..Default::default()
9448 },
9449 cx,
9450 )
9451 .await;
9452 cx.lsp
9453 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9454 Ok(Some(lsp::CompletionResponse::Array(vec![
9455 lsp::CompletionItem {
9456 label: "first".into(),
9457 ..Default::default()
9458 },
9459 lsp::CompletionItem {
9460 label: "last".into(),
9461 ..Default::default()
9462 },
9463 ])))
9464 });
9465 cx.set_state("variableˇ");
9466 cx.simulate_keystroke(".");
9467 cx.executor().run_until_parked();
9468
9469 cx.update_editor(|editor, _, _| {
9470 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9471 {
9472 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9473 } else {
9474 panic!("expected completion menu to be open");
9475 }
9476 });
9477
9478 cx.update_editor(|editor, window, cx| {
9479 editor.move_page_down(&MovePageDown::default(), window, cx);
9480 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9481 {
9482 assert!(
9483 menu.selected_item == 1,
9484 "expected PageDown to select the last item from the context menu"
9485 );
9486 } else {
9487 panic!("expected completion menu to stay open after PageDown");
9488 }
9489 });
9490
9491 cx.update_editor(|editor, window, cx| {
9492 editor.move_page_up(&MovePageUp::default(), window, cx);
9493 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9494 {
9495 assert!(
9496 menu.selected_item == 0,
9497 "expected PageUp to select the first item from the context menu"
9498 );
9499 } else {
9500 panic!("expected completion menu to stay open after PageUp");
9501 }
9502 });
9503}
9504
9505#[gpui::test]
9506async fn test_completion_sort(cx: &mut TestAppContext) {
9507 init_test(cx, |_| {});
9508 let mut cx = EditorLspTestContext::new_rust(
9509 lsp::ServerCapabilities {
9510 completion_provider: Some(lsp::CompletionOptions {
9511 trigger_characters: Some(vec![".".to_string()]),
9512 ..Default::default()
9513 }),
9514 ..Default::default()
9515 },
9516 cx,
9517 )
9518 .await;
9519 cx.lsp
9520 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9521 Ok(Some(lsp::CompletionResponse::Array(vec![
9522 lsp::CompletionItem {
9523 label: "Range".into(),
9524 sort_text: Some("a".into()),
9525 ..Default::default()
9526 },
9527 lsp::CompletionItem {
9528 label: "r".into(),
9529 sort_text: Some("b".into()),
9530 ..Default::default()
9531 },
9532 lsp::CompletionItem {
9533 label: "ret".into(),
9534 sort_text: Some("c".into()),
9535 ..Default::default()
9536 },
9537 lsp::CompletionItem {
9538 label: "return".into(),
9539 sort_text: Some("d".into()),
9540 ..Default::default()
9541 },
9542 lsp::CompletionItem {
9543 label: "slice".into(),
9544 sort_text: Some("d".into()),
9545 ..Default::default()
9546 },
9547 ])))
9548 });
9549 cx.set_state("rˇ");
9550 cx.executor().run_until_parked();
9551 cx.update_editor(|editor, window, cx| {
9552 editor.show_completions(
9553 &ShowCompletions {
9554 trigger: Some("r".into()),
9555 },
9556 window,
9557 cx,
9558 );
9559 });
9560 cx.executor().run_until_parked();
9561
9562 cx.update_editor(|editor, _, _| {
9563 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9564 {
9565 assert_eq!(
9566 completion_menu_entries(&menu),
9567 &["r", "ret", "Range", "return"]
9568 );
9569 } else {
9570 panic!("expected completion menu to be open");
9571 }
9572 });
9573}
9574
9575#[gpui::test]
9576async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9577 init_test(cx, |_| {});
9578
9579 let mut cx = EditorLspTestContext::new_rust(
9580 lsp::ServerCapabilities {
9581 completion_provider: Some(lsp::CompletionOptions {
9582 trigger_characters: Some(vec![".".to_string()]),
9583 resolve_provider: Some(true),
9584 ..Default::default()
9585 }),
9586 ..Default::default()
9587 },
9588 cx,
9589 )
9590 .await;
9591
9592 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9593 cx.simulate_keystroke(".");
9594 let completion_item = lsp::CompletionItem {
9595 label: "Some".into(),
9596 kind: Some(lsp::CompletionItemKind::SNIPPET),
9597 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9598 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9599 kind: lsp::MarkupKind::Markdown,
9600 value: "```rust\nSome(2)\n```".to_string(),
9601 })),
9602 deprecated: Some(false),
9603 sort_text: Some("Some".to_string()),
9604 filter_text: Some("Some".to_string()),
9605 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9606 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9607 range: lsp::Range {
9608 start: lsp::Position {
9609 line: 0,
9610 character: 22,
9611 },
9612 end: lsp::Position {
9613 line: 0,
9614 character: 22,
9615 },
9616 },
9617 new_text: "Some(2)".to_string(),
9618 })),
9619 additional_text_edits: Some(vec![lsp::TextEdit {
9620 range: lsp::Range {
9621 start: lsp::Position {
9622 line: 0,
9623 character: 20,
9624 },
9625 end: lsp::Position {
9626 line: 0,
9627 character: 22,
9628 },
9629 },
9630 new_text: "".to_string(),
9631 }]),
9632 ..Default::default()
9633 };
9634
9635 let closure_completion_item = completion_item.clone();
9636 let counter = Arc::new(AtomicUsize::new(0));
9637 let counter_clone = counter.clone();
9638 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9639 let task_completion_item = closure_completion_item.clone();
9640 counter_clone.fetch_add(1, atomic::Ordering::Release);
9641 async move {
9642 Ok(Some(lsp::CompletionResponse::Array(vec![
9643 task_completion_item,
9644 ])))
9645 }
9646 });
9647
9648 cx.condition(|editor, _| editor.context_menu_visible())
9649 .await;
9650 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9651 assert!(request.next().await.is_some());
9652 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9653
9654 cx.simulate_keystroke("S");
9655 cx.simulate_keystroke("o");
9656 cx.simulate_keystroke("m");
9657 cx.condition(|editor, _| editor.context_menu_visible())
9658 .await;
9659 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9660 assert!(request.next().await.is_some());
9661 assert!(request.next().await.is_some());
9662 assert!(request.next().await.is_some());
9663 request.close();
9664 assert!(request.next().await.is_none());
9665 assert_eq!(
9666 counter.load(atomic::Ordering::Acquire),
9667 4,
9668 "With the completions menu open, only one LSP request should happen per input"
9669 );
9670}
9671
9672#[gpui::test]
9673async fn test_toggle_comment(cx: &mut TestAppContext) {
9674 init_test(cx, |_| {});
9675 let mut cx = EditorTestContext::new(cx).await;
9676 let language = Arc::new(Language::new(
9677 LanguageConfig {
9678 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9679 ..Default::default()
9680 },
9681 Some(tree_sitter_rust::LANGUAGE.into()),
9682 ));
9683 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9684
9685 // If multiple selections intersect a line, the line is only toggled once.
9686 cx.set_state(indoc! {"
9687 fn a() {
9688 «//b();
9689 ˇ»// «c();
9690 //ˇ» d();
9691 }
9692 "});
9693
9694 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9695
9696 cx.assert_editor_state(indoc! {"
9697 fn a() {
9698 «b();
9699 c();
9700 ˇ» d();
9701 }
9702 "});
9703
9704 // The comment prefix is inserted at the same column for every line in a
9705 // selection.
9706 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9707
9708 cx.assert_editor_state(indoc! {"
9709 fn a() {
9710 // «b();
9711 // c();
9712 ˇ»// d();
9713 }
9714 "});
9715
9716 // If a selection ends at the beginning of a line, that line is not toggled.
9717 cx.set_selections_state(indoc! {"
9718 fn a() {
9719 // b();
9720 «// c();
9721 ˇ» // d();
9722 }
9723 "});
9724
9725 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9726
9727 cx.assert_editor_state(indoc! {"
9728 fn a() {
9729 // b();
9730 «c();
9731 ˇ» // d();
9732 }
9733 "});
9734
9735 // If a selection span a single line and is empty, the line is toggled.
9736 cx.set_state(indoc! {"
9737 fn a() {
9738 a();
9739 b();
9740 ˇ
9741 }
9742 "});
9743
9744 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9745
9746 cx.assert_editor_state(indoc! {"
9747 fn a() {
9748 a();
9749 b();
9750 //•ˇ
9751 }
9752 "});
9753
9754 // If a selection span multiple lines, empty lines are not toggled.
9755 cx.set_state(indoc! {"
9756 fn a() {
9757 «a();
9758
9759 c();ˇ»
9760 }
9761 "});
9762
9763 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9764
9765 cx.assert_editor_state(indoc! {"
9766 fn a() {
9767 // «a();
9768
9769 // c();ˇ»
9770 }
9771 "});
9772
9773 // If a selection includes multiple comment prefixes, all lines are uncommented.
9774 cx.set_state(indoc! {"
9775 fn a() {
9776 «// a();
9777 /// b();
9778 //! c();ˇ»
9779 }
9780 "});
9781
9782 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9783
9784 cx.assert_editor_state(indoc! {"
9785 fn a() {
9786 «a();
9787 b();
9788 c();ˇ»
9789 }
9790 "});
9791}
9792
9793#[gpui::test]
9794async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9795 init_test(cx, |_| {});
9796 let mut cx = EditorTestContext::new(cx).await;
9797 let language = Arc::new(Language::new(
9798 LanguageConfig {
9799 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9800 ..Default::default()
9801 },
9802 Some(tree_sitter_rust::LANGUAGE.into()),
9803 ));
9804 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9805
9806 let toggle_comments = &ToggleComments {
9807 advance_downwards: false,
9808 ignore_indent: true,
9809 };
9810
9811 // If multiple selections intersect a line, the line is only toggled once.
9812 cx.set_state(indoc! {"
9813 fn a() {
9814 // «b();
9815 // c();
9816 // ˇ» d();
9817 }
9818 "});
9819
9820 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9821
9822 cx.assert_editor_state(indoc! {"
9823 fn a() {
9824 «b();
9825 c();
9826 ˇ» d();
9827 }
9828 "});
9829
9830 // The comment prefix is inserted at the beginning of each line
9831 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9832
9833 cx.assert_editor_state(indoc! {"
9834 fn a() {
9835 // «b();
9836 // c();
9837 // ˇ» d();
9838 }
9839 "});
9840
9841 // If a selection ends at the beginning of a line, that line is not toggled.
9842 cx.set_selections_state(indoc! {"
9843 fn a() {
9844 // b();
9845 // «c();
9846 ˇ»// d();
9847 }
9848 "});
9849
9850 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9851
9852 cx.assert_editor_state(indoc! {"
9853 fn a() {
9854 // b();
9855 «c();
9856 ˇ»// d();
9857 }
9858 "});
9859
9860 // If a selection span a single line and is empty, the line is toggled.
9861 cx.set_state(indoc! {"
9862 fn a() {
9863 a();
9864 b();
9865 ˇ
9866 }
9867 "});
9868
9869 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9870
9871 cx.assert_editor_state(indoc! {"
9872 fn a() {
9873 a();
9874 b();
9875 //ˇ
9876 }
9877 "});
9878
9879 // If a selection span multiple lines, empty lines are not toggled.
9880 cx.set_state(indoc! {"
9881 fn a() {
9882 «a();
9883
9884 c();ˇ»
9885 }
9886 "});
9887
9888 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9889
9890 cx.assert_editor_state(indoc! {"
9891 fn a() {
9892 // «a();
9893
9894 // c();ˇ»
9895 }
9896 "});
9897
9898 // If a selection includes multiple comment prefixes, all lines are uncommented.
9899 cx.set_state(indoc! {"
9900 fn a() {
9901 // «a();
9902 /// b();
9903 //! c();ˇ»
9904 }
9905 "});
9906
9907 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9908
9909 cx.assert_editor_state(indoc! {"
9910 fn a() {
9911 «a();
9912 b();
9913 c();ˇ»
9914 }
9915 "});
9916}
9917
9918#[gpui::test]
9919async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9920 init_test(cx, |_| {});
9921
9922 let language = Arc::new(Language::new(
9923 LanguageConfig {
9924 line_comments: vec!["// ".into()],
9925 ..Default::default()
9926 },
9927 Some(tree_sitter_rust::LANGUAGE.into()),
9928 ));
9929
9930 let mut cx = EditorTestContext::new(cx).await;
9931
9932 cx.language_registry().add(language.clone());
9933 cx.update_buffer(|buffer, cx| {
9934 buffer.set_language(Some(language), cx);
9935 });
9936
9937 let toggle_comments = &ToggleComments {
9938 advance_downwards: true,
9939 ignore_indent: false,
9940 };
9941
9942 // Single cursor on one line -> advance
9943 // Cursor moves horizontally 3 characters as well on non-blank line
9944 cx.set_state(indoc!(
9945 "fn a() {
9946 ˇdog();
9947 cat();
9948 }"
9949 ));
9950 cx.update_editor(|editor, window, cx| {
9951 editor.toggle_comments(toggle_comments, window, cx);
9952 });
9953 cx.assert_editor_state(indoc!(
9954 "fn a() {
9955 // dog();
9956 catˇ();
9957 }"
9958 ));
9959
9960 // Single selection on one line -> don't advance
9961 cx.set_state(indoc!(
9962 "fn a() {
9963 «dog()ˇ»;
9964 cat();
9965 }"
9966 ));
9967 cx.update_editor(|editor, window, cx| {
9968 editor.toggle_comments(toggle_comments, window, cx);
9969 });
9970 cx.assert_editor_state(indoc!(
9971 "fn a() {
9972 // «dog()ˇ»;
9973 cat();
9974 }"
9975 ));
9976
9977 // Multiple cursors on one line -> advance
9978 cx.set_state(indoc!(
9979 "fn a() {
9980 ˇdˇog();
9981 cat();
9982 }"
9983 ));
9984 cx.update_editor(|editor, window, cx| {
9985 editor.toggle_comments(toggle_comments, window, cx);
9986 });
9987 cx.assert_editor_state(indoc!(
9988 "fn a() {
9989 // dog();
9990 catˇ(ˇ);
9991 }"
9992 ));
9993
9994 // Multiple cursors on one line, with selection -> don't advance
9995 cx.set_state(indoc!(
9996 "fn a() {
9997 ˇdˇog«()ˇ»;
9998 cat();
9999 }"
10000 ));
10001 cx.update_editor(|editor, window, cx| {
10002 editor.toggle_comments(toggle_comments, window, cx);
10003 });
10004 cx.assert_editor_state(indoc!(
10005 "fn a() {
10006 // ˇdˇog«()ˇ»;
10007 cat();
10008 }"
10009 ));
10010
10011 // Single cursor on one line -> advance
10012 // Cursor moves to column 0 on blank line
10013 cx.set_state(indoc!(
10014 "fn a() {
10015 ˇdog();
10016
10017 cat();
10018 }"
10019 ));
10020 cx.update_editor(|editor, window, cx| {
10021 editor.toggle_comments(toggle_comments, window, cx);
10022 });
10023 cx.assert_editor_state(indoc!(
10024 "fn a() {
10025 // dog();
10026 ˇ
10027 cat();
10028 }"
10029 ));
10030
10031 // Single cursor on one line -> advance
10032 // Cursor starts and ends at column 0
10033 cx.set_state(indoc!(
10034 "fn a() {
10035 ˇ dog();
10036 cat();
10037 }"
10038 ));
10039 cx.update_editor(|editor, window, cx| {
10040 editor.toggle_comments(toggle_comments, window, cx);
10041 });
10042 cx.assert_editor_state(indoc!(
10043 "fn a() {
10044 // dog();
10045 ˇ cat();
10046 }"
10047 ));
10048}
10049
10050#[gpui::test]
10051async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10052 init_test(cx, |_| {});
10053
10054 let mut cx = EditorTestContext::new(cx).await;
10055
10056 let html_language = Arc::new(
10057 Language::new(
10058 LanguageConfig {
10059 name: "HTML".into(),
10060 block_comment: Some(("<!-- ".into(), " -->".into())),
10061 ..Default::default()
10062 },
10063 Some(tree_sitter_html::LANGUAGE.into()),
10064 )
10065 .with_injection_query(
10066 r#"
10067 (script_element
10068 (raw_text) @injection.content
10069 (#set! injection.language "javascript"))
10070 "#,
10071 )
10072 .unwrap(),
10073 );
10074
10075 let javascript_language = Arc::new(Language::new(
10076 LanguageConfig {
10077 name: "JavaScript".into(),
10078 line_comments: vec!["// ".into()],
10079 ..Default::default()
10080 },
10081 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10082 ));
10083
10084 cx.language_registry().add(html_language.clone());
10085 cx.language_registry().add(javascript_language.clone());
10086 cx.update_buffer(|buffer, cx| {
10087 buffer.set_language(Some(html_language), cx);
10088 });
10089
10090 // Toggle comments for empty selections
10091 cx.set_state(
10092 &r#"
10093 <p>A</p>ˇ
10094 <p>B</p>ˇ
10095 <p>C</p>ˇ
10096 "#
10097 .unindent(),
10098 );
10099 cx.update_editor(|editor, window, cx| {
10100 editor.toggle_comments(&ToggleComments::default(), window, cx)
10101 });
10102 cx.assert_editor_state(
10103 &r#"
10104 <!-- <p>A</p>ˇ -->
10105 <!-- <p>B</p>ˇ -->
10106 <!-- <p>C</p>ˇ -->
10107 "#
10108 .unindent(),
10109 );
10110 cx.update_editor(|editor, window, cx| {
10111 editor.toggle_comments(&ToggleComments::default(), window, cx)
10112 });
10113 cx.assert_editor_state(
10114 &r#"
10115 <p>A</p>ˇ
10116 <p>B</p>ˇ
10117 <p>C</p>ˇ
10118 "#
10119 .unindent(),
10120 );
10121
10122 // Toggle comments for mixture of empty and non-empty selections, where
10123 // multiple selections occupy a given line.
10124 cx.set_state(
10125 &r#"
10126 <p>A«</p>
10127 <p>ˇ»B</p>ˇ
10128 <p>C«</p>
10129 <p>ˇ»D</p>ˇ
10130 "#
10131 .unindent(),
10132 );
10133
10134 cx.update_editor(|editor, window, cx| {
10135 editor.toggle_comments(&ToggleComments::default(), window, cx)
10136 });
10137 cx.assert_editor_state(
10138 &r#"
10139 <!-- <p>A«</p>
10140 <p>ˇ»B</p>ˇ -->
10141 <!-- <p>C«</p>
10142 <p>ˇ»D</p>ˇ -->
10143 "#
10144 .unindent(),
10145 );
10146 cx.update_editor(|editor, window, cx| {
10147 editor.toggle_comments(&ToggleComments::default(), window, cx)
10148 });
10149 cx.assert_editor_state(
10150 &r#"
10151 <p>A«</p>
10152 <p>ˇ»B</p>ˇ
10153 <p>C«</p>
10154 <p>ˇ»D</p>ˇ
10155 "#
10156 .unindent(),
10157 );
10158
10159 // Toggle comments when different languages are active for different
10160 // selections.
10161 cx.set_state(
10162 &r#"
10163 ˇ<script>
10164 ˇvar x = new Y();
10165 ˇ</script>
10166 "#
10167 .unindent(),
10168 );
10169 cx.executor().run_until_parked();
10170 cx.update_editor(|editor, window, cx| {
10171 editor.toggle_comments(&ToggleComments::default(), window, cx)
10172 });
10173 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10174 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10175 cx.assert_editor_state(
10176 &r#"
10177 <!-- ˇ<script> -->
10178 // ˇvar x = new Y();
10179 <!-- ˇ</script> -->
10180 "#
10181 .unindent(),
10182 );
10183}
10184
10185#[gpui::test]
10186fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10187 init_test(cx, |_| {});
10188
10189 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10190 let multibuffer = cx.new(|cx| {
10191 let mut multibuffer = MultiBuffer::new(ReadWrite);
10192 multibuffer.push_excerpts(
10193 buffer.clone(),
10194 [
10195 ExcerptRange {
10196 context: Point::new(0, 0)..Point::new(0, 4),
10197 primary: None,
10198 },
10199 ExcerptRange {
10200 context: Point::new(1, 0)..Point::new(1, 4),
10201 primary: None,
10202 },
10203 ],
10204 cx,
10205 );
10206 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10207 multibuffer
10208 });
10209
10210 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10211 editor.update_in(cx, |editor, window, cx| {
10212 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10213 editor.change_selections(None, window, cx, |s| {
10214 s.select_ranges([
10215 Point::new(0, 0)..Point::new(0, 0),
10216 Point::new(1, 0)..Point::new(1, 0),
10217 ])
10218 });
10219
10220 editor.handle_input("X", window, cx);
10221 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10222 assert_eq!(
10223 editor.selections.ranges(cx),
10224 [
10225 Point::new(0, 1)..Point::new(0, 1),
10226 Point::new(1, 1)..Point::new(1, 1),
10227 ]
10228 );
10229
10230 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10231 editor.change_selections(None, window, cx, |s| {
10232 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10233 });
10234 editor.backspace(&Default::default(), window, cx);
10235 assert_eq!(editor.text(cx), "Xa\nbbb");
10236 assert_eq!(
10237 editor.selections.ranges(cx),
10238 [Point::new(1, 0)..Point::new(1, 0)]
10239 );
10240
10241 editor.change_selections(None, window, cx, |s| {
10242 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10243 });
10244 editor.backspace(&Default::default(), window, cx);
10245 assert_eq!(editor.text(cx), "X\nbb");
10246 assert_eq!(
10247 editor.selections.ranges(cx),
10248 [Point::new(0, 1)..Point::new(0, 1)]
10249 );
10250 });
10251}
10252
10253#[gpui::test]
10254fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10255 init_test(cx, |_| {});
10256
10257 let markers = vec![('[', ']').into(), ('(', ')').into()];
10258 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10259 indoc! {"
10260 [aaaa
10261 (bbbb]
10262 cccc)",
10263 },
10264 markers.clone(),
10265 );
10266 let excerpt_ranges = markers.into_iter().map(|marker| {
10267 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10268 ExcerptRange {
10269 context,
10270 primary: None,
10271 }
10272 });
10273 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10274 let multibuffer = cx.new(|cx| {
10275 let mut multibuffer = MultiBuffer::new(ReadWrite);
10276 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10277 multibuffer
10278 });
10279
10280 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10281 editor.update_in(cx, |editor, window, cx| {
10282 let (expected_text, selection_ranges) = marked_text_ranges(
10283 indoc! {"
10284 aaaa
10285 bˇbbb
10286 bˇbbˇb
10287 cccc"
10288 },
10289 true,
10290 );
10291 assert_eq!(editor.text(cx), expected_text);
10292 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10293
10294 editor.handle_input("X", window, cx);
10295
10296 let (expected_text, expected_selections) = marked_text_ranges(
10297 indoc! {"
10298 aaaa
10299 bXˇbbXb
10300 bXˇbbXˇb
10301 cccc"
10302 },
10303 false,
10304 );
10305 assert_eq!(editor.text(cx), expected_text);
10306 assert_eq!(editor.selections.ranges(cx), expected_selections);
10307
10308 editor.newline(&Newline, window, cx);
10309 let (expected_text, expected_selections) = marked_text_ranges(
10310 indoc! {"
10311 aaaa
10312 bX
10313 ˇbbX
10314 b
10315 bX
10316 ˇbbX
10317 ˇb
10318 cccc"
10319 },
10320 false,
10321 );
10322 assert_eq!(editor.text(cx), expected_text);
10323 assert_eq!(editor.selections.ranges(cx), expected_selections);
10324 });
10325}
10326
10327#[gpui::test]
10328fn test_refresh_selections(cx: &mut TestAppContext) {
10329 init_test(cx, |_| {});
10330
10331 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10332 let mut excerpt1_id = None;
10333 let multibuffer = cx.new(|cx| {
10334 let mut multibuffer = MultiBuffer::new(ReadWrite);
10335 excerpt1_id = multibuffer
10336 .push_excerpts(
10337 buffer.clone(),
10338 [
10339 ExcerptRange {
10340 context: Point::new(0, 0)..Point::new(1, 4),
10341 primary: None,
10342 },
10343 ExcerptRange {
10344 context: Point::new(1, 0)..Point::new(2, 4),
10345 primary: None,
10346 },
10347 ],
10348 cx,
10349 )
10350 .into_iter()
10351 .next();
10352 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10353 multibuffer
10354 });
10355
10356 let editor = cx.add_window(|window, cx| {
10357 let mut editor = build_editor(multibuffer.clone(), window, cx);
10358 let snapshot = editor.snapshot(window, cx);
10359 editor.change_selections(None, window, cx, |s| {
10360 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10361 });
10362 editor.begin_selection(
10363 Point::new(2, 1).to_display_point(&snapshot),
10364 true,
10365 1,
10366 window,
10367 cx,
10368 );
10369 assert_eq!(
10370 editor.selections.ranges(cx),
10371 [
10372 Point::new(1, 3)..Point::new(1, 3),
10373 Point::new(2, 1)..Point::new(2, 1),
10374 ]
10375 );
10376 editor
10377 });
10378
10379 // Refreshing selections is a no-op when excerpts haven't changed.
10380 _ = editor.update(cx, |editor, window, cx| {
10381 editor.change_selections(None, window, cx, |s| s.refresh());
10382 assert_eq!(
10383 editor.selections.ranges(cx),
10384 [
10385 Point::new(1, 3)..Point::new(1, 3),
10386 Point::new(2, 1)..Point::new(2, 1),
10387 ]
10388 );
10389 });
10390
10391 multibuffer.update(cx, |multibuffer, cx| {
10392 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10393 });
10394 _ = editor.update(cx, |editor, window, cx| {
10395 // Removing an excerpt causes the first selection to become degenerate.
10396 assert_eq!(
10397 editor.selections.ranges(cx),
10398 [
10399 Point::new(0, 0)..Point::new(0, 0),
10400 Point::new(0, 1)..Point::new(0, 1)
10401 ]
10402 );
10403
10404 // Refreshing selections will relocate the first selection to the original buffer
10405 // location.
10406 editor.change_selections(None, window, cx, |s| s.refresh());
10407 assert_eq!(
10408 editor.selections.ranges(cx),
10409 [
10410 Point::new(0, 1)..Point::new(0, 1),
10411 Point::new(0, 3)..Point::new(0, 3)
10412 ]
10413 );
10414 assert!(editor.selections.pending_anchor().is_some());
10415 });
10416}
10417
10418#[gpui::test]
10419fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10420 init_test(cx, |_| {});
10421
10422 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10423 let mut excerpt1_id = None;
10424 let multibuffer = cx.new(|cx| {
10425 let mut multibuffer = MultiBuffer::new(ReadWrite);
10426 excerpt1_id = multibuffer
10427 .push_excerpts(
10428 buffer.clone(),
10429 [
10430 ExcerptRange {
10431 context: Point::new(0, 0)..Point::new(1, 4),
10432 primary: None,
10433 },
10434 ExcerptRange {
10435 context: Point::new(1, 0)..Point::new(2, 4),
10436 primary: None,
10437 },
10438 ],
10439 cx,
10440 )
10441 .into_iter()
10442 .next();
10443 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10444 multibuffer
10445 });
10446
10447 let editor = cx.add_window(|window, cx| {
10448 let mut editor = build_editor(multibuffer.clone(), window, cx);
10449 let snapshot = editor.snapshot(window, cx);
10450 editor.begin_selection(
10451 Point::new(1, 3).to_display_point(&snapshot),
10452 false,
10453 1,
10454 window,
10455 cx,
10456 );
10457 assert_eq!(
10458 editor.selections.ranges(cx),
10459 [Point::new(1, 3)..Point::new(1, 3)]
10460 );
10461 editor
10462 });
10463
10464 multibuffer.update(cx, |multibuffer, cx| {
10465 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10466 });
10467 _ = editor.update(cx, |editor, window, cx| {
10468 assert_eq!(
10469 editor.selections.ranges(cx),
10470 [Point::new(0, 0)..Point::new(0, 0)]
10471 );
10472
10473 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10474 editor.change_selections(None, window, cx, |s| s.refresh());
10475 assert_eq!(
10476 editor.selections.ranges(cx),
10477 [Point::new(0, 3)..Point::new(0, 3)]
10478 );
10479 assert!(editor.selections.pending_anchor().is_some());
10480 });
10481}
10482
10483#[gpui::test]
10484async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10485 init_test(cx, |_| {});
10486
10487 let language = Arc::new(
10488 Language::new(
10489 LanguageConfig {
10490 brackets: BracketPairConfig {
10491 pairs: vec![
10492 BracketPair {
10493 start: "{".to_string(),
10494 end: "}".to_string(),
10495 close: true,
10496 surround: true,
10497 newline: true,
10498 },
10499 BracketPair {
10500 start: "/* ".to_string(),
10501 end: " */".to_string(),
10502 close: true,
10503 surround: true,
10504 newline: true,
10505 },
10506 ],
10507 ..Default::default()
10508 },
10509 ..Default::default()
10510 },
10511 Some(tree_sitter_rust::LANGUAGE.into()),
10512 )
10513 .with_indents_query("")
10514 .unwrap(),
10515 );
10516
10517 let text = concat!(
10518 "{ }\n", //
10519 " x\n", //
10520 " /* */\n", //
10521 "x\n", //
10522 "{{} }\n", //
10523 );
10524
10525 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10526 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10527 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10528 editor
10529 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10530 .await;
10531
10532 editor.update_in(cx, |editor, window, cx| {
10533 editor.change_selections(None, window, cx, |s| {
10534 s.select_display_ranges([
10535 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10536 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10537 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10538 ])
10539 });
10540 editor.newline(&Newline, window, cx);
10541
10542 assert_eq!(
10543 editor.buffer().read(cx).read(cx).text(),
10544 concat!(
10545 "{ \n", // Suppress rustfmt
10546 "\n", //
10547 "}\n", //
10548 " x\n", //
10549 " /* \n", //
10550 " \n", //
10551 " */\n", //
10552 "x\n", //
10553 "{{} \n", //
10554 "}\n", //
10555 )
10556 );
10557 });
10558}
10559
10560#[gpui::test]
10561fn test_highlighted_ranges(cx: &mut TestAppContext) {
10562 init_test(cx, |_| {});
10563
10564 let editor = cx.add_window(|window, cx| {
10565 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10566 build_editor(buffer.clone(), window, cx)
10567 });
10568
10569 _ = editor.update(cx, |editor, window, cx| {
10570 struct Type1;
10571 struct Type2;
10572
10573 let buffer = editor.buffer.read(cx).snapshot(cx);
10574
10575 let anchor_range =
10576 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10577
10578 editor.highlight_background::<Type1>(
10579 &[
10580 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10581 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10582 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10583 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10584 ],
10585 |_| Hsla::red(),
10586 cx,
10587 );
10588 editor.highlight_background::<Type2>(
10589 &[
10590 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10591 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10592 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10593 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10594 ],
10595 |_| Hsla::green(),
10596 cx,
10597 );
10598
10599 let snapshot = editor.snapshot(window, cx);
10600 let mut highlighted_ranges = editor.background_highlights_in_range(
10601 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10602 &snapshot,
10603 cx.theme().colors(),
10604 );
10605 // Enforce a consistent ordering based on color without relying on the ordering of the
10606 // highlight's `TypeId` which is non-executor.
10607 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10608 assert_eq!(
10609 highlighted_ranges,
10610 &[
10611 (
10612 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10613 Hsla::red(),
10614 ),
10615 (
10616 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10617 Hsla::red(),
10618 ),
10619 (
10620 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10621 Hsla::green(),
10622 ),
10623 (
10624 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10625 Hsla::green(),
10626 ),
10627 ]
10628 );
10629 assert_eq!(
10630 editor.background_highlights_in_range(
10631 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10632 &snapshot,
10633 cx.theme().colors(),
10634 ),
10635 &[(
10636 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10637 Hsla::red(),
10638 )]
10639 );
10640 });
10641}
10642
10643#[gpui::test]
10644async fn test_following(cx: &mut TestAppContext) {
10645 init_test(cx, |_| {});
10646
10647 let fs = FakeFs::new(cx.executor());
10648 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10649
10650 let buffer = project.update(cx, |project, cx| {
10651 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10652 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10653 });
10654 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10655 let follower = cx.update(|cx| {
10656 cx.open_window(
10657 WindowOptions {
10658 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10659 gpui::Point::new(px(0.), px(0.)),
10660 gpui::Point::new(px(10.), px(80.)),
10661 ))),
10662 ..Default::default()
10663 },
10664 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10665 )
10666 .unwrap()
10667 });
10668
10669 let is_still_following = Rc::new(RefCell::new(true));
10670 let follower_edit_event_count = Rc::new(RefCell::new(0));
10671 let pending_update = Rc::new(RefCell::new(None));
10672 let leader_entity = leader.root(cx).unwrap();
10673 let follower_entity = follower.root(cx).unwrap();
10674 _ = follower.update(cx, {
10675 let update = pending_update.clone();
10676 let is_still_following = is_still_following.clone();
10677 let follower_edit_event_count = follower_edit_event_count.clone();
10678 |_, window, cx| {
10679 cx.subscribe_in(
10680 &leader_entity,
10681 window,
10682 move |_, leader, event, window, cx| {
10683 leader.read(cx).add_event_to_update_proto(
10684 event,
10685 &mut update.borrow_mut(),
10686 window,
10687 cx,
10688 );
10689 },
10690 )
10691 .detach();
10692
10693 cx.subscribe_in(
10694 &follower_entity,
10695 window,
10696 move |_, _, event: &EditorEvent, _window, _cx| {
10697 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10698 *is_still_following.borrow_mut() = false;
10699 }
10700
10701 if let EditorEvent::BufferEdited = event {
10702 *follower_edit_event_count.borrow_mut() += 1;
10703 }
10704 },
10705 )
10706 .detach();
10707 }
10708 });
10709
10710 // Update the selections only
10711 _ = leader.update(cx, |leader, window, cx| {
10712 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10713 });
10714 follower
10715 .update(cx, |follower, window, cx| {
10716 follower.apply_update_proto(
10717 &project,
10718 pending_update.borrow_mut().take().unwrap(),
10719 window,
10720 cx,
10721 )
10722 })
10723 .unwrap()
10724 .await
10725 .unwrap();
10726 _ = follower.update(cx, |follower, _, cx| {
10727 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10728 });
10729 assert!(*is_still_following.borrow());
10730 assert_eq!(*follower_edit_event_count.borrow(), 0);
10731
10732 // Update the scroll position only
10733 _ = leader.update(cx, |leader, window, cx| {
10734 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10735 });
10736 follower
10737 .update(cx, |follower, window, cx| {
10738 follower.apply_update_proto(
10739 &project,
10740 pending_update.borrow_mut().take().unwrap(),
10741 window,
10742 cx,
10743 )
10744 })
10745 .unwrap()
10746 .await
10747 .unwrap();
10748 assert_eq!(
10749 follower
10750 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10751 .unwrap(),
10752 gpui::Point::new(1.5, 3.5)
10753 );
10754 assert!(*is_still_following.borrow());
10755 assert_eq!(*follower_edit_event_count.borrow(), 0);
10756
10757 // Update the selections and scroll position. The follower's scroll position is updated
10758 // via autoscroll, not via the leader's exact scroll position.
10759 _ = leader.update(cx, |leader, window, cx| {
10760 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10761 leader.request_autoscroll(Autoscroll::newest(), cx);
10762 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10763 });
10764 follower
10765 .update(cx, |follower, window, cx| {
10766 follower.apply_update_proto(
10767 &project,
10768 pending_update.borrow_mut().take().unwrap(),
10769 window,
10770 cx,
10771 )
10772 })
10773 .unwrap()
10774 .await
10775 .unwrap();
10776 _ = follower.update(cx, |follower, _, cx| {
10777 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10778 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10779 });
10780 assert!(*is_still_following.borrow());
10781
10782 // Creating a pending selection that precedes another selection
10783 _ = leader.update(cx, |leader, window, cx| {
10784 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10785 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10786 });
10787 follower
10788 .update(cx, |follower, window, cx| {
10789 follower.apply_update_proto(
10790 &project,
10791 pending_update.borrow_mut().take().unwrap(),
10792 window,
10793 cx,
10794 )
10795 })
10796 .unwrap()
10797 .await
10798 .unwrap();
10799 _ = follower.update(cx, |follower, _, cx| {
10800 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10801 });
10802 assert!(*is_still_following.borrow());
10803
10804 // Extend the pending selection so that it surrounds another selection
10805 _ = leader.update(cx, |leader, window, cx| {
10806 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10807 });
10808 follower
10809 .update(cx, |follower, window, cx| {
10810 follower.apply_update_proto(
10811 &project,
10812 pending_update.borrow_mut().take().unwrap(),
10813 window,
10814 cx,
10815 )
10816 })
10817 .unwrap()
10818 .await
10819 .unwrap();
10820 _ = follower.update(cx, |follower, _, cx| {
10821 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10822 });
10823
10824 // Scrolling locally breaks the follow
10825 _ = follower.update(cx, |follower, window, cx| {
10826 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10827 follower.set_scroll_anchor(
10828 ScrollAnchor {
10829 anchor: top_anchor,
10830 offset: gpui::Point::new(0.0, 0.5),
10831 },
10832 window,
10833 cx,
10834 );
10835 });
10836 assert!(!(*is_still_following.borrow()));
10837}
10838
10839#[gpui::test]
10840async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10841 init_test(cx, |_| {});
10842
10843 let fs = FakeFs::new(cx.executor());
10844 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10845 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10846 let pane = workspace
10847 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10848 .unwrap();
10849
10850 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10851
10852 let leader = pane.update_in(cx, |_, window, cx| {
10853 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10854 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10855 });
10856
10857 // Start following the editor when it has no excerpts.
10858 let mut state_message =
10859 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10860 let workspace_entity = workspace.root(cx).unwrap();
10861 let follower_1 = cx
10862 .update_window(*workspace.deref(), |_, window, cx| {
10863 Editor::from_state_proto(
10864 workspace_entity,
10865 ViewId {
10866 creator: Default::default(),
10867 id: 0,
10868 },
10869 &mut state_message,
10870 window,
10871 cx,
10872 )
10873 })
10874 .unwrap()
10875 .unwrap()
10876 .await
10877 .unwrap();
10878
10879 let update_message = Rc::new(RefCell::new(None));
10880 follower_1.update_in(cx, {
10881 let update = update_message.clone();
10882 |_, window, cx| {
10883 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10884 leader.read(cx).add_event_to_update_proto(
10885 event,
10886 &mut update.borrow_mut(),
10887 window,
10888 cx,
10889 );
10890 })
10891 .detach();
10892 }
10893 });
10894
10895 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10896 (
10897 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10898 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10899 )
10900 });
10901
10902 // Insert some excerpts.
10903 leader.update(cx, |leader, cx| {
10904 leader.buffer.update(cx, |multibuffer, cx| {
10905 let excerpt_ids = multibuffer.push_excerpts(
10906 buffer_1.clone(),
10907 [
10908 ExcerptRange {
10909 context: 1..6,
10910 primary: None,
10911 },
10912 ExcerptRange {
10913 context: 12..15,
10914 primary: None,
10915 },
10916 ExcerptRange {
10917 context: 0..3,
10918 primary: None,
10919 },
10920 ],
10921 cx,
10922 );
10923 multibuffer.insert_excerpts_after(
10924 excerpt_ids[0],
10925 buffer_2.clone(),
10926 [
10927 ExcerptRange {
10928 context: 8..12,
10929 primary: None,
10930 },
10931 ExcerptRange {
10932 context: 0..6,
10933 primary: None,
10934 },
10935 ],
10936 cx,
10937 );
10938 });
10939 });
10940
10941 // Apply the update of adding the excerpts.
10942 follower_1
10943 .update_in(cx, |follower, window, cx| {
10944 follower.apply_update_proto(
10945 &project,
10946 update_message.borrow().clone().unwrap(),
10947 window,
10948 cx,
10949 )
10950 })
10951 .await
10952 .unwrap();
10953 assert_eq!(
10954 follower_1.update(cx, |editor, cx| editor.text(cx)),
10955 leader.update(cx, |editor, cx| editor.text(cx))
10956 );
10957 update_message.borrow_mut().take();
10958
10959 // Start following separately after it already has excerpts.
10960 let mut state_message =
10961 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10962 let workspace_entity = workspace.root(cx).unwrap();
10963 let follower_2 = cx
10964 .update_window(*workspace.deref(), |_, window, cx| {
10965 Editor::from_state_proto(
10966 workspace_entity,
10967 ViewId {
10968 creator: Default::default(),
10969 id: 0,
10970 },
10971 &mut state_message,
10972 window,
10973 cx,
10974 )
10975 })
10976 .unwrap()
10977 .unwrap()
10978 .await
10979 .unwrap();
10980 assert_eq!(
10981 follower_2.update(cx, |editor, cx| editor.text(cx)),
10982 leader.update(cx, |editor, cx| editor.text(cx))
10983 );
10984
10985 // Remove some excerpts.
10986 leader.update(cx, |leader, cx| {
10987 leader.buffer.update(cx, |multibuffer, cx| {
10988 let excerpt_ids = multibuffer.excerpt_ids();
10989 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10990 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10991 });
10992 });
10993
10994 // Apply the update of removing the excerpts.
10995 follower_1
10996 .update_in(cx, |follower, window, cx| {
10997 follower.apply_update_proto(
10998 &project,
10999 update_message.borrow().clone().unwrap(),
11000 window,
11001 cx,
11002 )
11003 })
11004 .await
11005 .unwrap();
11006 follower_2
11007 .update_in(cx, |follower, window, cx| {
11008 follower.apply_update_proto(
11009 &project,
11010 update_message.borrow().clone().unwrap(),
11011 window,
11012 cx,
11013 )
11014 })
11015 .await
11016 .unwrap();
11017 update_message.borrow_mut().take();
11018 assert_eq!(
11019 follower_1.update(cx, |editor, cx| editor.text(cx)),
11020 leader.update(cx, |editor, cx| editor.text(cx))
11021 );
11022}
11023
11024#[gpui::test]
11025async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11026 init_test(cx, |_| {});
11027
11028 let mut cx = EditorTestContext::new(cx).await;
11029 let lsp_store =
11030 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11031
11032 cx.set_state(indoc! {"
11033 ˇfn func(abc def: i32) -> u32 {
11034 }
11035 "});
11036
11037 cx.update(|_, cx| {
11038 lsp_store.update(cx, |lsp_store, cx| {
11039 lsp_store
11040 .update_diagnostics(
11041 LanguageServerId(0),
11042 lsp::PublishDiagnosticsParams {
11043 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11044 version: None,
11045 diagnostics: vec![
11046 lsp::Diagnostic {
11047 range: lsp::Range::new(
11048 lsp::Position::new(0, 11),
11049 lsp::Position::new(0, 12),
11050 ),
11051 severity: Some(lsp::DiagnosticSeverity::ERROR),
11052 ..Default::default()
11053 },
11054 lsp::Diagnostic {
11055 range: lsp::Range::new(
11056 lsp::Position::new(0, 12),
11057 lsp::Position::new(0, 15),
11058 ),
11059 severity: Some(lsp::DiagnosticSeverity::ERROR),
11060 ..Default::default()
11061 },
11062 lsp::Diagnostic {
11063 range: lsp::Range::new(
11064 lsp::Position::new(0, 25),
11065 lsp::Position::new(0, 28),
11066 ),
11067 severity: Some(lsp::DiagnosticSeverity::ERROR),
11068 ..Default::default()
11069 },
11070 ],
11071 },
11072 &[],
11073 cx,
11074 )
11075 .unwrap()
11076 });
11077 });
11078
11079 executor.run_until_parked();
11080
11081 cx.update_editor(|editor, window, cx| {
11082 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11083 });
11084
11085 cx.assert_editor_state(indoc! {"
11086 fn func(abc def: i32) -> ˇu32 {
11087 }
11088 "});
11089
11090 cx.update_editor(|editor, window, cx| {
11091 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11092 });
11093
11094 cx.assert_editor_state(indoc! {"
11095 fn func(abc ˇdef: i32) -> u32 {
11096 }
11097 "});
11098
11099 cx.update_editor(|editor, window, cx| {
11100 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11101 });
11102
11103 cx.assert_editor_state(indoc! {"
11104 fn func(abcˇ def: i32) -> u32 {
11105 }
11106 "});
11107
11108 cx.update_editor(|editor, window, cx| {
11109 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11110 });
11111
11112 cx.assert_editor_state(indoc! {"
11113 fn func(abc def: i32) -> ˇu32 {
11114 }
11115 "});
11116}
11117
11118#[gpui::test]
11119async fn cycle_through_same_place_diagnostics(
11120 executor: BackgroundExecutor,
11121 cx: &mut TestAppContext,
11122) {
11123 init_test(cx, |_| {});
11124
11125 let mut cx = EditorTestContext::new(cx).await;
11126 let lsp_store =
11127 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11128
11129 cx.set_state(indoc! {"
11130 ˇfn func(abc def: i32) -> u32 {
11131 }
11132 "});
11133
11134 cx.update(|_, cx| {
11135 lsp_store.update(cx, |lsp_store, cx| {
11136 lsp_store
11137 .update_diagnostics(
11138 LanguageServerId(0),
11139 lsp::PublishDiagnosticsParams {
11140 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11141 version: None,
11142 diagnostics: vec![
11143 lsp::Diagnostic {
11144 range: lsp::Range::new(
11145 lsp::Position::new(0, 11),
11146 lsp::Position::new(0, 12),
11147 ),
11148 severity: Some(lsp::DiagnosticSeverity::ERROR),
11149 ..Default::default()
11150 },
11151 lsp::Diagnostic {
11152 range: lsp::Range::new(
11153 lsp::Position::new(0, 12),
11154 lsp::Position::new(0, 15),
11155 ),
11156 severity: Some(lsp::DiagnosticSeverity::ERROR),
11157 ..Default::default()
11158 },
11159 lsp::Diagnostic {
11160 range: lsp::Range::new(
11161 lsp::Position::new(0, 12),
11162 lsp::Position::new(0, 15),
11163 ),
11164 severity: Some(lsp::DiagnosticSeverity::ERROR),
11165 ..Default::default()
11166 },
11167 lsp::Diagnostic {
11168 range: lsp::Range::new(
11169 lsp::Position::new(0, 25),
11170 lsp::Position::new(0, 28),
11171 ),
11172 severity: Some(lsp::DiagnosticSeverity::ERROR),
11173 ..Default::default()
11174 },
11175 ],
11176 },
11177 &[],
11178 cx,
11179 )
11180 .unwrap()
11181 });
11182 });
11183 executor.run_until_parked();
11184
11185 //// Backward
11186
11187 // Fourth diagnostic
11188 cx.update_editor(|editor, window, cx| {
11189 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11190 });
11191 cx.assert_editor_state(indoc! {"
11192 fn func(abc def: i32) -> ˇu32 {
11193 }
11194 "});
11195
11196 // Third diagnostic
11197 cx.update_editor(|editor, window, cx| {
11198 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11199 });
11200 cx.assert_editor_state(indoc! {"
11201 fn func(abc ˇdef: i32) -> u32 {
11202 }
11203 "});
11204
11205 // Second diagnostic, same place
11206 cx.update_editor(|editor, window, cx| {
11207 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11208 });
11209 cx.assert_editor_state(indoc! {"
11210 fn func(abc ˇdef: i32) -> u32 {
11211 }
11212 "});
11213
11214 // First diagnostic
11215 cx.update_editor(|editor, window, cx| {
11216 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11217 });
11218 cx.assert_editor_state(indoc! {"
11219 fn func(abcˇ def: i32) -> u32 {
11220 }
11221 "});
11222
11223 // Wrapped over, fourth diagnostic
11224 cx.update_editor(|editor, window, cx| {
11225 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11226 });
11227 cx.assert_editor_state(indoc! {"
11228 fn func(abc def: i32) -> ˇu32 {
11229 }
11230 "});
11231
11232 cx.update_editor(|editor, window, cx| {
11233 editor.move_to_beginning(&MoveToBeginning, window, cx);
11234 });
11235 cx.assert_editor_state(indoc! {"
11236 ˇfn func(abc def: i32) -> u32 {
11237 }
11238 "});
11239
11240 //// Forward
11241
11242 // First diagnostic
11243 cx.update_editor(|editor, window, cx| {
11244 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11245 });
11246 cx.assert_editor_state(indoc! {"
11247 fn func(abcˇ def: i32) -> u32 {
11248 }
11249 "});
11250
11251 // Second diagnostic
11252 cx.update_editor(|editor, window, cx| {
11253 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11254 });
11255 cx.assert_editor_state(indoc! {"
11256 fn func(abc ˇdef: i32) -> u32 {
11257 }
11258 "});
11259
11260 // Third diagnostic, same place
11261 cx.update_editor(|editor, window, cx| {
11262 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11263 });
11264 cx.assert_editor_state(indoc! {"
11265 fn func(abc ˇdef: i32) -> u32 {
11266 }
11267 "});
11268
11269 // Fourth diagnostic
11270 cx.update_editor(|editor, window, cx| {
11271 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11272 });
11273 cx.assert_editor_state(indoc! {"
11274 fn func(abc def: i32) -> ˇu32 {
11275 }
11276 "});
11277
11278 // Wrapped around, first diagnostic
11279 cx.update_editor(|editor, window, cx| {
11280 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11281 });
11282 cx.assert_editor_state(indoc! {"
11283 fn func(abcˇ def: i32) -> u32 {
11284 }
11285 "});
11286}
11287
11288#[gpui::test]
11289async fn active_diagnostics_dismiss_after_invalidation(
11290 executor: BackgroundExecutor,
11291 cx: &mut TestAppContext,
11292) {
11293 init_test(cx, |_| {});
11294
11295 let mut cx = EditorTestContext::new(cx).await;
11296 let lsp_store =
11297 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11298
11299 cx.set_state(indoc! {"
11300 ˇfn func(abc def: i32) -> u32 {
11301 }
11302 "});
11303
11304 let message = "Something's wrong!";
11305 cx.update(|_, cx| {
11306 lsp_store.update(cx, |lsp_store, cx| {
11307 lsp_store
11308 .update_diagnostics(
11309 LanguageServerId(0),
11310 lsp::PublishDiagnosticsParams {
11311 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11312 version: None,
11313 diagnostics: vec![lsp::Diagnostic {
11314 range: lsp::Range::new(
11315 lsp::Position::new(0, 11),
11316 lsp::Position::new(0, 12),
11317 ),
11318 severity: Some(lsp::DiagnosticSeverity::ERROR),
11319 message: message.to_string(),
11320 ..Default::default()
11321 }],
11322 },
11323 &[],
11324 cx,
11325 )
11326 .unwrap()
11327 });
11328 });
11329 executor.run_until_parked();
11330
11331 cx.update_editor(|editor, window, cx| {
11332 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11333 assert_eq!(
11334 editor
11335 .active_diagnostics
11336 .as_ref()
11337 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11338 Some(message),
11339 "Should have a diagnostics group activated"
11340 );
11341 });
11342 cx.assert_editor_state(indoc! {"
11343 fn func(abcˇ def: i32) -> u32 {
11344 }
11345 "});
11346
11347 cx.update(|_, cx| {
11348 lsp_store.update(cx, |lsp_store, cx| {
11349 lsp_store
11350 .update_diagnostics(
11351 LanguageServerId(0),
11352 lsp::PublishDiagnosticsParams {
11353 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11354 version: None,
11355 diagnostics: Vec::new(),
11356 },
11357 &[],
11358 cx,
11359 )
11360 .unwrap()
11361 });
11362 });
11363 executor.run_until_parked();
11364 cx.update_editor(|editor, _, _| {
11365 assert_eq!(
11366 editor.active_diagnostics, None,
11367 "After no diagnostics set to the editor, no diagnostics should be active"
11368 );
11369 });
11370 cx.assert_editor_state(indoc! {"
11371 fn func(abcˇ def: i32) -> u32 {
11372 }
11373 "});
11374
11375 cx.update_editor(|editor, window, cx| {
11376 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11377 assert_eq!(
11378 editor.active_diagnostics, None,
11379 "Should be no diagnostics to go to and activate"
11380 );
11381 });
11382 cx.assert_editor_state(indoc! {"
11383 fn func(abcˇ def: i32) -> u32 {
11384 }
11385 "});
11386}
11387
11388#[gpui::test]
11389async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11390 init_test(cx, |_| {});
11391
11392 let mut cx = EditorTestContext::new(cx).await;
11393
11394 cx.set_state(indoc! {"
11395 fn func(abˇc def: i32) -> u32 {
11396 }
11397 "});
11398 let lsp_store =
11399 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11400
11401 cx.update(|_, cx| {
11402 lsp_store.update(cx, |lsp_store, cx| {
11403 lsp_store.update_diagnostics(
11404 LanguageServerId(0),
11405 lsp::PublishDiagnosticsParams {
11406 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11407 version: None,
11408 diagnostics: vec![lsp::Diagnostic {
11409 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11410 severity: Some(lsp::DiagnosticSeverity::ERROR),
11411 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11412 ..Default::default()
11413 }],
11414 },
11415 &[],
11416 cx,
11417 )
11418 })
11419 }).unwrap();
11420 cx.run_until_parked();
11421 cx.update_editor(|editor, window, cx| {
11422 hover_popover::hover(editor, &Default::default(), window, cx)
11423 });
11424 cx.run_until_parked();
11425 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11426}
11427
11428#[gpui::test]
11429async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11430 init_test(cx, |_| {});
11431
11432 let mut cx = EditorTestContext::new(cx).await;
11433
11434 let diff_base = r#"
11435 use some::mod;
11436
11437 const A: u32 = 42;
11438
11439 fn main() {
11440 println!("hello");
11441
11442 println!("world");
11443 }
11444 "#
11445 .unindent();
11446
11447 // Edits are modified, removed, modified, added
11448 cx.set_state(
11449 &r#"
11450 use some::modified;
11451
11452 ˇ
11453 fn main() {
11454 println!("hello there");
11455
11456 println!("around the");
11457 println!("world");
11458 }
11459 "#
11460 .unindent(),
11461 );
11462
11463 cx.set_head_text(&diff_base);
11464 executor.run_until_parked();
11465
11466 cx.update_editor(|editor, window, cx| {
11467 //Wrap around the bottom of the buffer
11468 for _ in 0..3 {
11469 editor.go_to_next_hunk(&GoToHunk, window, cx);
11470 }
11471 });
11472
11473 cx.assert_editor_state(
11474 &r#"
11475 ˇuse some::modified;
11476
11477
11478 fn main() {
11479 println!("hello there");
11480
11481 println!("around the");
11482 println!("world");
11483 }
11484 "#
11485 .unindent(),
11486 );
11487
11488 cx.update_editor(|editor, window, cx| {
11489 //Wrap around the top of the buffer
11490 for _ in 0..2 {
11491 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11492 }
11493 });
11494
11495 cx.assert_editor_state(
11496 &r#"
11497 use some::modified;
11498
11499
11500 fn main() {
11501 ˇ println!("hello there");
11502
11503 println!("around the");
11504 println!("world");
11505 }
11506 "#
11507 .unindent(),
11508 );
11509
11510 cx.update_editor(|editor, window, cx| {
11511 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11512 });
11513
11514 cx.assert_editor_state(
11515 &r#"
11516 use some::modified;
11517
11518 ˇ
11519 fn main() {
11520 println!("hello there");
11521
11522 println!("around the");
11523 println!("world");
11524 }
11525 "#
11526 .unindent(),
11527 );
11528
11529 cx.update_editor(|editor, window, cx| {
11530 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11531 });
11532
11533 cx.assert_editor_state(
11534 &r#"
11535 ˇuse some::modified;
11536
11537
11538 fn main() {
11539 println!("hello there");
11540
11541 println!("around the");
11542 println!("world");
11543 }
11544 "#
11545 .unindent(),
11546 );
11547
11548 cx.update_editor(|editor, window, cx| {
11549 for _ in 0..2 {
11550 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11551 }
11552 });
11553
11554 cx.assert_editor_state(
11555 &r#"
11556 use some::modified;
11557
11558
11559 fn main() {
11560 ˇ println!("hello there");
11561
11562 println!("around the");
11563 println!("world");
11564 }
11565 "#
11566 .unindent(),
11567 );
11568
11569 cx.update_editor(|editor, window, cx| {
11570 editor.fold(&Fold, window, cx);
11571 });
11572
11573 cx.update_editor(|editor, window, cx| {
11574 editor.go_to_next_hunk(&GoToHunk, window, cx);
11575 });
11576
11577 cx.assert_editor_state(
11578 &r#"
11579 ˇuse some::modified;
11580
11581
11582 fn main() {
11583 println!("hello there");
11584
11585 println!("around the");
11586 println!("world");
11587 }
11588 "#
11589 .unindent(),
11590 );
11591}
11592
11593#[test]
11594fn test_split_words() {
11595 fn split(text: &str) -> Vec<&str> {
11596 split_words(text).collect()
11597 }
11598
11599 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11600 assert_eq!(split("hello_world"), &["hello_", "world"]);
11601 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11602 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11603 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11604 assert_eq!(split("helloworld"), &["helloworld"]);
11605
11606 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11607}
11608
11609#[gpui::test]
11610async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11611 init_test(cx, |_| {});
11612
11613 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11614 let mut assert = |before, after| {
11615 let _state_context = cx.set_state(before);
11616 cx.run_until_parked();
11617 cx.update_editor(|editor, window, cx| {
11618 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11619 });
11620 cx.assert_editor_state(after);
11621 };
11622
11623 // Outside bracket jumps to outside of matching bracket
11624 assert("console.logˇ(var);", "console.log(var)ˇ;");
11625 assert("console.log(var)ˇ;", "console.logˇ(var);");
11626
11627 // Inside bracket jumps to inside of matching bracket
11628 assert("console.log(ˇvar);", "console.log(varˇ);");
11629 assert("console.log(varˇ);", "console.log(ˇvar);");
11630
11631 // When outside a bracket and inside, favor jumping to the inside bracket
11632 assert(
11633 "console.log('foo', [1, 2, 3]ˇ);",
11634 "console.log(ˇ'foo', [1, 2, 3]);",
11635 );
11636 assert(
11637 "console.log(ˇ'foo', [1, 2, 3]);",
11638 "console.log('foo', [1, 2, 3]ˇ);",
11639 );
11640
11641 // Bias forward if two options are equally likely
11642 assert(
11643 "let result = curried_fun()ˇ();",
11644 "let result = curried_fun()()ˇ;",
11645 );
11646
11647 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11648 assert(
11649 indoc! {"
11650 function test() {
11651 console.log('test')ˇ
11652 }"},
11653 indoc! {"
11654 function test() {
11655 console.logˇ('test')
11656 }"},
11657 );
11658}
11659
11660#[gpui::test]
11661async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11662 init_test(cx, |_| {});
11663
11664 let fs = FakeFs::new(cx.executor());
11665 fs.insert_tree(
11666 path!("/a"),
11667 json!({
11668 "main.rs": "fn main() { let a = 5; }",
11669 "other.rs": "// Test file",
11670 }),
11671 )
11672 .await;
11673 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11674
11675 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11676 language_registry.add(Arc::new(Language::new(
11677 LanguageConfig {
11678 name: "Rust".into(),
11679 matcher: LanguageMatcher {
11680 path_suffixes: vec!["rs".to_string()],
11681 ..Default::default()
11682 },
11683 brackets: BracketPairConfig {
11684 pairs: vec![BracketPair {
11685 start: "{".to_string(),
11686 end: "}".to_string(),
11687 close: true,
11688 surround: true,
11689 newline: true,
11690 }],
11691 disabled_scopes_by_bracket_ix: Vec::new(),
11692 },
11693 ..Default::default()
11694 },
11695 Some(tree_sitter_rust::LANGUAGE.into()),
11696 )));
11697 let mut fake_servers = language_registry.register_fake_lsp(
11698 "Rust",
11699 FakeLspAdapter {
11700 capabilities: lsp::ServerCapabilities {
11701 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11702 first_trigger_character: "{".to_string(),
11703 more_trigger_character: None,
11704 }),
11705 ..Default::default()
11706 },
11707 ..Default::default()
11708 },
11709 );
11710
11711 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11712
11713 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11714
11715 let worktree_id = workspace
11716 .update(cx, |workspace, _, cx| {
11717 workspace.project().update(cx, |project, cx| {
11718 project.worktrees(cx).next().unwrap().read(cx).id()
11719 })
11720 })
11721 .unwrap();
11722
11723 let buffer = project
11724 .update(cx, |project, cx| {
11725 project.open_local_buffer(path!("/a/main.rs"), cx)
11726 })
11727 .await
11728 .unwrap();
11729 let editor_handle = workspace
11730 .update(cx, |workspace, window, cx| {
11731 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11732 })
11733 .unwrap()
11734 .await
11735 .unwrap()
11736 .downcast::<Editor>()
11737 .unwrap();
11738
11739 cx.executor().start_waiting();
11740 let fake_server = fake_servers.next().await.unwrap();
11741
11742 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11743 assert_eq!(
11744 params.text_document_position.text_document.uri,
11745 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11746 );
11747 assert_eq!(
11748 params.text_document_position.position,
11749 lsp::Position::new(0, 21),
11750 );
11751
11752 Ok(Some(vec![lsp::TextEdit {
11753 new_text: "]".to_string(),
11754 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11755 }]))
11756 });
11757
11758 editor_handle.update_in(cx, |editor, window, cx| {
11759 window.focus(&editor.focus_handle(cx));
11760 editor.change_selections(None, window, cx, |s| {
11761 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11762 });
11763 editor.handle_input("{", window, cx);
11764 });
11765
11766 cx.executor().run_until_parked();
11767
11768 buffer.update(cx, |buffer, _| {
11769 assert_eq!(
11770 buffer.text(),
11771 "fn main() { let a = {5}; }",
11772 "No extra braces from on type formatting should appear in the buffer"
11773 )
11774 });
11775}
11776
11777#[gpui::test]
11778async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11779 init_test(cx, |_| {});
11780
11781 let fs = FakeFs::new(cx.executor());
11782 fs.insert_tree(
11783 path!("/a"),
11784 json!({
11785 "main.rs": "fn main() { let a = 5; }",
11786 "other.rs": "// Test file",
11787 }),
11788 )
11789 .await;
11790
11791 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11792
11793 let server_restarts = Arc::new(AtomicUsize::new(0));
11794 let closure_restarts = Arc::clone(&server_restarts);
11795 let language_server_name = "test language server";
11796 let language_name: LanguageName = "Rust".into();
11797
11798 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11799 language_registry.add(Arc::new(Language::new(
11800 LanguageConfig {
11801 name: language_name.clone(),
11802 matcher: LanguageMatcher {
11803 path_suffixes: vec!["rs".to_string()],
11804 ..Default::default()
11805 },
11806 ..Default::default()
11807 },
11808 Some(tree_sitter_rust::LANGUAGE.into()),
11809 )));
11810 let mut fake_servers = language_registry.register_fake_lsp(
11811 "Rust",
11812 FakeLspAdapter {
11813 name: language_server_name,
11814 initialization_options: Some(json!({
11815 "testOptionValue": true
11816 })),
11817 initializer: Some(Box::new(move |fake_server| {
11818 let task_restarts = Arc::clone(&closure_restarts);
11819 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11820 task_restarts.fetch_add(1, atomic::Ordering::Release);
11821 futures::future::ready(Ok(()))
11822 });
11823 })),
11824 ..Default::default()
11825 },
11826 );
11827
11828 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11829 let _buffer = project
11830 .update(cx, |project, cx| {
11831 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11832 })
11833 .await
11834 .unwrap();
11835 let _fake_server = fake_servers.next().await.unwrap();
11836 update_test_language_settings(cx, |language_settings| {
11837 language_settings.languages.insert(
11838 language_name.clone(),
11839 LanguageSettingsContent {
11840 tab_size: NonZeroU32::new(8),
11841 ..Default::default()
11842 },
11843 );
11844 });
11845 cx.executor().run_until_parked();
11846 assert_eq!(
11847 server_restarts.load(atomic::Ordering::Acquire),
11848 0,
11849 "Should not restart LSP server on an unrelated change"
11850 );
11851
11852 update_test_project_settings(cx, |project_settings| {
11853 project_settings.lsp.insert(
11854 "Some other server name".into(),
11855 LspSettings {
11856 binary: None,
11857 settings: None,
11858 initialization_options: Some(json!({
11859 "some other init value": false
11860 })),
11861 },
11862 );
11863 });
11864 cx.executor().run_until_parked();
11865 assert_eq!(
11866 server_restarts.load(atomic::Ordering::Acquire),
11867 0,
11868 "Should not restart LSP server on an unrelated LSP settings change"
11869 );
11870
11871 update_test_project_settings(cx, |project_settings| {
11872 project_settings.lsp.insert(
11873 language_server_name.into(),
11874 LspSettings {
11875 binary: None,
11876 settings: None,
11877 initialization_options: Some(json!({
11878 "anotherInitValue": false
11879 })),
11880 },
11881 );
11882 });
11883 cx.executor().run_until_parked();
11884 assert_eq!(
11885 server_restarts.load(atomic::Ordering::Acquire),
11886 1,
11887 "Should restart LSP server on a related LSP settings change"
11888 );
11889
11890 update_test_project_settings(cx, |project_settings| {
11891 project_settings.lsp.insert(
11892 language_server_name.into(),
11893 LspSettings {
11894 binary: None,
11895 settings: None,
11896 initialization_options: Some(json!({
11897 "anotherInitValue": false
11898 })),
11899 },
11900 );
11901 });
11902 cx.executor().run_until_parked();
11903 assert_eq!(
11904 server_restarts.load(atomic::Ordering::Acquire),
11905 1,
11906 "Should not restart LSP server on a related LSP settings change that is the same"
11907 );
11908
11909 update_test_project_settings(cx, |project_settings| {
11910 project_settings.lsp.insert(
11911 language_server_name.into(),
11912 LspSettings {
11913 binary: None,
11914 settings: None,
11915 initialization_options: None,
11916 },
11917 );
11918 });
11919 cx.executor().run_until_parked();
11920 assert_eq!(
11921 server_restarts.load(atomic::Ordering::Acquire),
11922 2,
11923 "Should restart LSP server on another related LSP settings change"
11924 );
11925}
11926
11927#[gpui::test]
11928async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11929 init_test(cx, |_| {});
11930
11931 let mut cx = EditorLspTestContext::new_rust(
11932 lsp::ServerCapabilities {
11933 completion_provider: Some(lsp::CompletionOptions {
11934 trigger_characters: Some(vec![".".to_string()]),
11935 resolve_provider: Some(true),
11936 ..Default::default()
11937 }),
11938 ..Default::default()
11939 },
11940 cx,
11941 )
11942 .await;
11943
11944 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11945 cx.simulate_keystroke(".");
11946 let completion_item = lsp::CompletionItem {
11947 label: "some".into(),
11948 kind: Some(lsp::CompletionItemKind::SNIPPET),
11949 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11950 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11951 kind: lsp::MarkupKind::Markdown,
11952 value: "```rust\nSome(2)\n```".to_string(),
11953 })),
11954 deprecated: Some(false),
11955 sort_text: Some("fffffff2".to_string()),
11956 filter_text: Some("some".to_string()),
11957 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11958 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11959 range: lsp::Range {
11960 start: lsp::Position {
11961 line: 0,
11962 character: 22,
11963 },
11964 end: lsp::Position {
11965 line: 0,
11966 character: 22,
11967 },
11968 },
11969 new_text: "Some(2)".to_string(),
11970 })),
11971 additional_text_edits: Some(vec![lsp::TextEdit {
11972 range: lsp::Range {
11973 start: lsp::Position {
11974 line: 0,
11975 character: 20,
11976 },
11977 end: lsp::Position {
11978 line: 0,
11979 character: 22,
11980 },
11981 },
11982 new_text: "".to_string(),
11983 }]),
11984 ..Default::default()
11985 };
11986
11987 let closure_completion_item = completion_item.clone();
11988 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11989 let task_completion_item = closure_completion_item.clone();
11990 async move {
11991 Ok(Some(lsp::CompletionResponse::Array(vec![
11992 task_completion_item,
11993 ])))
11994 }
11995 });
11996
11997 request.next().await;
11998
11999 cx.condition(|editor, _| editor.context_menu_visible())
12000 .await;
12001 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12002 editor
12003 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12004 .unwrap()
12005 });
12006 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12007
12008 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12009 let task_completion_item = completion_item.clone();
12010 async move { Ok(task_completion_item) }
12011 })
12012 .next()
12013 .await
12014 .unwrap();
12015 apply_additional_edits.await.unwrap();
12016 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12017}
12018
12019#[gpui::test]
12020async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12021 init_test(cx, |_| {});
12022
12023 let mut cx = EditorLspTestContext::new_rust(
12024 lsp::ServerCapabilities {
12025 completion_provider: Some(lsp::CompletionOptions {
12026 trigger_characters: Some(vec![".".to_string()]),
12027 resolve_provider: Some(true),
12028 ..Default::default()
12029 }),
12030 ..Default::default()
12031 },
12032 cx,
12033 )
12034 .await;
12035
12036 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12037 cx.simulate_keystroke(".");
12038
12039 let item1 = lsp::CompletionItem {
12040 label: "method id()".to_string(),
12041 filter_text: Some("id".to_string()),
12042 detail: None,
12043 documentation: None,
12044 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12045 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12046 new_text: ".id".to_string(),
12047 })),
12048 ..lsp::CompletionItem::default()
12049 };
12050
12051 let item2 = lsp::CompletionItem {
12052 label: "other".to_string(),
12053 filter_text: Some("other".to_string()),
12054 detail: None,
12055 documentation: None,
12056 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12057 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12058 new_text: ".other".to_string(),
12059 })),
12060 ..lsp::CompletionItem::default()
12061 };
12062
12063 let item1 = item1.clone();
12064 cx.handle_request::<lsp::request::Completion, _, _>({
12065 let item1 = item1.clone();
12066 move |_, _, _| {
12067 let item1 = item1.clone();
12068 let item2 = item2.clone();
12069 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12070 }
12071 })
12072 .next()
12073 .await;
12074
12075 cx.condition(|editor, _| editor.context_menu_visible())
12076 .await;
12077 cx.update_editor(|editor, _, _| {
12078 let context_menu = editor.context_menu.borrow_mut();
12079 let context_menu = context_menu
12080 .as_ref()
12081 .expect("Should have the context menu deployed");
12082 match context_menu {
12083 CodeContextMenu::Completions(completions_menu) => {
12084 let completions = completions_menu.completions.borrow_mut();
12085 assert_eq!(
12086 completions
12087 .iter()
12088 .map(|completion| &completion.label.text)
12089 .collect::<Vec<_>>(),
12090 vec!["method id()", "other"]
12091 )
12092 }
12093 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12094 }
12095 });
12096
12097 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12098 let item1 = item1.clone();
12099 move |_, item_to_resolve, _| {
12100 let item1 = item1.clone();
12101 async move {
12102 if item1 == item_to_resolve {
12103 Ok(lsp::CompletionItem {
12104 label: "method id()".to_string(),
12105 filter_text: Some("id".to_string()),
12106 detail: Some("Now resolved!".to_string()),
12107 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12108 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12109 range: lsp::Range::new(
12110 lsp::Position::new(0, 22),
12111 lsp::Position::new(0, 22),
12112 ),
12113 new_text: ".id".to_string(),
12114 })),
12115 ..lsp::CompletionItem::default()
12116 })
12117 } else {
12118 Ok(item_to_resolve)
12119 }
12120 }
12121 }
12122 })
12123 .next()
12124 .await
12125 .unwrap();
12126 cx.run_until_parked();
12127
12128 cx.update_editor(|editor, window, cx| {
12129 editor.context_menu_next(&Default::default(), window, cx);
12130 });
12131
12132 cx.update_editor(|editor, _, _| {
12133 let context_menu = editor.context_menu.borrow_mut();
12134 let context_menu = context_menu
12135 .as_ref()
12136 .expect("Should have the context menu deployed");
12137 match context_menu {
12138 CodeContextMenu::Completions(completions_menu) => {
12139 let completions = completions_menu.completions.borrow_mut();
12140 assert_eq!(
12141 completions
12142 .iter()
12143 .map(|completion| &completion.label.text)
12144 .collect::<Vec<_>>(),
12145 vec!["method id() Now resolved!", "other"],
12146 "Should update first completion label, but not second as the filter text did not match."
12147 );
12148 }
12149 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12150 }
12151 });
12152}
12153
12154#[gpui::test]
12155async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12156 init_test(cx, |_| {});
12157
12158 let mut cx = EditorLspTestContext::new_rust(
12159 lsp::ServerCapabilities {
12160 completion_provider: Some(lsp::CompletionOptions {
12161 trigger_characters: Some(vec![".".to_string()]),
12162 resolve_provider: Some(true),
12163 ..Default::default()
12164 }),
12165 ..Default::default()
12166 },
12167 cx,
12168 )
12169 .await;
12170
12171 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12172 cx.simulate_keystroke(".");
12173
12174 let unresolved_item_1 = lsp::CompletionItem {
12175 label: "id".to_string(),
12176 filter_text: Some("id".to_string()),
12177 detail: None,
12178 documentation: None,
12179 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12180 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12181 new_text: ".id".to_string(),
12182 })),
12183 ..lsp::CompletionItem::default()
12184 };
12185 let resolved_item_1 = lsp::CompletionItem {
12186 additional_text_edits: Some(vec![lsp::TextEdit {
12187 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12188 new_text: "!!".to_string(),
12189 }]),
12190 ..unresolved_item_1.clone()
12191 };
12192 let unresolved_item_2 = lsp::CompletionItem {
12193 label: "other".to_string(),
12194 filter_text: Some("other".to_string()),
12195 detail: None,
12196 documentation: None,
12197 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12198 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12199 new_text: ".other".to_string(),
12200 })),
12201 ..lsp::CompletionItem::default()
12202 };
12203 let resolved_item_2 = lsp::CompletionItem {
12204 additional_text_edits: Some(vec![lsp::TextEdit {
12205 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12206 new_text: "??".to_string(),
12207 }]),
12208 ..unresolved_item_2.clone()
12209 };
12210
12211 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12212 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12213 cx.lsp
12214 .server
12215 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12216 let unresolved_item_1 = unresolved_item_1.clone();
12217 let resolved_item_1 = resolved_item_1.clone();
12218 let unresolved_item_2 = unresolved_item_2.clone();
12219 let resolved_item_2 = resolved_item_2.clone();
12220 let resolve_requests_1 = resolve_requests_1.clone();
12221 let resolve_requests_2 = resolve_requests_2.clone();
12222 move |unresolved_request, _| {
12223 let unresolved_item_1 = unresolved_item_1.clone();
12224 let resolved_item_1 = resolved_item_1.clone();
12225 let unresolved_item_2 = unresolved_item_2.clone();
12226 let resolved_item_2 = resolved_item_2.clone();
12227 let resolve_requests_1 = resolve_requests_1.clone();
12228 let resolve_requests_2 = resolve_requests_2.clone();
12229 async move {
12230 if unresolved_request == unresolved_item_1 {
12231 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12232 Ok(resolved_item_1.clone())
12233 } else if unresolved_request == unresolved_item_2 {
12234 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12235 Ok(resolved_item_2.clone())
12236 } else {
12237 panic!("Unexpected completion item {unresolved_request:?}")
12238 }
12239 }
12240 }
12241 })
12242 .detach();
12243
12244 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12245 let unresolved_item_1 = unresolved_item_1.clone();
12246 let unresolved_item_2 = unresolved_item_2.clone();
12247 async move {
12248 Ok(Some(lsp::CompletionResponse::Array(vec![
12249 unresolved_item_1,
12250 unresolved_item_2,
12251 ])))
12252 }
12253 })
12254 .next()
12255 .await;
12256
12257 cx.condition(|editor, _| editor.context_menu_visible())
12258 .await;
12259 cx.update_editor(|editor, _, _| {
12260 let context_menu = editor.context_menu.borrow_mut();
12261 let context_menu = context_menu
12262 .as_ref()
12263 .expect("Should have the context menu deployed");
12264 match context_menu {
12265 CodeContextMenu::Completions(completions_menu) => {
12266 let completions = completions_menu.completions.borrow_mut();
12267 assert_eq!(
12268 completions
12269 .iter()
12270 .map(|completion| &completion.label.text)
12271 .collect::<Vec<_>>(),
12272 vec!["id", "other"]
12273 )
12274 }
12275 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12276 }
12277 });
12278 cx.run_until_parked();
12279
12280 cx.update_editor(|editor, window, cx| {
12281 editor.context_menu_next(&ContextMenuNext, window, cx);
12282 });
12283 cx.run_until_parked();
12284 cx.update_editor(|editor, window, cx| {
12285 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12286 });
12287 cx.run_until_parked();
12288 cx.update_editor(|editor, window, cx| {
12289 editor.context_menu_next(&ContextMenuNext, window, cx);
12290 });
12291 cx.run_until_parked();
12292 cx.update_editor(|editor, window, cx| {
12293 editor
12294 .compose_completion(&ComposeCompletion::default(), window, cx)
12295 .expect("No task returned")
12296 })
12297 .await
12298 .expect("Completion failed");
12299 cx.run_until_parked();
12300
12301 cx.update_editor(|editor, _, cx| {
12302 assert_eq!(
12303 resolve_requests_1.load(atomic::Ordering::Acquire),
12304 1,
12305 "Should always resolve once despite multiple selections"
12306 );
12307 assert_eq!(
12308 resolve_requests_2.load(atomic::Ordering::Acquire),
12309 1,
12310 "Should always resolve once after multiple selections and applying the completion"
12311 );
12312 assert_eq!(
12313 editor.text(cx),
12314 "fn main() { let a = ??.other; }",
12315 "Should use resolved data when applying the completion"
12316 );
12317 });
12318}
12319
12320#[gpui::test]
12321async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12322 init_test(cx, |_| {});
12323
12324 let item_0 = lsp::CompletionItem {
12325 label: "abs".into(),
12326 insert_text: Some("abs".into()),
12327 data: Some(json!({ "very": "special"})),
12328 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12329 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12330 lsp::InsertReplaceEdit {
12331 new_text: "abs".to_string(),
12332 insert: lsp::Range::default(),
12333 replace: lsp::Range::default(),
12334 },
12335 )),
12336 ..lsp::CompletionItem::default()
12337 };
12338 let items = iter::once(item_0.clone())
12339 .chain((11..51).map(|i| lsp::CompletionItem {
12340 label: format!("item_{}", i),
12341 insert_text: Some(format!("item_{}", i)),
12342 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12343 ..lsp::CompletionItem::default()
12344 }))
12345 .collect::<Vec<_>>();
12346
12347 let default_commit_characters = vec!["?".to_string()];
12348 let default_data = json!({ "default": "data"});
12349 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12350 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12351 let default_edit_range = lsp::Range {
12352 start: lsp::Position {
12353 line: 0,
12354 character: 5,
12355 },
12356 end: lsp::Position {
12357 line: 0,
12358 character: 5,
12359 },
12360 };
12361
12362 let mut cx = EditorLspTestContext::new_rust(
12363 lsp::ServerCapabilities {
12364 completion_provider: Some(lsp::CompletionOptions {
12365 trigger_characters: Some(vec![".".to_string()]),
12366 resolve_provider: Some(true),
12367 ..Default::default()
12368 }),
12369 ..Default::default()
12370 },
12371 cx,
12372 )
12373 .await;
12374
12375 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12376 cx.simulate_keystroke(".");
12377
12378 let completion_data = default_data.clone();
12379 let completion_characters = default_commit_characters.clone();
12380 let completion_items = items.clone();
12381 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12382 let default_data = completion_data.clone();
12383 let default_commit_characters = completion_characters.clone();
12384 let items = completion_items.clone();
12385 async move {
12386 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12387 items,
12388 item_defaults: Some(lsp::CompletionListItemDefaults {
12389 data: Some(default_data.clone()),
12390 commit_characters: Some(default_commit_characters.clone()),
12391 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12392 default_edit_range,
12393 )),
12394 insert_text_format: Some(default_insert_text_format),
12395 insert_text_mode: Some(default_insert_text_mode),
12396 }),
12397 ..lsp::CompletionList::default()
12398 })))
12399 }
12400 })
12401 .next()
12402 .await;
12403
12404 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12405 cx.lsp
12406 .server
12407 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12408 let closure_resolved_items = resolved_items.clone();
12409 move |item_to_resolve, _| {
12410 let closure_resolved_items = closure_resolved_items.clone();
12411 async move {
12412 closure_resolved_items.lock().push(item_to_resolve.clone());
12413 Ok(item_to_resolve)
12414 }
12415 }
12416 })
12417 .detach();
12418
12419 cx.condition(|editor, _| editor.context_menu_visible())
12420 .await;
12421 cx.run_until_parked();
12422 cx.update_editor(|editor, _, _| {
12423 let menu = editor.context_menu.borrow_mut();
12424 match menu.as_ref().expect("should have the completions menu") {
12425 CodeContextMenu::Completions(completions_menu) => {
12426 assert_eq!(
12427 completions_menu
12428 .entries
12429 .borrow()
12430 .iter()
12431 .map(|mat| mat.string.clone())
12432 .collect::<Vec<String>>(),
12433 items
12434 .iter()
12435 .map(|completion| completion.label.clone())
12436 .collect::<Vec<String>>()
12437 );
12438 }
12439 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12440 }
12441 });
12442 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12443 // with 4 from the end.
12444 assert_eq!(
12445 *resolved_items.lock(),
12446 [&items[0..16], &items[items.len() - 4..items.len()]]
12447 .concat()
12448 .iter()
12449 .cloned()
12450 .map(|mut item| {
12451 if item.data.is_none() {
12452 item.data = Some(default_data.clone());
12453 }
12454 item
12455 })
12456 .collect::<Vec<lsp::CompletionItem>>(),
12457 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12458 );
12459 resolved_items.lock().clear();
12460
12461 cx.update_editor(|editor, window, cx| {
12462 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12463 });
12464 cx.run_until_parked();
12465 // Completions that have already been resolved are skipped.
12466 assert_eq!(
12467 *resolved_items.lock(),
12468 items[items.len() - 16..items.len() - 4]
12469 .iter()
12470 .cloned()
12471 .map(|mut item| {
12472 if item.data.is_none() {
12473 item.data = Some(default_data.clone());
12474 }
12475 item
12476 })
12477 .collect::<Vec<lsp::CompletionItem>>()
12478 );
12479 resolved_items.lock().clear();
12480}
12481
12482#[gpui::test]
12483async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12484 init_test(cx, |_| {});
12485
12486 let mut cx = EditorLspTestContext::new(
12487 Language::new(
12488 LanguageConfig {
12489 matcher: LanguageMatcher {
12490 path_suffixes: vec!["jsx".into()],
12491 ..Default::default()
12492 },
12493 overrides: [(
12494 "element".into(),
12495 LanguageConfigOverride {
12496 word_characters: Override::Set(['-'].into_iter().collect()),
12497 ..Default::default()
12498 },
12499 )]
12500 .into_iter()
12501 .collect(),
12502 ..Default::default()
12503 },
12504 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12505 )
12506 .with_override_query("(jsx_self_closing_element) @element")
12507 .unwrap(),
12508 lsp::ServerCapabilities {
12509 completion_provider: Some(lsp::CompletionOptions {
12510 trigger_characters: Some(vec![":".to_string()]),
12511 ..Default::default()
12512 }),
12513 ..Default::default()
12514 },
12515 cx,
12516 )
12517 .await;
12518
12519 cx.lsp
12520 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12521 Ok(Some(lsp::CompletionResponse::Array(vec![
12522 lsp::CompletionItem {
12523 label: "bg-blue".into(),
12524 ..Default::default()
12525 },
12526 lsp::CompletionItem {
12527 label: "bg-red".into(),
12528 ..Default::default()
12529 },
12530 lsp::CompletionItem {
12531 label: "bg-yellow".into(),
12532 ..Default::default()
12533 },
12534 ])))
12535 });
12536
12537 cx.set_state(r#"<p class="bgˇ" />"#);
12538
12539 // Trigger completion when typing a dash, because the dash is an extra
12540 // word character in the 'element' scope, which contains the cursor.
12541 cx.simulate_keystroke("-");
12542 cx.executor().run_until_parked();
12543 cx.update_editor(|editor, _, _| {
12544 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12545 {
12546 assert_eq!(
12547 completion_menu_entries(&menu),
12548 &["bg-red", "bg-blue", "bg-yellow"]
12549 );
12550 } else {
12551 panic!("expected completion menu to be open");
12552 }
12553 });
12554
12555 cx.simulate_keystroke("l");
12556 cx.executor().run_until_parked();
12557 cx.update_editor(|editor, _, _| {
12558 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12559 {
12560 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12561 } else {
12562 panic!("expected completion menu to be open");
12563 }
12564 });
12565
12566 // When filtering completions, consider the character after the '-' to
12567 // be the start of a subword.
12568 cx.set_state(r#"<p class="yelˇ" />"#);
12569 cx.simulate_keystroke("l");
12570 cx.executor().run_until_parked();
12571 cx.update_editor(|editor, _, _| {
12572 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12573 {
12574 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12575 } else {
12576 panic!("expected completion menu to be open");
12577 }
12578 });
12579}
12580
12581fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12582 let entries = menu.entries.borrow();
12583 entries.iter().map(|mat| mat.string.clone()).collect()
12584}
12585
12586#[gpui::test]
12587async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12588 init_test(cx, |settings| {
12589 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12590 FormatterList(vec![Formatter::Prettier].into()),
12591 ))
12592 });
12593
12594 let fs = FakeFs::new(cx.executor());
12595 fs.insert_file(path!("/file.ts"), Default::default()).await;
12596
12597 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12598 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12599
12600 language_registry.add(Arc::new(Language::new(
12601 LanguageConfig {
12602 name: "TypeScript".into(),
12603 matcher: LanguageMatcher {
12604 path_suffixes: vec!["ts".to_string()],
12605 ..Default::default()
12606 },
12607 ..Default::default()
12608 },
12609 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12610 )));
12611 update_test_language_settings(cx, |settings| {
12612 settings.defaults.prettier = Some(PrettierSettings {
12613 allowed: true,
12614 ..PrettierSettings::default()
12615 });
12616 });
12617
12618 let test_plugin = "test_plugin";
12619 let _ = language_registry.register_fake_lsp(
12620 "TypeScript",
12621 FakeLspAdapter {
12622 prettier_plugins: vec![test_plugin],
12623 ..Default::default()
12624 },
12625 );
12626
12627 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12628 let buffer = project
12629 .update(cx, |project, cx| {
12630 project.open_local_buffer(path!("/file.ts"), cx)
12631 })
12632 .await
12633 .unwrap();
12634
12635 let buffer_text = "one\ntwo\nthree\n";
12636 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12637 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12638 editor.update_in(cx, |editor, window, cx| {
12639 editor.set_text(buffer_text, window, cx)
12640 });
12641
12642 editor
12643 .update_in(cx, |editor, window, cx| {
12644 editor.perform_format(
12645 project.clone(),
12646 FormatTrigger::Manual,
12647 FormatTarget::Buffers,
12648 window,
12649 cx,
12650 )
12651 })
12652 .unwrap()
12653 .await;
12654 assert_eq!(
12655 editor.update(cx, |editor, cx| editor.text(cx)),
12656 buffer_text.to_string() + prettier_format_suffix,
12657 "Test prettier formatting was not applied to the original buffer text",
12658 );
12659
12660 update_test_language_settings(cx, |settings| {
12661 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12662 });
12663 let format = editor.update_in(cx, |editor, window, cx| {
12664 editor.perform_format(
12665 project.clone(),
12666 FormatTrigger::Manual,
12667 FormatTarget::Buffers,
12668 window,
12669 cx,
12670 )
12671 });
12672 format.await.unwrap();
12673 assert_eq!(
12674 editor.update(cx, |editor, cx| editor.text(cx)),
12675 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12676 "Autoformatting (via test prettier) was not applied to the original buffer text",
12677 );
12678}
12679
12680#[gpui::test]
12681async fn test_addition_reverts(cx: &mut TestAppContext) {
12682 init_test(cx, |_| {});
12683 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12684 let base_text = indoc! {r#"
12685 struct Row;
12686 struct Row1;
12687 struct Row2;
12688
12689 struct Row4;
12690 struct Row5;
12691 struct Row6;
12692
12693 struct Row8;
12694 struct Row9;
12695 struct Row10;"#};
12696
12697 // When addition hunks are not adjacent to carets, no hunk revert is performed
12698 assert_hunk_revert(
12699 indoc! {r#"struct Row;
12700 struct Row1;
12701 struct Row1.1;
12702 struct Row1.2;
12703 struct Row2;ˇ
12704
12705 struct Row4;
12706 struct Row5;
12707 struct Row6;
12708
12709 struct Row8;
12710 ˇstruct Row9;
12711 struct Row9.1;
12712 struct Row9.2;
12713 struct Row9.3;
12714 struct Row10;"#},
12715 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12716 indoc! {r#"struct Row;
12717 struct Row1;
12718 struct Row1.1;
12719 struct Row1.2;
12720 struct Row2;ˇ
12721
12722 struct Row4;
12723 struct Row5;
12724 struct Row6;
12725
12726 struct Row8;
12727 ˇstruct Row9;
12728 struct Row9.1;
12729 struct Row9.2;
12730 struct Row9.3;
12731 struct Row10;"#},
12732 base_text,
12733 &mut cx,
12734 );
12735 // Same for selections
12736 assert_hunk_revert(
12737 indoc! {r#"struct Row;
12738 struct Row1;
12739 struct Row2;
12740 struct Row2.1;
12741 struct Row2.2;
12742 «ˇ
12743 struct Row4;
12744 struct» Row5;
12745 «struct Row6;
12746 ˇ»
12747 struct Row9.1;
12748 struct Row9.2;
12749 struct Row9.3;
12750 struct Row8;
12751 struct Row9;
12752 struct Row10;"#},
12753 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12754 indoc! {r#"struct Row;
12755 struct Row1;
12756 struct Row2;
12757 struct Row2.1;
12758 struct Row2.2;
12759 «ˇ
12760 struct Row4;
12761 struct» Row5;
12762 «struct Row6;
12763 ˇ»
12764 struct Row9.1;
12765 struct Row9.2;
12766 struct Row9.3;
12767 struct Row8;
12768 struct Row9;
12769 struct Row10;"#},
12770 base_text,
12771 &mut cx,
12772 );
12773
12774 // When carets and selections intersect the addition hunks, those are reverted.
12775 // Adjacent carets got merged.
12776 assert_hunk_revert(
12777 indoc! {r#"struct Row;
12778 ˇ// something on the top
12779 struct Row1;
12780 struct Row2;
12781 struct Roˇw3.1;
12782 struct Row2.2;
12783 struct Row2.3;ˇ
12784
12785 struct Row4;
12786 struct ˇRow5.1;
12787 struct Row5.2;
12788 struct «Rowˇ»5.3;
12789 struct Row5;
12790 struct Row6;
12791 ˇ
12792 struct Row9.1;
12793 struct «Rowˇ»9.2;
12794 struct «ˇRow»9.3;
12795 struct Row8;
12796 struct Row9;
12797 «ˇ// something on bottom»
12798 struct Row10;"#},
12799 vec![
12800 DiffHunkStatusKind::Added,
12801 DiffHunkStatusKind::Added,
12802 DiffHunkStatusKind::Added,
12803 DiffHunkStatusKind::Added,
12804 DiffHunkStatusKind::Added,
12805 ],
12806 indoc! {r#"struct Row;
12807 ˇstruct Row1;
12808 struct Row2;
12809 ˇ
12810 struct Row4;
12811 ˇstruct Row5;
12812 struct Row6;
12813 ˇ
12814 ˇstruct Row8;
12815 struct Row9;
12816 ˇstruct Row10;"#},
12817 base_text,
12818 &mut cx,
12819 );
12820}
12821
12822#[gpui::test]
12823async fn test_modification_reverts(cx: &mut TestAppContext) {
12824 init_test(cx, |_| {});
12825 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12826 let base_text = indoc! {r#"
12827 struct Row;
12828 struct Row1;
12829 struct Row2;
12830
12831 struct Row4;
12832 struct Row5;
12833 struct Row6;
12834
12835 struct Row8;
12836 struct Row9;
12837 struct Row10;"#};
12838
12839 // Modification hunks behave the same as the addition ones.
12840 assert_hunk_revert(
12841 indoc! {r#"struct Row;
12842 struct Row1;
12843 struct Row33;
12844 ˇ
12845 struct Row4;
12846 struct Row5;
12847 struct Row6;
12848 ˇ
12849 struct Row99;
12850 struct Row9;
12851 struct Row10;"#},
12852 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12853 indoc! {r#"struct Row;
12854 struct Row1;
12855 struct Row33;
12856 ˇ
12857 struct Row4;
12858 struct Row5;
12859 struct Row6;
12860 ˇ
12861 struct Row99;
12862 struct Row9;
12863 struct Row10;"#},
12864 base_text,
12865 &mut cx,
12866 );
12867 assert_hunk_revert(
12868 indoc! {r#"struct Row;
12869 struct Row1;
12870 struct Row33;
12871 «ˇ
12872 struct Row4;
12873 struct» Row5;
12874 «struct Row6;
12875 ˇ»
12876 struct Row99;
12877 struct Row9;
12878 struct Row10;"#},
12879 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12880 indoc! {r#"struct Row;
12881 struct Row1;
12882 struct Row33;
12883 «ˇ
12884 struct Row4;
12885 struct» Row5;
12886 «struct Row6;
12887 ˇ»
12888 struct Row99;
12889 struct Row9;
12890 struct Row10;"#},
12891 base_text,
12892 &mut cx,
12893 );
12894
12895 assert_hunk_revert(
12896 indoc! {r#"ˇstruct Row1.1;
12897 struct Row1;
12898 «ˇstr»uct Row22;
12899
12900 struct ˇRow44;
12901 struct Row5;
12902 struct «Rˇ»ow66;ˇ
12903
12904 «struˇ»ct Row88;
12905 struct Row9;
12906 struct Row1011;ˇ"#},
12907 vec![
12908 DiffHunkStatusKind::Modified,
12909 DiffHunkStatusKind::Modified,
12910 DiffHunkStatusKind::Modified,
12911 DiffHunkStatusKind::Modified,
12912 DiffHunkStatusKind::Modified,
12913 DiffHunkStatusKind::Modified,
12914 ],
12915 indoc! {r#"struct Row;
12916 ˇstruct Row1;
12917 struct Row2;
12918 ˇ
12919 struct Row4;
12920 ˇstruct Row5;
12921 struct Row6;
12922 ˇ
12923 struct Row8;
12924 ˇstruct Row9;
12925 struct Row10;ˇ"#},
12926 base_text,
12927 &mut cx,
12928 );
12929}
12930
12931#[gpui::test]
12932async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12933 init_test(cx, |_| {});
12934 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12935 let base_text = indoc! {r#"
12936 one
12937
12938 two
12939 three
12940 "#};
12941
12942 cx.set_head_text(base_text);
12943 cx.set_state("\nˇ\n");
12944 cx.executor().run_until_parked();
12945 cx.update_editor(|editor, _window, cx| {
12946 editor.expand_selected_diff_hunks(cx);
12947 });
12948 cx.executor().run_until_parked();
12949 cx.update_editor(|editor, window, cx| {
12950 editor.backspace(&Default::default(), window, cx);
12951 });
12952 cx.run_until_parked();
12953 cx.assert_state_with_diff(
12954 indoc! {r#"
12955
12956 - two
12957 - threeˇ
12958 +
12959 "#}
12960 .to_string(),
12961 );
12962}
12963
12964#[gpui::test]
12965async fn test_deletion_reverts(cx: &mut TestAppContext) {
12966 init_test(cx, |_| {});
12967 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12968 let base_text = indoc! {r#"struct Row;
12969struct Row1;
12970struct Row2;
12971
12972struct Row4;
12973struct Row5;
12974struct Row6;
12975
12976struct Row8;
12977struct Row9;
12978struct Row10;"#};
12979
12980 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12981 assert_hunk_revert(
12982 indoc! {r#"struct Row;
12983 struct Row2;
12984
12985 ˇstruct Row4;
12986 struct Row5;
12987 struct Row6;
12988 ˇ
12989 struct Row8;
12990 struct Row10;"#},
12991 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12992 indoc! {r#"struct Row;
12993 struct Row2;
12994
12995 ˇstruct Row4;
12996 struct Row5;
12997 struct Row6;
12998 ˇ
12999 struct Row8;
13000 struct Row10;"#},
13001 base_text,
13002 &mut cx,
13003 );
13004 assert_hunk_revert(
13005 indoc! {r#"struct Row;
13006 struct Row2;
13007
13008 «ˇstruct Row4;
13009 struct» Row5;
13010 «struct Row6;
13011 ˇ»
13012 struct Row8;
13013 struct Row10;"#},
13014 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13015 indoc! {r#"struct Row;
13016 struct Row2;
13017
13018 «ˇstruct Row4;
13019 struct» Row5;
13020 «struct Row6;
13021 ˇ»
13022 struct Row8;
13023 struct Row10;"#},
13024 base_text,
13025 &mut cx,
13026 );
13027
13028 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13029 assert_hunk_revert(
13030 indoc! {r#"struct Row;
13031 ˇstruct Row2;
13032
13033 struct Row4;
13034 struct Row5;
13035 struct Row6;
13036
13037 struct Row8;ˇ
13038 struct Row10;"#},
13039 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13040 indoc! {r#"struct Row;
13041 struct Row1;
13042 ˇstruct Row2;
13043
13044 struct Row4;
13045 struct Row5;
13046 struct Row6;
13047
13048 struct Row8;ˇ
13049 struct Row9;
13050 struct Row10;"#},
13051 base_text,
13052 &mut cx,
13053 );
13054 assert_hunk_revert(
13055 indoc! {r#"struct Row;
13056 struct Row2«ˇ;
13057 struct Row4;
13058 struct» Row5;
13059 «struct Row6;
13060
13061 struct Row8;ˇ»
13062 struct Row10;"#},
13063 vec![
13064 DiffHunkStatusKind::Deleted,
13065 DiffHunkStatusKind::Deleted,
13066 DiffHunkStatusKind::Deleted,
13067 ],
13068 indoc! {r#"struct Row;
13069 struct Row1;
13070 struct Row2«ˇ;
13071
13072 struct Row4;
13073 struct» Row5;
13074 «struct Row6;
13075
13076 struct Row8;ˇ»
13077 struct Row9;
13078 struct Row10;"#},
13079 base_text,
13080 &mut cx,
13081 );
13082}
13083
13084#[gpui::test]
13085async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13086 init_test(cx, |_| {});
13087
13088 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13089 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13090 let base_text_3 =
13091 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13092
13093 let text_1 = edit_first_char_of_every_line(base_text_1);
13094 let text_2 = edit_first_char_of_every_line(base_text_2);
13095 let text_3 = edit_first_char_of_every_line(base_text_3);
13096
13097 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13098 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13099 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13100
13101 let multibuffer = cx.new(|cx| {
13102 let mut multibuffer = MultiBuffer::new(ReadWrite);
13103 multibuffer.push_excerpts(
13104 buffer_1.clone(),
13105 [
13106 ExcerptRange {
13107 context: Point::new(0, 0)..Point::new(3, 0),
13108 primary: None,
13109 },
13110 ExcerptRange {
13111 context: Point::new(5, 0)..Point::new(7, 0),
13112 primary: None,
13113 },
13114 ExcerptRange {
13115 context: Point::new(9, 0)..Point::new(10, 4),
13116 primary: None,
13117 },
13118 ],
13119 cx,
13120 );
13121 multibuffer.push_excerpts(
13122 buffer_2.clone(),
13123 [
13124 ExcerptRange {
13125 context: Point::new(0, 0)..Point::new(3, 0),
13126 primary: None,
13127 },
13128 ExcerptRange {
13129 context: Point::new(5, 0)..Point::new(7, 0),
13130 primary: None,
13131 },
13132 ExcerptRange {
13133 context: Point::new(9, 0)..Point::new(10, 4),
13134 primary: None,
13135 },
13136 ],
13137 cx,
13138 );
13139 multibuffer.push_excerpts(
13140 buffer_3.clone(),
13141 [
13142 ExcerptRange {
13143 context: Point::new(0, 0)..Point::new(3, 0),
13144 primary: None,
13145 },
13146 ExcerptRange {
13147 context: Point::new(5, 0)..Point::new(7, 0),
13148 primary: None,
13149 },
13150 ExcerptRange {
13151 context: Point::new(9, 0)..Point::new(10, 4),
13152 primary: None,
13153 },
13154 ],
13155 cx,
13156 );
13157 multibuffer
13158 });
13159
13160 let fs = FakeFs::new(cx.executor());
13161 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13162 let (editor, cx) = cx
13163 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13164 editor.update_in(cx, |editor, _window, cx| {
13165 for (buffer, diff_base) in [
13166 (buffer_1.clone(), base_text_1),
13167 (buffer_2.clone(), base_text_2),
13168 (buffer_3.clone(), base_text_3),
13169 ] {
13170 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13171 editor
13172 .buffer
13173 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13174 }
13175 });
13176 cx.executor().run_until_parked();
13177
13178 editor.update_in(cx, |editor, window, cx| {
13179 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}");
13180 editor.select_all(&SelectAll, window, cx);
13181 editor.git_restore(&Default::default(), window, cx);
13182 });
13183 cx.executor().run_until_parked();
13184
13185 // When all ranges are selected, all buffer hunks are reverted.
13186 editor.update(cx, |editor, cx| {
13187 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");
13188 });
13189 buffer_1.update(cx, |buffer, _| {
13190 assert_eq!(buffer.text(), base_text_1);
13191 });
13192 buffer_2.update(cx, |buffer, _| {
13193 assert_eq!(buffer.text(), base_text_2);
13194 });
13195 buffer_3.update(cx, |buffer, _| {
13196 assert_eq!(buffer.text(), base_text_3);
13197 });
13198
13199 editor.update_in(cx, |editor, window, cx| {
13200 editor.undo(&Default::default(), window, cx);
13201 });
13202
13203 editor.update_in(cx, |editor, window, cx| {
13204 editor.change_selections(None, window, cx, |s| {
13205 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13206 });
13207 editor.git_restore(&Default::default(), window, cx);
13208 });
13209
13210 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13211 // but not affect buffer_2 and its related excerpts.
13212 editor.update(cx, |editor, cx| {
13213 assert_eq!(
13214 editor.text(cx),
13215 "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}"
13216 );
13217 });
13218 buffer_1.update(cx, |buffer, _| {
13219 assert_eq!(buffer.text(), base_text_1);
13220 });
13221 buffer_2.update(cx, |buffer, _| {
13222 assert_eq!(
13223 buffer.text(),
13224 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13225 );
13226 });
13227 buffer_3.update(cx, |buffer, _| {
13228 assert_eq!(
13229 buffer.text(),
13230 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13231 );
13232 });
13233
13234 fn edit_first_char_of_every_line(text: &str) -> String {
13235 text.split('\n')
13236 .map(|line| format!("X{}", &line[1..]))
13237 .collect::<Vec<_>>()
13238 .join("\n")
13239 }
13240}
13241
13242#[gpui::test]
13243async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13244 init_test(cx, |_| {});
13245
13246 let cols = 4;
13247 let rows = 10;
13248 let sample_text_1 = sample_text(rows, cols, 'a');
13249 assert_eq!(
13250 sample_text_1,
13251 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13252 );
13253 let sample_text_2 = sample_text(rows, cols, 'l');
13254 assert_eq!(
13255 sample_text_2,
13256 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13257 );
13258 let sample_text_3 = sample_text(rows, cols, 'v');
13259 assert_eq!(
13260 sample_text_3,
13261 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13262 );
13263
13264 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13265 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13266 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13267
13268 let multi_buffer = cx.new(|cx| {
13269 let mut multibuffer = MultiBuffer::new(ReadWrite);
13270 multibuffer.push_excerpts(
13271 buffer_1.clone(),
13272 [
13273 ExcerptRange {
13274 context: Point::new(0, 0)..Point::new(3, 0),
13275 primary: None,
13276 },
13277 ExcerptRange {
13278 context: Point::new(5, 0)..Point::new(7, 0),
13279 primary: None,
13280 },
13281 ExcerptRange {
13282 context: Point::new(9, 0)..Point::new(10, 4),
13283 primary: None,
13284 },
13285 ],
13286 cx,
13287 );
13288 multibuffer.push_excerpts(
13289 buffer_2.clone(),
13290 [
13291 ExcerptRange {
13292 context: Point::new(0, 0)..Point::new(3, 0),
13293 primary: None,
13294 },
13295 ExcerptRange {
13296 context: Point::new(5, 0)..Point::new(7, 0),
13297 primary: None,
13298 },
13299 ExcerptRange {
13300 context: Point::new(9, 0)..Point::new(10, 4),
13301 primary: None,
13302 },
13303 ],
13304 cx,
13305 );
13306 multibuffer.push_excerpts(
13307 buffer_3.clone(),
13308 [
13309 ExcerptRange {
13310 context: Point::new(0, 0)..Point::new(3, 0),
13311 primary: None,
13312 },
13313 ExcerptRange {
13314 context: Point::new(5, 0)..Point::new(7, 0),
13315 primary: None,
13316 },
13317 ExcerptRange {
13318 context: Point::new(9, 0)..Point::new(10, 4),
13319 primary: None,
13320 },
13321 ],
13322 cx,
13323 );
13324 multibuffer
13325 });
13326
13327 let fs = FakeFs::new(cx.executor());
13328 fs.insert_tree(
13329 "/a",
13330 json!({
13331 "main.rs": sample_text_1,
13332 "other.rs": sample_text_2,
13333 "lib.rs": sample_text_3,
13334 }),
13335 )
13336 .await;
13337 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13338 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13339 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13340 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13341 Editor::new(
13342 EditorMode::Full,
13343 multi_buffer,
13344 Some(project.clone()),
13345 true,
13346 window,
13347 cx,
13348 )
13349 });
13350 let multibuffer_item_id = workspace
13351 .update(cx, |workspace, window, cx| {
13352 assert!(
13353 workspace.active_item(cx).is_none(),
13354 "active item should be None before the first item is added"
13355 );
13356 workspace.add_item_to_active_pane(
13357 Box::new(multi_buffer_editor.clone()),
13358 None,
13359 true,
13360 window,
13361 cx,
13362 );
13363 let active_item = workspace
13364 .active_item(cx)
13365 .expect("should have an active item after adding the multi buffer");
13366 assert!(
13367 !active_item.is_singleton(cx),
13368 "A multi buffer was expected to active after adding"
13369 );
13370 active_item.item_id()
13371 })
13372 .unwrap();
13373 cx.executor().run_until_parked();
13374
13375 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13376 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13377 s.select_ranges(Some(1..2))
13378 });
13379 editor.open_excerpts(&OpenExcerpts, window, cx);
13380 });
13381 cx.executor().run_until_parked();
13382 let first_item_id = workspace
13383 .update(cx, |workspace, window, cx| {
13384 let active_item = workspace
13385 .active_item(cx)
13386 .expect("should have an active item after navigating into the 1st buffer");
13387 let first_item_id = active_item.item_id();
13388 assert_ne!(
13389 first_item_id, multibuffer_item_id,
13390 "Should navigate into the 1st buffer and activate it"
13391 );
13392 assert!(
13393 active_item.is_singleton(cx),
13394 "New active item should be a singleton buffer"
13395 );
13396 assert_eq!(
13397 active_item
13398 .act_as::<Editor>(cx)
13399 .expect("should have navigated into an editor for the 1st buffer")
13400 .read(cx)
13401 .text(cx),
13402 sample_text_1
13403 );
13404
13405 workspace
13406 .go_back(workspace.active_pane().downgrade(), window, cx)
13407 .detach_and_log_err(cx);
13408
13409 first_item_id
13410 })
13411 .unwrap();
13412 cx.executor().run_until_parked();
13413 workspace
13414 .update(cx, |workspace, _, cx| {
13415 let active_item = workspace
13416 .active_item(cx)
13417 .expect("should have an active item after navigating back");
13418 assert_eq!(
13419 active_item.item_id(),
13420 multibuffer_item_id,
13421 "Should navigate back to the multi buffer"
13422 );
13423 assert!(!active_item.is_singleton(cx));
13424 })
13425 .unwrap();
13426
13427 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13428 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13429 s.select_ranges(Some(39..40))
13430 });
13431 editor.open_excerpts(&OpenExcerpts, window, cx);
13432 });
13433 cx.executor().run_until_parked();
13434 let second_item_id = workspace
13435 .update(cx, |workspace, window, cx| {
13436 let active_item = workspace
13437 .active_item(cx)
13438 .expect("should have an active item after navigating into the 2nd buffer");
13439 let second_item_id = active_item.item_id();
13440 assert_ne!(
13441 second_item_id, multibuffer_item_id,
13442 "Should navigate away from the multibuffer"
13443 );
13444 assert_ne!(
13445 second_item_id, first_item_id,
13446 "Should navigate into the 2nd buffer and activate it"
13447 );
13448 assert!(
13449 active_item.is_singleton(cx),
13450 "New active item should be a singleton buffer"
13451 );
13452 assert_eq!(
13453 active_item
13454 .act_as::<Editor>(cx)
13455 .expect("should have navigated into an editor")
13456 .read(cx)
13457 .text(cx),
13458 sample_text_2
13459 );
13460
13461 workspace
13462 .go_back(workspace.active_pane().downgrade(), window, cx)
13463 .detach_and_log_err(cx);
13464
13465 second_item_id
13466 })
13467 .unwrap();
13468 cx.executor().run_until_parked();
13469 workspace
13470 .update(cx, |workspace, _, cx| {
13471 let active_item = workspace
13472 .active_item(cx)
13473 .expect("should have an active item after navigating back from the 2nd buffer");
13474 assert_eq!(
13475 active_item.item_id(),
13476 multibuffer_item_id,
13477 "Should navigate back from the 2nd buffer to the multi buffer"
13478 );
13479 assert!(!active_item.is_singleton(cx));
13480 })
13481 .unwrap();
13482
13483 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13484 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13485 s.select_ranges(Some(70..70))
13486 });
13487 editor.open_excerpts(&OpenExcerpts, window, cx);
13488 });
13489 cx.executor().run_until_parked();
13490 workspace
13491 .update(cx, |workspace, window, cx| {
13492 let active_item = workspace
13493 .active_item(cx)
13494 .expect("should have an active item after navigating into the 3rd buffer");
13495 let third_item_id = active_item.item_id();
13496 assert_ne!(
13497 third_item_id, multibuffer_item_id,
13498 "Should navigate into the 3rd buffer and activate it"
13499 );
13500 assert_ne!(third_item_id, first_item_id);
13501 assert_ne!(third_item_id, second_item_id);
13502 assert!(
13503 active_item.is_singleton(cx),
13504 "New active item should be a singleton buffer"
13505 );
13506 assert_eq!(
13507 active_item
13508 .act_as::<Editor>(cx)
13509 .expect("should have navigated into an editor")
13510 .read(cx)
13511 .text(cx),
13512 sample_text_3
13513 );
13514
13515 workspace
13516 .go_back(workspace.active_pane().downgrade(), window, cx)
13517 .detach_and_log_err(cx);
13518 })
13519 .unwrap();
13520 cx.executor().run_until_parked();
13521 workspace
13522 .update(cx, |workspace, _, cx| {
13523 let active_item = workspace
13524 .active_item(cx)
13525 .expect("should have an active item after navigating back from the 3rd buffer");
13526 assert_eq!(
13527 active_item.item_id(),
13528 multibuffer_item_id,
13529 "Should navigate back from the 3rd buffer to the multi buffer"
13530 );
13531 assert!(!active_item.is_singleton(cx));
13532 })
13533 .unwrap();
13534}
13535
13536#[gpui::test]
13537async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13538 init_test(cx, |_| {});
13539
13540 let mut cx = EditorTestContext::new(cx).await;
13541
13542 let diff_base = r#"
13543 use some::mod;
13544
13545 const A: u32 = 42;
13546
13547 fn main() {
13548 println!("hello");
13549
13550 println!("world");
13551 }
13552 "#
13553 .unindent();
13554
13555 cx.set_state(
13556 &r#"
13557 use some::modified;
13558
13559 ˇ
13560 fn main() {
13561 println!("hello there");
13562
13563 println!("around the");
13564 println!("world");
13565 }
13566 "#
13567 .unindent(),
13568 );
13569
13570 cx.set_head_text(&diff_base);
13571 executor.run_until_parked();
13572
13573 cx.update_editor(|editor, window, cx| {
13574 editor.go_to_next_hunk(&GoToHunk, window, cx);
13575 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13576 });
13577 executor.run_until_parked();
13578 cx.assert_state_with_diff(
13579 r#"
13580 use some::modified;
13581
13582
13583 fn main() {
13584 - println!("hello");
13585 + ˇ println!("hello there");
13586
13587 println!("around the");
13588 println!("world");
13589 }
13590 "#
13591 .unindent(),
13592 );
13593
13594 cx.update_editor(|editor, window, cx| {
13595 for _ in 0..2 {
13596 editor.go_to_next_hunk(&GoToHunk, window, cx);
13597 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13598 }
13599 });
13600 executor.run_until_parked();
13601 cx.assert_state_with_diff(
13602 r#"
13603 - use some::mod;
13604 + ˇuse some::modified;
13605
13606
13607 fn main() {
13608 - println!("hello");
13609 + println!("hello there");
13610
13611 + println!("around the");
13612 println!("world");
13613 }
13614 "#
13615 .unindent(),
13616 );
13617
13618 cx.update_editor(|editor, window, cx| {
13619 editor.go_to_next_hunk(&GoToHunk, window, cx);
13620 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13621 });
13622 executor.run_until_parked();
13623 cx.assert_state_with_diff(
13624 r#"
13625 - use some::mod;
13626 + use some::modified;
13627
13628 - const A: u32 = 42;
13629 ˇ
13630 fn main() {
13631 - println!("hello");
13632 + println!("hello there");
13633
13634 + println!("around the");
13635 println!("world");
13636 }
13637 "#
13638 .unindent(),
13639 );
13640
13641 cx.update_editor(|editor, window, cx| {
13642 editor.cancel(&Cancel, window, cx);
13643 });
13644
13645 cx.assert_state_with_diff(
13646 r#"
13647 use some::modified;
13648
13649 ˇ
13650 fn main() {
13651 println!("hello there");
13652
13653 println!("around the");
13654 println!("world");
13655 }
13656 "#
13657 .unindent(),
13658 );
13659}
13660
13661#[gpui::test]
13662async fn test_diff_base_change_with_expanded_diff_hunks(
13663 executor: BackgroundExecutor,
13664 cx: &mut TestAppContext,
13665) {
13666 init_test(cx, |_| {});
13667
13668 let mut cx = EditorTestContext::new(cx).await;
13669
13670 let diff_base = r#"
13671 use some::mod1;
13672 use some::mod2;
13673
13674 const A: u32 = 42;
13675 const B: u32 = 42;
13676 const C: u32 = 42;
13677
13678 fn main() {
13679 println!("hello");
13680
13681 println!("world");
13682 }
13683 "#
13684 .unindent();
13685
13686 cx.set_state(
13687 &r#"
13688 use some::mod2;
13689
13690 const A: u32 = 42;
13691 const C: u32 = 42;
13692
13693 fn main(ˇ) {
13694 //println!("hello");
13695
13696 println!("world");
13697 //
13698 //
13699 }
13700 "#
13701 .unindent(),
13702 );
13703
13704 cx.set_head_text(&diff_base);
13705 executor.run_until_parked();
13706
13707 cx.update_editor(|editor, window, cx| {
13708 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13709 });
13710 executor.run_until_parked();
13711 cx.assert_state_with_diff(
13712 r#"
13713 - use some::mod1;
13714 use some::mod2;
13715
13716 const A: u32 = 42;
13717 - const B: u32 = 42;
13718 const C: u32 = 42;
13719
13720 fn main(ˇ) {
13721 - println!("hello");
13722 + //println!("hello");
13723
13724 println!("world");
13725 + //
13726 + //
13727 }
13728 "#
13729 .unindent(),
13730 );
13731
13732 cx.set_head_text("new diff base!");
13733 executor.run_until_parked();
13734 cx.assert_state_with_diff(
13735 r#"
13736 - new diff base!
13737 + use some::mod2;
13738 +
13739 + const A: u32 = 42;
13740 + const C: u32 = 42;
13741 +
13742 + fn main(ˇ) {
13743 + //println!("hello");
13744 +
13745 + println!("world");
13746 + //
13747 + //
13748 + }
13749 "#
13750 .unindent(),
13751 );
13752}
13753
13754#[gpui::test]
13755async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13756 init_test(cx, |_| {});
13757
13758 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13759 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13760 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13761 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13762 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13763 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13764
13765 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13766 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13767 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13768
13769 let multi_buffer = cx.new(|cx| {
13770 let mut multibuffer = MultiBuffer::new(ReadWrite);
13771 multibuffer.push_excerpts(
13772 buffer_1.clone(),
13773 [
13774 ExcerptRange {
13775 context: Point::new(0, 0)..Point::new(3, 0),
13776 primary: None,
13777 },
13778 ExcerptRange {
13779 context: Point::new(5, 0)..Point::new(7, 0),
13780 primary: None,
13781 },
13782 ExcerptRange {
13783 context: Point::new(9, 0)..Point::new(10, 3),
13784 primary: None,
13785 },
13786 ],
13787 cx,
13788 );
13789 multibuffer.push_excerpts(
13790 buffer_2.clone(),
13791 [
13792 ExcerptRange {
13793 context: Point::new(0, 0)..Point::new(3, 0),
13794 primary: None,
13795 },
13796 ExcerptRange {
13797 context: Point::new(5, 0)..Point::new(7, 0),
13798 primary: None,
13799 },
13800 ExcerptRange {
13801 context: Point::new(9, 0)..Point::new(10, 3),
13802 primary: None,
13803 },
13804 ],
13805 cx,
13806 );
13807 multibuffer.push_excerpts(
13808 buffer_3.clone(),
13809 [
13810 ExcerptRange {
13811 context: Point::new(0, 0)..Point::new(3, 0),
13812 primary: None,
13813 },
13814 ExcerptRange {
13815 context: Point::new(5, 0)..Point::new(7, 0),
13816 primary: None,
13817 },
13818 ExcerptRange {
13819 context: Point::new(9, 0)..Point::new(10, 3),
13820 primary: None,
13821 },
13822 ],
13823 cx,
13824 );
13825 multibuffer
13826 });
13827
13828 let editor = cx.add_window(|window, cx| {
13829 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13830 });
13831 editor
13832 .update(cx, |editor, _window, cx| {
13833 for (buffer, diff_base) in [
13834 (buffer_1.clone(), file_1_old),
13835 (buffer_2.clone(), file_2_old),
13836 (buffer_3.clone(), file_3_old),
13837 ] {
13838 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13839 editor
13840 .buffer
13841 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13842 }
13843 })
13844 .unwrap();
13845
13846 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13847 cx.run_until_parked();
13848
13849 cx.assert_editor_state(
13850 &"
13851 ˇaaa
13852 ccc
13853 ddd
13854
13855 ggg
13856 hhh
13857
13858
13859 lll
13860 mmm
13861 NNN
13862
13863 qqq
13864 rrr
13865
13866 uuu
13867 111
13868 222
13869 333
13870
13871 666
13872 777
13873
13874 000
13875 !!!"
13876 .unindent(),
13877 );
13878
13879 cx.update_editor(|editor, window, cx| {
13880 editor.select_all(&SelectAll, window, cx);
13881 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13882 });
13883 cx.executor().run_until_parked();
13884
13885 cx.assert_state_with_diff(
13886 "
13887 «aaa
13888 - bbb
13889 ccc
13890 ddd
13891
13892 ggg
13893 hhh
13894
13895
13896 lll
13897 mmm
13898 - nnn
13899 + NNN
13900
13901 qqq
13902 rrr
13903
13904 uuu
13905 111
13906 222
13907 333
13908
13909 + 666
13910 777
13911
13912 000
13913 !!!ˇ»"
13914 .unindent(),
13915 );
13916}
13917
13918#[gpui::test]
13919async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13920 init_test(cx, |_| {});
13921
13922 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13923 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13924
13925 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13926 let multi_buffer = cx.new(|cx| {
13927 let mut multibuffer = MultiBuffer::new(ReadWrite);
13928 multibuffer.push_excerpts(
13929 buffer.clone(),
13930 [
13931 ExcerptRange {
13932 context: Point::new(0, 0)..Point::new(2, 0),
13933 primary: None,
13934 },
13935 ExcerptRange {
13936 context: Point::new(4, 0)..Point::new(7, 0),
13937 primary: None,
13938 },
13939 ExcerptRange {
13940 context: Point::new(9, 0)..Point::new(10, 0),
13941 primary: None,
13942 },
13943 ],
13944 cx,
13945 );
13946 multibuffer
13947 });
13948
13949 let editor = cx.add_window(|window, cx| {
13950 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13951 });
13952 editor
13953 .update(cx, |editor, _window, cx| {
13954 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13955 editor
13956 .buffer
13957 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13958 })
13959 .unwrap();
13960
13961 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13962 cx.run_until_parked();
13963
13964 cx.update_editor(|editor, window, cx| {
13965 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13966 });
13967 cx.executor().run_until_parked();
13968
13969 // When the start of a hunk coincides with the start of its excerpt,
13970 // the hunk is expanded. When the start of a a hunk is earlier than
13971 // the start of its excerpt, the hunk is not expanded.
13972 cx.assert_state_with_diff(
13973 "
13974 ˇaaa
13975 - bbb
13976 + BBB
13977
13978 - ddd
13979 - eee
13980 + DDD
13981 + EEE
13982 fff
13983
13984 iii
13985 "
13986 .unindent(),
13987 );
13988}
13989
13990#[gpui::test]
13991async fn test_edits_around_expanded_insertion_hunks(
13992 executor: BackgroundExecutor,
13993 cx: &mut TestAppContext,
13994) {
13995 init_test(cx, |_| {});
13996
13997 let mut cx = EditorTestContext::new(cx).await;
13998
13999 let diff_base = r#"
14000 use some::mod1;
14001 use some::mod2;
14002
14003 const A: u32 = 42;
14004
14005 fn main() {
14006 println!("hello");
14007
14008 println!("world");
14009 }
14010 "#
14011 .unindent();
14012 executor.run_until_parked();
14013 cx.set_state(
14014 &r#"
14015 use some::mod1;
14016 use some::mod2;
14017
14018 const A: u32 = 42;
14019 const B: u32 = 42;
14020 const C: u32 = 42;
14021 ˇ
14022
14023 fn main() {
14024 println!("hello");
14025
14026 println!("world");
14027 }
14028 "#
14029 .unindent(),
14030 );
14031
14032 cx.set_head_text(&diff_base);
14033 executor.run_until_parked();
14034
14035 cx.update_editor(|editor, window, cx| {
14036 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14037 });
14038 executor.run_until_parked();
14039
14040 cx.assert_state_with_diff(
14041 r#"
14042 use some::mod1;
14043 use some::mod2;
14044
14045 const A: u32 = 42;
14046 + const B: u32 = 42;
14047 + const C: u32 = 42;
14048 + ˇ
14049
14050 fn main() {
14051 println!("hello");
14052
14053 println!("world");
14054 }
14055 "#
14056 .unindent(),
14057 );
14058
14059 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14060 executor.run_until_parked();
14061
14062 cx.assert_state_with_diff(
14063 r#"
14064 use some::mod1;
14065 use some::mod2;
14066
14067 const A: u32 = 42;
14068 + const B: u32 = 42;
14069 + const C: u32 = 42;
14070 + const D: u32 = 42;
14071 + ˇ
14072
14073 fn main() {
14074 println!("hello");
14075
14076 println!("world");
14077 }
14078 "#
14079 .unindent(),
14080 );
14081
14082 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14083 executor.run_until_parked();
14084
14085 cx.assert_state_with_diff(
14086 r#"
14087 use some::mod1;
14088 use some::mod2;
14089
14090 const A: u32 = 42;
14091 + const B: u32 = 42;
14092 + const C: u32 = 42;
14093 + const D: u32 = 42;
14094 + const E: u32 = 42;
14095 + ˇ
14096
14097 fn main() {
14098 println!("hello");
14099
14100 println!("world");
14101 }
14102 "#
14103 .unindent(),
14104 );
14105
14106 cx.update_editor(|editor, window, cx| {
14107 editor.delete_line(&DeleteLine, window, cx);
14108 });
14109 executor.run_until_parked();
14110
14111 cx.assert_state_with_diff(
14112 r#"
14113 use some::mod1;
14114 use some::mod2;
14115
14116 const A: u32 = 42;
14117 + const B: u32 = 42;
14118 + const C: u32 = 42;
14119 + const D: u32 = 42;
14120 + const E: u32 = 42;
14121 ˇ
14122 fn main() {
14123 println!("hello");
14124
14125 println!("world");
14126 }
14127 "#
14128 .unindent(),
14129 );
14130
14131 cx.update_editor(|editor, window, cx| {
14132 editor.move_up(&MoveUp, window, cx);
14133 editor.delete_line(&DeleteLine, window, cx);
14134 editor.move_up(&MoveUp, window, cx);
14135 editor.delete_line(&DeleteLine, window, cx);
14136 editor.move_up(&MoveUp, window, cx);
14137 editor.delete_line(&DeleteLine, window, cx);
14138 });
14139 executor.run_until_parked();
14140 cx.assert_state_with_diff(
14141 r#"
14142 use some::mod1;
14143 use some::mod2;
14144
14145 const A: u32 = 42;
14146 + const B: u32 = 42;
14147 ˇ
14148 fn main() {
14149 println!("hello");
14150
14151 println!("world");
14152 }
14153 "#
14154 .unindent(),
14155 );
14156
14157 cx.update_editor(|editor, window, cx| {
14158 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14159 editor.delete_line(&DeleteLine, window, cx);
14160 });
14161 executor.run_until_parked();
14162 cx.assert_state_with_diff(
14163 r#"
14164 ˇ
14165 fn main() {
14166 println!("hello");
14167
14168 println!("world");
14169 }
14170 "#
14171 .unindent(),
14172 );
14173}
14174
14175#[gpui::test]
14176async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14177 init_test(cx, |_| {});
14178
14179 let mut cx = EditorTestContext::new(cx).await;
14180 cx.set_head_text(indoc! { "
14181 one
14182 two
14183 three
14184 four
14185 five
14186 "
14187 });
14188 cx.set_state(indoc! { "
14189 one
14190 ˇthree
14191 five
14192 "});
14193 cx.run_until_parked();
14194 cx.update_editor(|editor, window, cx| {
14195 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14196 });
14197 cx.assert_state_with_diff(
14198 indoc! { "
14199 one
14200 - two
14201 ˇthree
14202 - four
14203 five
14204 "}
14205 .to_string(),
14206 );
14207 cx.update_editor(|editor, window, cx| {
14208 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14209 });
14210
14211 cx.assert_state_with_diff(
14212 indoc! { "
14213 one
14214 ˇthree
14215 five
14216 "}
14217 .to_string(),
14218 );
14219
14220 cx.set_state(indoc! { "
14221 one
14222 ˇTWO
14223 three
14224 four
14225 five
14226 "});
14227 cx.run_until_parked();
14228 cx.update_editor(|editor, window, cx| {
14229 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14230 });
14231
14232 cx.assert_state_with_diff(
14233 indoc! { "
14234 one
14235 - two
14236 + ˇTWO
14237 three
14238 four
14239 five
14240 "}
14241 .to_string(),
14242 );
14243 cx.update_editor(|editor, window, cx| {
14244 editor.move_up(&Default::default(), window, cx);
14245 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14246 });
14247 cx.assert_state_with_diff(
14248 indoc! { "
14249 one
14250 ˇTWO
14251 three
14252 four
14253 five
14254 "}
14255 .to_string(),
14256 );
14257}
14258
14259#[gpui::test]
14260async fn test_edits_around_expanded_deletion_hunks(
14261 executor: BackgroundExecutor,
14262 cx: &mut TestAppContext,
14263) {
14264 init_test(cx, |_| {});
14265
14266 let mut cx = EditorTestContext::new(cx).await;
14267
14268 let diff_base = r#"
14269 use some::mod1;
14270 use some::mod2;
14271
14272 const A: u32 = 42;
14273 const B: u32 = 42;
14274 const C: u32 = 42;
14275
14276
14277 fn main() {
14278 println!("hello");
14279
14280 println!("world");
14281 }
14282 "#
14283 .unindent();
14284 executor.run_until_parked();
14285 cx.set_state(
14286 &r#"
14287 use some::mod1;
14288 use some::mod2;
14289
14290 ˇconst B: u32 = 42;
14291 const C: u32 = 42;
14292
14293
14294 fn main() {
14295 println!("hello");
14296
14297 println!("world");
14298 }
14299 "#
14300 .unindent(),
14301 );
14302
14303 cx.set_head_text(&diff_base);
14304 executor.run_until_parked();
14305
14306 cx.update_editor(|editor, window, cx| {
14307 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14308 });
14309 executor.run_until_parked();
14310
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.delete_line(&DeleteLine, 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
14344 fn main() {
14345 println!("hello");
14346
14347 println!("world");
14348 }
14349 "#
14350 .unindent(),
14351 );
14352
14353 cx.update_editor(|editor, window, cx| {
14354 editor.delete_line(&DeleteLine, window, cx);
14355 });
14356 executor.run_until_parked();
14357 cx.assert_state_with_diff(
14358 r#"
14359 use some::mod1;
14360 use some::mod2;
14361
14362 - const A: u32 = 42;
14363 - const B: u32 = 42;
14364 - const C: u32 = 42;
14365 ˇ
14366
14367 fn main() {
14368 println!("hello");
14369
14370 println!("world");
14371 }
14372 "#
14373 .unindent(),
14374 );
14375
14376 cx.update_editor(|editor, window, cx| {
14377 editor.handle_input("replacement", window, cx);
14378 });
14379 executor.run_until_parked();
14380 cx.assert_state_with_diff(
14381 r#"
14382 use some::mod1;
14383 use some::mod2;
14384
14385 - const A: u32 = 42;
14386 - const B: u32 = 42;
14387 - const C: u32 = 42;
14388 -
14389 + replacementˇ
14390
14391 fn main() {
14392 println!("hello");
14393
14394 println!("world");
14395 }
14396 "#
14397 .unindent(),
14398 );
14399}
14400
14401#[gpui::test]
14402async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14403 init_test(cx, |_| {});
14404
14405 let mut cx = EditorTestContext::new(cx).await;
14406
14407 let base_text = r#"
14408 one
14409 two
14410 three
14411 four
14412 five
14413 "#
14414 .unindent();
14415 executor.run_until_parked();
14416 cx.set_state(
14417 &r#"
14418 one
14419 two
14420 fˇour
14421 five
14422 "#
14423 .unindent(),
14424 );
14425
14426 cx.set_head_text(&base_text);
14427 executor.run_until_parked();
14428
14429 cx.update_editor(|editor, window, cx| {
14430 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14431 });
14432 executor.run_until_parked();
14433
14434 cx.assert_state_with_diff(
14435 r#"
14436 one
14437 two
14438 - three
14439 fˇour
14440 five
14441 "#
14442 .unindent(),
14443 );
14444
14445 cx.update_editor(|editor, window, cx| {
14446 editor.backspace(&Backspace, window, cx);
14447 editor.backspace(&Backspace, window, cx);
14448 });
14449 executor.run_until_parked();
14450 cx.assert_state_with_diff(
14451 r#"
14452 one
14453 two
14454 - threeˇ
14455 - four
14456 + our
14457 five
14458 "#
14459 .unindent(),
14460 );
14461}
14462
14463#[gpui::test]
14464async fn test_edit_after_expanded_modification_hunk(
14465 executor: BackgroundExecutor,
14466 cx: &mut TestAppContext,
14467) {
14468 init_test(cx, |_| {});
14469
14470 let mut cx = EditorTestContext::new(cx).await;
14471
14472 let diff_base = r#"
14473 use some::mod1;
14474 use some::mod2;
14475
14476 const A: u32 = 42;
14477 const B: u32 = 42;
14478 const C: u32 = 42;
14479 const D: u32 = 42;
14480
14481
14482 fn main() {
14483 println!("hello");
14484
14485 println!("world");
14486 }"#
14487 .unindent();
14488
14489 cx.set_state(
14490 &r#"
14491 use some::mod1;
14492 use some::mod2;
14493
14494 const A: u32 = 42;
14495 const B: u32 = 42;
14496 const C: u32 = 43ˇ
14497 const D: u32 = 42;
14498
14499
14500 fn main() {
14501 println!("hello");
14502
14503 println!("world");
14504 }"#
14505 .unindent(),
14506 );
14507
14508 cx.set_head_text(&diff_base);
14509 executor.run_until_parked();
14510 cx.update_editor(|editor, window, cx| {
14511 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14512 });
14513 executor.run_until_parked();
14514
14515 cx.assert_state_with_diff(
14516 r#"
14517 use some::mod1;
14518 use some::mod2;
14519
14520 const A: u32 = 42;
14521 const B: u32 = 42;
14522 - const C: u32 = 42;
14523 + const C: u32 = 43ˇ
14524 const D: u32 = 42;
14525
14526
14527 fn main() {
14528 println!("hello");
14529
14530 println!("world");
14531 }"#
14532 .unindent(),
14533 );
14534
14535 cx.update_editor(|editor, window, cx| {
14536 editor.handle_input("\nnew_line\n", window, cx);
14537 });
14538 executor.run_until_parked();
14539
14540 cx.assert_state_with_diff(
14541 r#"
14542 use some::mod1;
14543 use some::mod2;
14544
14545 const A: u32 = 42;
14546 const B: u32 = 42;
14547 - const C: u32 = 42;
14548 + const C: u32 = 43
14549 + new_line
14550 + ˇ
14551 const D: u32 = 42;
14552
14553
14554 fn main() {
14555 println!("hello");
14556
14557 println!("world");
14558 }"#
14559 .unindent(),
14560 );
14561}
14562
14563#[gpui::test]
14564async fn test_stage_and_unstage_added_file_hunk(
14565 executor: BackgroundExecutor,
14566 cx: &mut TestAppContext,
14567) {
14568 init_test(cx, |_| {});
14569
14570 let mut cx = EditorTestContext::new(cx).await;
14571 cx.update_editor(|editor, _, cx| {
14572 editor.set_expand_all_diff_hunks(cx);
14573 });
14574
14575 let working_copy = r#"
14576 ˇfn main() {
14577 println!("hello, world!");
14578 }
14579 "#
14580 .unindent();
14581
14582 cx.set_state(&working_copy);
14583 executor.run_until_parked();
14584
14585 cx.assert_state_with_diff(
14586 r#"
14587 + ˇfn main() {
14588 + println!("hello, world!");
14589 + }
14590 "#
14591 .unindent(),
14592 );
14593 cx.assert_index_text(None);
14594
14595 cx.update_editor(|editor, window, cx| {
14596 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14597 });
14598 executor.run_until_parked();
14599 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14600 cx.assert_state_with_diff(
14601 r#"
14602 + ˇfn main() {
14603 + println!("hello, world!");
14604 + }
14605 "#
14606 .unindent(),
14607 );
14608
14609 cx.update_editor(|editor, window, cx| {
14610 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14611 });
14612 executor.run_until_parked();
14613 cx.assert_index_text(None);
14614}
14615
14616async fn setup_indent_guides_editor(
14617 text: &str,
14618 cx: &mut TestAppContext,
14619) -> (BufferId, EditorTestContext) {
14620 init_test(cx, |_| {});
14621
14622 let mut cx = EditorTestContext::new(cx).await;
14623
14624 let buffer_id = cx.update_editor(|editor, window, cx| {
14625 editor.set_text(text, window, cx);
14626 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14627
14628 buffer_ids[0]
14629 });
14630
14631 (buffer_id, cx)
14632}
14633
14634fn assert_indent_guides(
14635 range: Range<u32>,
14636 expected: Vec<IndentGuide>,
14637 active_indices: Option<Vec<usize>>,
14638 cx: &mut EditorTestContext,
14639) {
14640 let indent_guides = cx.update_editor(|editor, window, cx| {
14641 let snapshot = editor.snapshot(window, cx).display_snapshot;
14642 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14643 editor,
14644 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14645 true,
14646 &snapshot,
14647 cx,
14648 );
14649
14650 indent_guides.sort_by(|a, b| {
14651 a.depth.cmp(&b.depth).then(
14652 a.start_row
14653 .cmp(&b.start_row)
14654 .then(a.end_row.cmp(&b.end_row)),
14655 )
14656 });
14657 indent_guides
14658 });
14659
14660 if let Some(expected) = active_indices {
14661 let active_indices = cx.update_editor(|editor, window, cx| {
14662 let snapshot = editor.snapshot(window, cx).display_snapshot;
14663 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14664 });
14665
14666 assert_eq!(
14667 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14668 expected,
14669 "Active indent guide indices do not match"
14670 );
14671 }
14672
14673 assert_eq!(indent_guides, expected, "Indent guides do not match");
14674}
14675
14676fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14677 IndentGuide {
14678 buffer_id,
14679 start_row: MultiBufferRow(start_row),
14680 end_row: MultiBufferRow(end_row),
14681 depth,
14682 tab_size: 4,
14683 settings: IndentGuideSettings {
14684 enabled: true,
14685 line_width: 1,
14686 active_line_width: 1,
14687 ..Default::default()
14688 },
14689 }
14690}
14691
14692#[gpui::test]
14693async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14694 let (buffer_id, mut cx) = setup_indent_guides_editor(
14695 &"
14696 fn main() {
14697 let a = 1;
14698 }"
14699 .unindent(),
14700 cx,
14701 )
14702 .await;
14703
14704 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14705}
14706
14707#[gpui::test]
14708async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14709 let (buffer_id, mut cx) = setup_indent_guides_editor(
14710 &"
14711 fn main() {
14712 let a = 1;
14713 let b = 2;
14714 }"
14715 .unindent(),
14716 cx,
14717 )
14718 .await;
14719
14720 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14721}
14722
14723#[gpui::test]
14724async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14725 let (buffer_id, mut cx) = setup_indent_guides_editor(
14726 &"
14727 fn main() {
14728 let a = 1;
14729 if a == 3 {
14730 let b = 2;
14731 } else {
14732 let c = 3;
14733 }
14734 }"
14735 .unindent(),
14736 cx,
14737 )
14738 .await;
14739
14740 assert_indent_guides(
14741 0..8,
14742 vec![
14743 indent_guide(buffer_id, 1, 6, 0),
14744 indent_guide(buffer_id, 3, 3, 1),
14745 indent_guide(buffer_id, 5, 5, 1),
14746 ],
14747 None,
14748 &mut cx,
14749 );
14750}
14751
14752#[gpui::test]
14753async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14754 let (buffer_id, mut cx) = setup_indent_guides_editor(
14755 &"
14756 fn main() {
14757 let a = 1;
14758 let b = 2;
14759 let c = 3;
14760 }"
14761 .unindent(),
14762 cx,
14763 )
14764 .await;
14765
14766 assert_indent_guides(
14767 0..5,
14768 vec![
14769 indent_guide(buffer_id, 1, 3, 0),
14770 indent_guide(buffer_id, 2, 2, 1),
14771 ],
14772 None,
14773 &mut cx,
14774 );
14775}
14776
14777#[gpui::test]
14778async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14779 let (buffer_id, mut cx) = setup_indent_guides_editor(
14780 &"
14781 fn main() {
14782 let a = 1;
14783
14784 let c = 3;
14785 }"
14786 .unindent(),
14787 cx,
14788 )
14789 .await;
14790
14791 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14792}
14793
14794#[gpui::test]
14795async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14796 let (buffer_id, mut cx) = setup_indent_guides_editor(
14797 &"
14798 fn main() {
14799 let a = 1;
14800
14801 let c = 3;
14802
14803 if a == 3 {
14804 let b = 2;
14805 } else {
14806 let c = 3;
14807 }
14808 }"
14809 .unindent(),
14810 cx,
14811 )
14812 .await;
14813
14814 assert_indent_guides(
14815 0..11,
14816 vec![
14817 indent_guide(buffer_id, 1, 9, 0),
14818 indent_guide(buffer_id, 6, 6, 1),
14819 indent_guide(buffer_id, 8, 8, 1),
14820 ],
14821 None,
14822 &mut cx,
14823 );
14824}
14825
14826#[gpui::test]
14827async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14828 let (buffer_id, mut cx) = setup_indent_guides_editor(
14829 &"
14830 fn main() {
14831 let a = 1;
14832
14833 let c = 3;
14834
14835 if a == 3 {
14836 let b = 2;
14837 } else {
14838 let c = 3;
14839 }
14840 }"
14841 .unindent(),
14842 cx,
14843 )
14844 .await;
14845
14846 assert_indent_guides(
14847 1..11,
14848 vec![
14849 indent_guide(buffer_id, 1, 9, 0),
14850 indent_guide(buffer_id, 6, 6, 1),
14851 indent_guide(buffer_id, 8, 8, 1),
14852 ],
14853 None,
14854 &mut cx,
14855 );
14856}
14857
14858#[gpui::test]
14859async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14860 let (buffer_id, mut cx) = setup_indent_guides_editor(
14861 &"
14862 fn main() {
14863 let a = 1;
14864
14865 let c = 3;
14866
14867 if a == 3 {
14868 let b = 2;
14869 } else {
14870 let c = 3;
14871 }
14872 }"
14873 .unindent(),
14874 cx,
14875 )
14876 .await;
14877
14878 assert_indent_guides(
14879 1..10,
14880 vec![
14881 indent_guide(buffer_id, 1, 9, 0),
14882 indent_guide(buffer_id, 6, 6, 1),
14883 indent_guide(buffer_id, 8, 8, 1),
14884 ],
14885 None,
14886 &mut cx,
14887 );
14888}
14889
14890#[gpui::test]
14891async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14892 let (buffer_id, mut cx) = setup_indent_guides_editor(
14893 &"
14894 block1
14895 block2
14896 block3
14897 block4
14898 block2
14899 block1
14900 block1"
14901 .unindent(),
14902 cx,
14903 )
14904 .await;
14905
14906 assert_indent_guides(
14907 1..10,
14908 vec![
14909 indent_guide(buffer_id, 1, 4, 0),
14910 indent_guide(buffer_id, 2, 3, 1),
14911 indent_guide(buffer_id, 3, 3, 2),
14912 ],
14913 None,
14914 &mut cx,
14915 );
14916}
14917
14918#[gpui::test]
14919async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14920 let (buffer_id, mut cx) = setup_indent_guides_editor(
14921 &"
14922 block1
14923 block2
14924 block3
14925
14926 block1
14927 block1"
14928 .unindent(),
14929 cx,
14930 )
14931 .await;
14932
14933 assert_indent_guides(
14934 0..6,
14935 vec![
14936 indent_guide(buffer_id, 1, 2, 0),
14937 indent_guide(buffer_id, 2, 2, 1),
14938 ],
14939 None,
14940 &mut cx,
14941 );
14942}
14943
14944#[gpui::test]
14945async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14946 let (buffer_id, mut cx) = setup_indent_guides_editor(
14947 &"
14948 block1
14949
14950
14951
14952 block2
14953 "
14954 .unindent(),
14955 cx,
14956 )
14957 .await;
14958
14959 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14960}
14961
14962#[gpui::test]
14963async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14964 let (buffer_id, mut cx) = setup_indent_guides_editor(
14965 &"
14966 def a:
14967 \tb = 3
14968 \tif True:
14969 \t\tc = 4
14970 \t\td = 5
14971 \tprint(b)
14972 "
14973 .unindent(),
14974 cx,
14975 )
14976 .await;
14977
14978 assert_indent_guides(
14979 0..6,
14980 vec![
14981 indent_guide(buffer_id, 1, 6, 0),
14982 indent_guide(buffer_id, 3, 4, 1),
14983 ],
14984 None,
14985 &mut cx,
14986 );
14987}
14988
14989#[gpui::test]
14990async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14991 let (buffer_id, mut cx) = setup_indent_guides_editor(
14992 &"
14993 fn main() {
14994 let a = 1;
14995 }"
14996 .unindent(),
14997 cx,
14998 )
14999 .await;
15000
15001 cx.update_editor(|editor, window, cx| {
15002 editor.change_selections(None, window, cx, |s| {
15003 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15004 });
15005 });
15006
15007 assert_indent_guides(
15008 0..3,
15009 vec![indent_guide(buffer_id, 1, 1, 0)],
15010 Some(vec![0]),
15011 &mut cx,
15012 );
15013}
15014
15015#[gpui::test]
15016async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15017 let (buffer_id, mut cx) = setup_indent_guides_editor(
15018 &"
15019 fn main() {
15020 if 1 == 2 {
15021 let a = 1;
15022 }
15023 }"
15024 .unindent(),
15025 cx,
15026 )
15027 .await;
15028
15029 cx.update_editor(|editor, window, cx| {
15030 editor.change_selections(None, window, cx, |s| {
15031 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15032 });
15033 });
15034
15035 assert_indent_guides(
15036 0..4,
15037 vec![
15038 indent_guide(buffer_id, 1, 3, 0),
15039 indent_guide(buffer_id, 2, 2, 1),
15040 ],
15041 Some(vec![1]),
15042 &mut cx,
15043 );
15044
15045 cx.update_editor(|editor, window, cx| {
15046 editor.change_selections(None, window, cx, |s| {
15047 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15048 });
15049 });
15050
15051 assert_indent_guides(
15052 0..4,
15053 vec![
15054 indent_guide(buffer_id, 1, 3, 0),
15055 indent_guide(buffer_id, 2, 2, 1),
15056 ],
15057 Some(vec![1]),
15058 &mut cx,
15059 );
15060
15061 cx.update_editor(|editor, window, cx| {
15062 editor.change_selections(None, window, cx, |s| {
15063 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15064 });
15065 });
15066
15067 assert_indent_guides(
15068 0..4,
15069 vec![
15070 indent_guide(buffer_id, 1, 3, 0),
15071 indent_guide(buffer_id, 2, 2, 1),
15072 ],
15073 Some(vec![0]),
15074 &mut cx,
15075 );
15076}
15077
15078#[gpui::test]
15079async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15080 let (buffer_id, mut cx) = setup_indent_guides_editor(
15081 &"
15082 fn main() {
15083 let a = 1;
15084
15085 let b = 2;
15086 }"
15087 .unindent(),
15088 cx,
15089 )
15090 .await;
15091
15092 cx.update_editor(|editor, window, cx| {
15093 editor.change_selections(None, window, cx, |s| {
15094 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15095 });
15096 });
15097
15098 assert_indent_guides(
15099 0..5,
15100 vec![indent_guide(buffer_id, 1, 3, 0)],
15101 Some(vec![0]),
15102 &mut cx,
15103 );
15104}
15105
15106#[gpui::test]
15107async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15108 let (buffer_id, mut cx) = setup_indent_guides_editor(
15109 &"
15110 def m:
15111 a = 1
15112 pass"
15113 .unindent(),
15114 cx,
15115 )
15116 .await;
15117
15118 cx.update_editor(|editor, window, cx| {
15119 editor.change_selections(None, window, cx, |s| {
15120 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15121 });
15122 });
15123
15124 assert_indent_guides(
15125 0..3,
15126 vec![indent_guide(buffer_id, 1, 2, 0)],
15127 Some(vec![0]),
15128 &mut cx,
15129 );
15130}
15131
15132#[gpui::test]
15133async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15134 init_test(cx, |_| {});
15135 let mut cx = EditorTestContext::new(cx).await;
15136 let text = indoc! {
15137 "
15138 impl A {
15139 fn b() {
15140 0;
15141 3;
15142 5;
15143 6;
15144 7;
15145 }
15146 }
15147 "
15148 };
15149 let base_text = indoc! {
15150 "
15151 impl A {
15152 fn b() {
15153 0;
15154 1;
15155 2;
15156 3;
15157 4;
15158 }
15159 fn c() {
15160 5;
15161 6;
15162 7;
15163 }
15164 }
15165 "
15166 };
15167
15168 cx.update_editor(|editor, window, cx| {
15169 editor.set_text(text, window, cx);
15170
15171 editor.buffer().update(cx, |multibuffer, cx| {
15172 let buffer = multibuffer.as_singleton().unwrap();
15173 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15174
15175 multibuffer.set_all_diff_hunks_expanded(cx);
15176 multibuffer.add_diff(diff, cx);
15177
15178 buffer.read(cx).remote_id()
15179 })
15180 });
15181 cx.run_until_parked();
15182
15183 cx.assert_state_with_diff(
15184 indoc! { "
15185 impl A {
15186 fn b() {
15187 0;
15188 - 1;
15189 - 2;
15190 3;
15191 - 4;
15192 - }
15193 - fn c() {
15194 5;
15195 6;
15196 7;
15197 }
15198 }
15199 ˇ"
15200 }
15201 .to_string(),
15202 );
15203
15204 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15205 editor
15206 .snapshot(window, cx)
15207 .buffer_snapshot
15208 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15209 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15210 .collect::<Vec<_>>()
15211 });
15212 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15213 assert_eq!(
15214 actual_guides,
15215 vec![
15216 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15217 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15218 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15219 ]
15220 );
15221}
15222
15223#[gpui::test]
15224async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15225 init_test(cx, |_| {});
15226 let mut cx = EditorTestContext::new(cx).await;
15227
15228 let diff_base = r#"
15229 a
15230 b
15231 c
15232 "#
15233 .unindent();
15234
15235 cx.set_state(
15236 &r#"
15237 ˇA
15238 b
15239 C
15240 "#
15241 .unindent(),
15242 );
15243 cx.set_head_text(&diff_base);
15244 cx.update_editor(|editor, window, cx| {
15245 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15246 });
15247 executor.run_until_parked();
15248
15249 let both_hunks_expanded = r#"
15250 - a
15251 + ˇA
15252 b
15253 - c
15254 + C
15255 "#
15256 .unindent();
15257
15258 cx.assert_state_with_diff(both_hunks_expanded.clone());
15259
15260 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15261 let snapshot = editor.snapshot(window, cx);
15262 let hunks = editor
15263 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15264 .collect::<Vec<_>>();
15265 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15266 let buffer_id = hunks[0].buffer_id;
15267 hunks
15268 .into_iter()
15269 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15270 .collect::<Vec<_>>()
15271 });
15272 assert_eq!(hunk_ranges.len(), 2);
15273
15274 cx.update_editor(|editor, _, cx| {
15275 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15276 });
15277 executor.run_until_parked();
15278
15279 let second_hunk_expanded = r#"
15280 ˇA
15281 b
15282 - c
15283 + C
15284 "#
15285 .unindent();
15286
15287 cx.assert_state_with_diff(second_hunk_expanded);
15288
15289 cx.update_editor(|editor, _, cx| {
15290 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15291 });
15292 executor.run_until_parked();
15293
15294 cx.assert_state_with_diff(both_hunks_expanded.clone());
15295
15296 cx.update_editor(|editor, _, cx| {
15297 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15298 });
15299 executor.run_until_parked();
15300
15301 let first_hunk_expanded = r#"
15302 - a
15303 + ˇA
15304 b
15305 C
15306 "#
15307 .unindent();
15308
15309 cx.assert_state_with_diff(first_hunk_expanded);
15310
15311 cx.update_editor(|editor, _, cx| {
15312 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15313 });
15314 executor.run_until_parked();
15315
15316 cx.assert_state_with_diff(both_hunks_expanded);
15317
15318 cx.set_state(
15319 &r#"
15320 ˇA
15321 b
15322 "#
15323 .unindent(),
15324 );
15325 cx.run_until_parked();
15326
15327 // TODO this cursor position seems bad
15328 cx.assert_state_with_diff(
15329 r#"
15330 - ˇa
15331 + A
15332 b
15333 "#
15334 .unindent(),
15335 );
15336
15337 cx.update_editor(|editor, window, cx| {
15338 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15339 });
15340
15341 cx.assert_state_with_diff(
15342 r#"
15343 - ˇa
15344 + A
15345 b
15346 - c
15347 "#
15348 .unindent(),
15349 );
15350
15351 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15352 let snapshot = editor.snapshot(window, cx);
15353 let hunks = editor
15354 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15355 .collect::<Vec<_>>();
15356 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15357 let buffer_id = hunks[0].buffer_id;
15358 hunks
15359 .into_iter()
15360 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15361 .collect::<Vec<_>>()
15362 });
15363 assert_eq!(hunk_ranges.len(), 2);
15364
15365 cx.update_editor(|editor, _, cx| {
15366 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15367 });
15368 executor.run_until_parked();
15369
15370 cx.assert_state_with_diff(
15371 r#"
15372 - ˇa
15373 + A
15374 b
15375 "#
15376 .unindent(),
15377 );
15378}
15379
15380#[gpui::test]
15381async fn test_toggle_deletion_hunk_at_start_of_file(
15382 executor: BackgroundExecutor,
15383 cx: &mut TestAppContext,
15384) {
15385 init_test(cx, |_| {});
15386 let mut cx = EditorTestContext::new(cx).await;
15387
15388 let diff_base = r#"
15389 a
15390 b
15391 c
15392 "#
15393 .unindent();
15394
15395 cx.set_state(
15396 &r#"
15397 ˇb
15398 c
15399 "#
15400 .unindent(),
15401 );
15402 cx.set_head_text(&diff_base);
15403 cx.update_editor(|editor, window, cx| {
15404 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15405 });
15406 executor.run_until_parked();
15407
15408 let hunk_expanded = r#"
15409 - a
15410 ˇb
15411 c
15412 "#
15413 .unindent();
15414
15415 cx.assert_state_with_diff(hunk_expanded.clone());
15416
15417 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15418 let snapshot = editor.snapshot(window, cx);
15419 let hunks = editor
15420 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15421 .collect::<Vec<_>>();
15422 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15423 let buffer_id = hunks[0].buffer_id;
15424 hunks
15425 .into_iter()
15426 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15427 .collect::<Vec<_>>()
15428 });
15429 assert_eq!(hunk_ranges.len(), 1);
15430
15431 cx.update_editor(|editor, _, cx| {
15432 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15433 });
15434 executor.run_until_parked();
15435
15436 let hunk_collapsed = r#"
15437 ˇb
15438 c
15439 "#
15440 .unindent();
15441
15442 cx.assert_state_with_diff(hunk_collapsed);
15443
15444 cx.update_editor(|editor, _, cx| {
15445 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15446 });
15447 executor.run_until_parked();
15448
15449 cx.assert_state_with_diff(hunk_expanded.clone());
15450}
15451
15452#[gpui::test]
15453async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15454 init_test(cx, |_| {});
15455
15456 let fs = FakeFs::new(cx.executor());
15457 fs.insert_tree(
15458 path!("/test"),
15459 json!({
15460 ".git": {},
15461 "file-1": "ONE\n",
15462 "file-2": "TWO\n",
15463 "file-3": "THREE\n",
15464 }),
15465 )
15466 .await;
15467
15468 fs.set_head_for_repo(
15469 path!("/test/.git").as_ref(),
15470 &[
15471 ("file-1".into(), "one\n".into()),
15472 ("file-2".into(), "two\n".into()),
15473 ("file-3".into(), "three\n".into()),
15474 ],
15475 );
15476
15477 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15478 let mut buffers = vec![];
15479 for i in 1..=3 {
15480 let buffer = project
15481 .update(cx, |project, cx| {
15482 let path = format!(path!("/test/file-{}"), i);
15483 project.open_local_buffer(path, cx)
15484 })
15485 .await
15486 .unwrap();
15487 buffers.push(buffer);
15488 }
15489
15490 let multibuffer = cx.new(|cx| {
15491 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15492 multibuffer.set_all_diff_hunks_expanded(cx);
15493 for buffer in &buffers {
15494 let snapshot = buffer.read(cx).snapshot();
15495 multibuffer.set_excerpts_for_path(
15496 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15497 buffer.clone(),
15498 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15499 DEFAULT_MULTIBUFFER_CONTEXT,
15500 cx,
15501 );
15502 }
15503 multibuffer
15504 });
15505
15506 let editor = cx.add_window(|window, cx| {
15507 Editor::new(
15508 EditorMode::Full,
15509 multibuffer,
15510 Some(project),
15511 true,
15512 window,
15513 cx,
15514 )
15515 });
15516 cx.run_until_parked();
15517
15518 let snapshot = editor
15519 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15520 .unwrap();
15521 let hunks = snapshot
15522 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15523 .map(|hunk| match hunk {
15524 DisplayDiffHunk::Unfolded {
15525 display_row_range, ..
15526 } => display_row_range,
15527 DisplayDiffHunk::Folded { .. } => unreachable!(),
15528 })
15529 .collect::<Vec<_>>();
15530 assert_eq!(
15531 hunks,
15532 [
15533 DisplayRow(3)..DisplayRow(5),
15534 DisplayRow(10)..DisplayRow(12),
15535 DisplayRow(17)..DisplayRow(19),
15536 ]
15537 );
15538}
15539
15540#[gpui::test]
15541async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15542 init_test(cx, |_| {});
15543
15544 let mut cx = EditorTestContext::new(cx).await;
15545 cx.set_head_text(indoc! { "
15546 one
15547 two
15548 three
15549 four
15550 five
15551 "
15552 });
15553 cx.set_index_text(indoc! { "
15554 one
15555 two
15556 three
15557 four
15558 five
15559 "
15560 });
15561 cx.set_state(indoc! {"
15562 one
15563 TWO
15564 ˇTHREE
15565 FOUR
15566 five
15567 "});
15568 cx.run_until_parked();
15569 cx.update_editor(|editor, window, cx| {
15570 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15571 });
15572 cx.run_until_parked();
15573 cx.assert_index_text(Some(indoc! {"
15574 one
15575 TWO
15576 THREE
15577 FOUR
15578 five
15579 "}));
15580 cx.set_state(indoc! { "
15581 one
15582 TWO
15583 ˇTHREE-HUNDRED
15584 FOUR
15585 five
15586 "});
15587 cx.run_until_parked();
15588 cx.update_editor(|editor, window, cx| {
15589 let snapshot = editor.snapshot(window, cx);
15590 let hunks = editor
15591 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15592 .collect::<Vec<_>>();
15593 assert_eq!(hunks.len(), 1);
15594 assert_eq!(
15595 hunks[0].status(),
15596 DiffHunkStatus {
15597 kind: DiffHunkStatusKind::Modified,
15598 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15599 }
15600 );
15601
15602 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15603 });
15604 cx.run_until_parked();
15605 cx.assert_index_text(Some(indoc! {"
15606 one
15607 TWO
15608 THREE-HUNDRED
15609 FOUR
15610 five
15611 "}));
15612}
15613
15614#[gpui::test]
15615fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15616 init_test(cx, |_| {});
15617
15618 let editor = cx.add_window(|window, cx| {
15619 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15620 build_editor(buffer, window, cx)
15621 });
15622
15623 let render_args = Arc::new(Mutex::new(None));
15624 let snapshot = editor
15625 .update(cx, |editor, window, cx| {
15626 let snapshot = editor.buffer().read(cx).snapshot(cx);
15627 let range =
15628 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15629
15630 struct RenderArgs {
15631 row: MultiBufferRow,
15632 folded: bool,
15633 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15634 }
15635
15636 let crease = Crease::inline(
15637 range,
15638 FoldPlaceholder::test(),
15639 {
15640 let toggle_callback = render_args.clone();
15641 move |row, folded, callback, _window, _cx| {
15642 *toggle_callback.lock() = Some(RenderArgs {
15643 row,
15644 folded,
15645 callback,
15646 });
15647 div()
15648 }
15649 },
15650 |_row, _folded, _window, _cx| div(),
15651 );
15652
15653 editor.insert_creases(Some(crease), cx);
15654 let snapshot = editor.snapshot(window, cx);
15655 let _div = snapshot.render_crease_toggle(
15656 MultiBufferRow(1),
15657 false,
15658 cx.entity().clone(),
15659 window,
15660 cx,
15661 );
15662 snapshot
15663 })
15664 .unwrap();
15665
15666 let render_args = render_args.lock().take().unwrap();
15667 assert_eq!(render_args.row, MultiBufferRow(1));
15668 assert!(!render_args.folded);
15669 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15670
15671 cx.update_window(*editor, |_, window, cx| {
15672 (render_args.callback)(true, window, cx)
15673 })
15674 .unwrap();
15675 let snapshot = editor
15676 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15677 .unwrap();
15678 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15679
15680 cx.update_window(*editor, |_, window, cx| {
15681 (render_args.callback)(false, window, cx)
15682 })
15683 .unwrap();
15684 let snapshot = editor
15685 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15686 .unwrap();
15687 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15688}
15689
15690#[gpui::test]
15691async fn test_input_text(cx: &mut TestAppContext) {
15692 init_test(cx, |_| {});
15693 let mut cx = EditorTestContext::new(cx).await;
15694
15695 cx.set_state(
15696 &r#"ˇone
15697 two
15698
15699 three
15700 fourˇ
15701 five
15702
15703 siˇx"#
15704 .unindent(),
15705 );
15706
15707 cx.dispatch_action(HandleInput(String::new()));
15708 cx.assert_editor_state(
15709 &r#"ˇone
15710 two
15711
15712 three
15713 fourˇ
15714 five
15715
15716 siˇx"#
15717 .unindent(),
15718 );
15719
15720 cx.dispatch_action(HandleInput("AAAA".to_string()));
15721 cx.assert_editor_state(
15722 &r#"AAAAˇone
15723 two
15724
15725 three
15726 fourAAAAˇ
15727 five
15728
15729 siAAAAˇx"#
15730 .unindent(),
15731 );
15732}
15733
15734#[gpui::test]
15735async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15736 init_test(cx, |_| {});
15737
15738 let mut cx = EditorTestContext::new(cx).await;
15739 cx.set_state(
15740 r#"let foo = 1;
15741let foo = 2;
15742let foo = 3;
15743let fooˇ = 4;
15744let foo = 5;
15745let foo = 6;
15746let foo = 7;
15747let foo = 8;
15748let foo = 9;
15749let foo = 10;
15750let foo = 11;
15751let foo = 12;
15752let foo = 13;
15753let foo = 14;
15754let foo = 15;"#,
15755 );
15756
15757 cx.update_editor(|e, window, cx| {
15758 assert_eq!(
15759 e.next_scroll_position,
15760 NextScrollCursorCenterTopBottom::Center,
15761 "Default next scroll direction is center",
15762 );
15763
15764 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15765 assert_eq!(
15766 e.next_scroll_position,
15767 NextScrollCursorCenterTopBottom::Top,
15768 "After center, next scroll direction should be top",
15769 );
15770
15771 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15772 assert_eq!(
15773 e.next_scroll_position,
15774 NextScrollCursorCenterTopBottom::Bottom,
15775 "After top, next scroll direction should be bottom",
15776 );
15777
15778 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15779 assert_eq!(
15780 e.next_scroll_position,
15781 NextScrollCursorCenterTopBottom::Center,
15782 "After bottom, scrolling should start over",
15783 );
15784
15785 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15786 assert_eq!(
15787 e.next_scroll_position,
15788 NextScrollCursorCenterTopBottom::Top,
15789 "Scrolling continues if retriggered fast enough"
15790 );
15791 });
15792
15793 cx.executor()
15794 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15795 cx.executor().run_until_parked();
15796 cx.update_editor(|e, _, _| {
15797 assert_eq!(
15798 e.next_scroll_position,
15799 NextScrollCursorCenterTopBottom::Center,
15800 "If scrolling is not triggered fast enough, it should reset"
15801 );
15802 });
15803}
15804
15805#[gpui::test]
15806async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15807 init_test(cx, |_| {});
15808 let mut cx = EditorLspTestContext::new_rust(
15809 lsp::ServerCapabilities {
15810 definition_provider: Some(lsp::OneOf::Left(true)),
15811 references_provider: Some(lsp::OneOf::Left(true)),
15812 ..lsp::ServerCapabilities::default()
15813 },
15814 cx,
15815 )
15816 .await;
15817
15818 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15819 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15820 move |params, _| async move {
15821 if empty_go_to_definition {
15822 Ok(None)
15823 } else {
15824 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15825 uri: params.text_document_position_params.text_document.uri,
15826 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15827 })))
15828 }
15829 },
15830 );
15831 let references =
15832 cx.lsp
15833 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15834 Ok(Some(vec![lsp::Location {
15835 uri: params.text_document_position.text_document.uri,
15836 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15837 }]))
15838 });
15839 (go_to_definition, references)
15840 };
15841
15842 cx.set_state(
15843 &r#"fn one() {
15844 let mut a = ˇtwo();
15845 }
15846
15847 fn two() {}"#
15848 .unindent(),
15849 );
15850 set_up_lsp_handlers(false, &mut cx);
15851 let navigated = cx
15852 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15853 .await
15854 .expect("Failed to navigate to definition");
15855 assert_eq!(
15856 navigated,
15857 Navigated::Yes,
15858 "Should have navigated to definition from the GetDefinition response"
15859 );
15860 cx.assert_editor_state(
15861 &r#"fn one() {
15862 let mut a = two();
15863 }
15864
15865 fn «twoˇ»() {}"#
15866 .unindent(),
15867 );
15868
15869 let editors = cx.update_workspace(|workspace, _, cx| {
15870 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15871 });
15872 cx.update_editor(|_, _, test_editor_cx| {
15873 assert_eq!(
15874 editors.len(),
15875 1,
15876 "Initially, only one, test, editor should be open in the workspace"
15877 );
15878 assert_eq!(
15879 test_editor_cx.entity(),
15880 editors.last().expect("Asserted len is 1").clone()
15881 );
15882 });
15883
15884 set_up_lsp_handlers(true, &mut cx);
15885 let navigated = cx
15886 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15887 .await
15888 .expect("Failed to navigate to lookup references");
15889 assert_eq!(
15890 navigated,
15891 Navigated::Yes,
15892 "Should have navigated to references as a fallback after empty GoToDefinition response"
15893 );
15894 // We should not change the selections in the existing file,
15895 // if opening another milti buffer with the references
15896 cx.assert_editor_state(
15897 &r#"fn one() {
15898 let mut a = two();
15899 }
15900
15901 fn «twoˇ»() {}"#
15902 .unindent(),
15903 );
15904 let editors = cx.update_workspace(|workspace, _, cx| {
15905 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15906 });
15907 cx.update_editor(|_, _, test_editor_cx| {
15908 assert_eq!(
15909 editors.len(),
15910 2,
15911 "After falling back to references search, we open a new editor with the results"
15912 );
15913 let references_fallback_text = editors
15914 .into_iter()
15915 .find(|new_editor| *new_editor != test_editor_cx.entity())
15916 .expect("Should have one non-test editor now")
15917 .read(test_editor_cx)
15918 .text(test_editor_cx);
15919 assert_eq!(
15920 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15921 "Should use the range from the references response and not the GoToDefinition one"
15922 );
15923 });
15924}
15925
15926#[gpui::test]
15927async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15928 init_test(cx, |_| {});
15929
15930 let language = Arc::new(Language::new(
15931 LanguageConfig::default(),
15932 Some(tree_sitter_rust::LANGUAGE.into()),
15933 ));
15934
15935 let text = r#"
15936 #[cfg(test)]
15937 mod tests() {
15938 #[test]
15939 fn runnable_1() {
15940 let a = 1;
15941 }
15942
15943 #[test]
15944 fn runnable_2() {
15945 let a = 1;
15946 let b = 2;
15947 }
15948 }
15949 "#
15950 .unindent();
15951
15952 let fs = FakeFs::new(cx.executor());
15953 fs.insert_file("/file.rs", Default::default()).await;
15954
15955 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15956 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15957 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15958 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15959 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15960
15961 let editor = cx.new_window_entity(|window, cx| {
15962 Editor::new(
15963 EditorMode::Full,
15964 multi_buffer,
15965 Some(project.clone()),
15966 true,
15967 window,
15968 cx,
15969 )
15970 });
15971
15972 editor.update_in(cx, |editor, window, cx| {
15973 let snapshot = editor.buffer().read(cx).snapshot(cx);
15974 editor.tasks.insert(
15975 (buffer.read(cx).remote_id(), 3),
15976 RunnableTasks {
15977 templates: vec![],
15978 offset: snapshot.anchor_before(43),
15979 column: 0,
15980 extra_variables: HashMap::default(),
15981 context_range: BufferOffset(43)..BufferOffset(85),
15982 },
15983 );
15984 editor.tasks.insert(
15985 (buffer.read(cx).remote_id(), 8),
15986 RunnableTasks {
15987 templates: vec![],
15988 offset: snapshot.anchor_before(86),
15989 column: 0,
15990 extra_variables: HashMap::default(),
15991 context_range: BufferOffset(86)..BufferOffset(191),
15992 },
15993 );
15994
15995 // Test finding task when cursor is inside function body
15996 editor.change_selections(None, window, cx, |s| {
15997 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15998 });
15999 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16000 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16001
16002 // Test finding task when cursor is on function name
16003 editor.change_selections(None, window, cx, |s| {
16004 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16005 });
16006 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16007 assert_eq!(row, 8, "Should find task when cursor is on function name");
16008 });
16009}
16010
16011#[gpui::test]
16012async fn test_folding_buffers(cx: &mut TestAppContext) {
16013 init_test(cx, |_| {});
16014
16015 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16016 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16017 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16018
16019 let fs = FakeFs::new(cx.executor());
16020 fs.insert_tree(
16021 path!("/a"),
16022 json!({
16023 "first.rs": sample_text_1,
16024 "second.rs": sample_text_2,
16025 "third.rs": sample_text_3,
16026 }),
16027 )
16028 .await;
16029 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16030 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16031 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16032 let worktree = project.update(cx, |project, cx| {
16033 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16034 assert_eq!(worktrees.len(), 1);
16035 worktrees.pop().unwrap()
16036 });
16037 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16038
16039 let buffer_1 = project
16040 .update(cx, |project, cx| {
16041 project.open_buffer((worktree_id, "first.rs"), cx)
16042 })
16043 .await
16044 .unwrap();
16045 let buffer_2 = project
16046 .update(cx, |project, cx| {
16047 project.open_buffer((worktree_id, "second.rs"), cx)
16048 })
16049 .await
16050 .unwrap();
16051 let buffer_3 = project
16052 .update(cx, |project, cx| {
16053 project.open_buffer((worktree_id, "third.rs"), cx)
16054 })
16055 .await
16056 .unwrap();
16057
16058 let multi_buffer = cx.new(|cx| {
16059 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16060 multi_buffer.push_excerpts(
16061 buffer_1.clone(),
16062 [
16063 ExcerptRange {
16064 context: Point::new(0, 0)..Point::new(3, 0),
16065 primary: None,
16066 },
16067 ExcerptRange {
16068 context: Point::new(5, 0)..Point::new(7, 0),
16069 primary: None,
16070 },
16071 ExcerptRange {
16072 context: Point::new(9, 0)..Point::new(10, 4),
16073 primary: None,
16074 },
16075 ],
16076 cx,
16077 );
16078 multi_buffer.push_excerpts(
16079 buffer_2.clone(),
16080 [
16081 ExcerptRange {
16082 context: Point::new(0, 0)..Point::new(3, 0),
16083 primary: None,
16084 },
16085 ExcerptRange {
16086 context: Point::new(5, 0)..Point::new(7, 0),
16087 primary: None,
16088 },
16089 ExcerptRange {
16090 context: Point::new(9, 0)..Point::new(10, 4),
16091 primary: None,
16092 },
16093 ],
16094 cx,
16095 );
16096 multi_buffer.push_excerpts(
16097 buffer_3.clone(),
16098 [
16099 ExcerptRange {
16100 context: Point::new(0, 0)..Point::new(3, 0),
16101 primary: None,
16102 },
16103 ExcerptRange {
16104 context: Point::new(5, 0)..Point::new(7, 0),
16105 primary: None,
16106 },
16107 ExcerptRange {
16108 context: Point::new(9, 0)..Point::new(10, 4),
16109 primary: None,
16110 },
16111 ],
16112 cx,
16113 );
16114 multi_buffer
16115 });
16116 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16117 Editor::new(
16118 EditorMode::Full,
16119 multi_buffer.clone(),
16120 Some(project.clone()),
16121 true,
16122 window,
16123 cx,
16124 )
16125 });
16126
16127 assert_eq!(
16128 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16129 "\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",
16130 );
16131
16132 multi_buffer_editor.update(cx, |editor, cx| {
16133 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16134 });
16135 assert_eq!(
16136 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16137 "\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",
16138 "After folding the first buffer, its text should not be displayed"
16139 );
16140
16141 multi_buffer_editor.update(cx, |editor, cx| {
16142 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16143 });
16144 assert_eq!(
16145 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16146 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16147 "After folding the second buffer, its text should not be displayed"
16148 );
16149
16150 multi_buffer_editor.update(cx, |editor, cx| {
16151 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16152 });
16153 assert_eq!(
16154 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16155 "\n\n\n\n\n",
16156 "After folding the third buffer, its text should not be displayed"
16157 );
16158
16159 // Emulate selection inside the fold logic, that should work
16160 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16161 editor
16162 .snapshot(window, cx)
16163 .next_line_boundary(Point::new(0, 4));
16164 });
16165
16166 multi_buffer_editor.update(cx, |editor, cx| {
16167 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16168 });
16169 assert_eq!(
16170 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16171 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16172 "After unfolding the second buffer, its text should be displayed"
16173 );
16174
16175 // Typing inside of buffer 1 causes that buffer to be unfolded.
16176 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16177 assert_eq!(
16178 multi_buffer
16179 .read(cx)
16180 .snapshot(cx)
16181 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16182 .collect::<String>(),
16183 "bbbb"
16184 );
16185 editor.change_selections(None, window, cx, |selections| {
16186 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16187 });
16188 editor.handle_input("B", window, cx);
16189 });
16190
16191 assert_eq!(
16192 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16193 "\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",
16194 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16195 );
16196
16197 multi_buffer_editor.update(cx, |editor, cx| {
16198 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16199 });
16200 assert_eq!(
16201 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16202 "\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",
16203 "After unfolding the all buffers, all original text should be displayed"
16204 );
16205}
16206
16207#[gpui::test]
16208async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16209 init_test(cx, |_| {});
16210
16211 let sample_text_1 = "1111\n2222\n3333".to_string();
16212 let sample_text_2 = "4444\n5555\n6666".to_string();
16213 let sample_text_3 = "7777\n8888\n9999".to_string();
16214
16215 let fs = FakeFs::new(cx.executor());
16216 fs.insert_tree(
16217 path!("/a"),
16218 json!({
16219 "first.rs": sample_text_1,
16220 "second.rs": sample_text_2,
16221 "third.rs": sample_text_3,
16222 }),
16223 )
16224 .await;
16225 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16226 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16227 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16228 let worktree = project.update(cx, |project, cx| {
16229 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16230 assert_eq!(worktrees.len(), 1);
16231 worktrees.pop().unwrap()
16232 });
16233 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16234
16235 let buffer_1 = project
16236 .update(cx, |project, cx| {
16237 project.open_buffer((worktree_id, "first.rs"), cx)
16238 })
16239 .await
16240 .unwrap();
16241 let buffer_2 = project
16242 .update(cx, |project, cx| {
16243 project.open_buffer((worktree_id, "second.rs"), cx)
16244 })
16245 .await
16246 .unwrap();
16247 let buffer_3 = project
16248 .update(cx, |project, cx| {
16249 project.open_buffer((worktree_id, "third.rs"), cx)
16250 })
16251 .await
16252 .unwrap();
16253
16254 let multi_buffer = cx.new(|cx| {
16255 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16256 multi_buffer.push_excerpts(
16257 buffer_1.clone(),
16258 [ExcerptRange {
16259 context: Point::new(0, 0)..Point::new(3, 0),
16260 primary: None,
16261 }],
16262 cx,
16263 );
16264 multi_buffer.push_excerpts(
16265 buffer_2.clone(),
16266 [ExcerptRange {
16267 context: Point::new(0, 0)..Point::new(3, 0),
16268 primary: None,
16269 }],
16270 cx,
16271 );
16272 multi_buffer.push_excerpts(
16273 buffer_3.clone(),
16274 [ExcerptRange {
16275 context: Point::new(0, 0)..Point::new(3, 0),
16276 primary: None,
16277 }],
16278 cx,
16279 );
16280 multi_buffer
16281 });
16282
16283 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16284 Editor::new(
16285 EditorMode::Full,
16286 multi_buffer,
16287 Some(project.clone()),
16288 true,
16289 window,
16290 cx,
16291 )
16292 });
16293
16294 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16295 assert_eq!(
16296 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16297 full_text,
16298 );
16299
16300 multi_buffer_editor.update(cx, |editor, cx| {
16301 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16302 });
16303 assert_eq!(
16304 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16305 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16306 "After folding the first buffer, its text should not be displayed"
16307 );
16308
16309 multi_buffer_editor.update(cx, |editor, cx| {
16310 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16311 });
16312
16313 assert_eq!(
16314 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16315 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16316 "After folding the second buffer, its text should not be displayed"
16317 );
16318
16319 multi_buffer_editor.update(cx, |editor, cx| {
16320 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16321 });
16322 assert_eq!(
16323 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16324 "\n\n\n\n\n",
16325 "After folding the third buffer, its text should not be displayed"
16326 );
16327
16328 multi_buffer_editor.update(cx, |editor, cx| {
16329 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16330 });
16331 assert_eq!(
16332 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16333 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16334 "After unfolding the second buffer, its text should be displayed"
16335 );
16336
16337 multi_buffer_editor.update(cx, |editor, cx| {
16338 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16339 });
16340 assert_eq!(
16341 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16342 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16343 "After unfolding the first buffer, its text should be displayed"
16344 );
16345
16346 multi_buffer_editor.update(cx, |editor, cx| {
16347 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16348 });
16349 assert_eq!(
16350 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16351 full_text,
16352 "After unfolding all buffers, all original text should be displayed"
16353 );
16354}
16355
16356#[gpui::test]
16357async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16358 init_test(cx, |_| {});
16359
16360 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16361
16362 let fs = FakeFs::new(cx.executor());
16363 fs.insert_tree(
16364 path!("/a"),
16365 json!({
16366 "main.rs": sample_text,
16367 }),
16368 )
16369 .await;
16370 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16371 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16372 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16373 let worktree = project.update(cx, |project, cx| {
16374 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16375 assert_eq!(worktrees.len(), 1);
16376 worktrees.pop().unwrap()
16377 });
16378 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16379
16380 let buffer_1 = project
16381 .update(cx, |project, cx| {
16382 project.open_buffer((worktree_id, "main.rs"), cx)
16383 })
16384 .await
16385 .unwrap();
16386
16387 let multi_buffer = cx.new(|cx| {
16388 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16389 multi_buffer.push_excerpts(
16390 buffer_1.clone(),
16391 [ExcerptRange {
16392 context: Point::new(0, 0)
16393 ..Point::new(
16394 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16395 0,
16396 ),
16397 primary: None,
16398 }],
16399 cx,
16400 );
16401 multi_buffer
16402 });
16403 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16404 Editor::new(
16405 EditorMode::Full,
16406 multi_buffer,
16407 Some(project.clone()),
16408 true,
16409 window,
16410 cx,
16411 )
16412 });
16413
16414 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16415 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16416 enum TestHighlight {}
16417 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16418 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16419 editor.highlight_text::<TestHighlight>(
16420 vec![highlight_range.clone()],
16421 HighlightStyle::color(Hsla::green()),
16422 cx,
16423 );
16424 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16425 });
16426
16427 let full_text = format!("\n\n\n{sample_text}\n");
16428 assert_eq!(
16429 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16430 full_text,
16431 );
16432}
16433
16434#[gpui::test]
16435async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16436 init_test(cx, |_| {});
16437 cx.update(|cx| {
16438 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16439 "keymaps/default-linux.json",
16440 cx,
16441 )
16442 .unwrap();
16443 cx.bind_keys(default_key_bindings);
16444 });
16445
16446 let (editor, cx) = cx.add_window_view(|window, cx| {
16447 let multi_buffer = MultiBuffer::build_multi(
16448 [
16449 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16450 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16451 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16452 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16453 ],
16454 cx,
16455 );
16456 let mut editor = Editor::new(
16457 EditorMode::Full,
16458 multi_buffer.clone(),
16459 None,
16460 true,
16461 window,
16462 cx,
16463 );
16464
16465 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16466 // fold all but the second buffer, so that we test navigating between two
16467 // adjacent folded buffers, as well as folded buffers at the start and
16468 // end the multibuffer
16469 editor.fold_buffer(buffer_ids[0], cx);
16470 editor.fold_buffer(buffer_ids[2], cx);
16471 editor.fold_buffer(buffer_ids[3], cx);
16472
16473 editor
16474 });
16475 cx.simulate_resize(size(px(1000.), px(1000.)));
16476
16477 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16478 cx.assert_excerpts_with_selections(indoc! {"
16479 [EXCERPT]
16480 ˇ[FOLDED]
16481 [EXCERPT]
16482 a1
16483 b1
16484 [EXCERPT]
16485 [FOLDED]
16486 [EXCERPT]
16487 [FOLDED]
16488 "
16489 });
16490 cx.simulate_keystroke("down");
16491 cx.assert_excerpts_with_selections(indoc! {"
16492 [EXCERPT]
16493 [FOLDED]
16494 [EXCERPT]
16495 ˇa1
16496 b1
16497 [EXCERPT]
16498 [FOLDED]
16499 [EXCERPT]
16500 [FOLDED]
16501 "
16502 });
16503 cx.simulate_keystroke("down");
16504 cx.assert_excerpts_with_selections(indoc! {"
16505 [EXCERPT]
16506 [FOLDED]
16507 [EXCERPT]
16508 a1
16509 ˇb1
16510 [EXCERPT]
16511 [FOLDED]
16512 [EXCERPT]
16513 [FOLDED]
16514 "
16515 });
16516 cx.simulate_keystroke("down");
16517 cx.assert_excerpts_with_selections(indoc! {"
16518 [EXCERPT]
16519 [FOLDED]
16520 [EXCERPT]
16521 a1
16522 b1
16523 ˇ[EXCERPT]
16524 [FOLDED]
16525 [EXCERPT]
16526 [FOLDED]
16527 "
16528 });
16529 cx.simulate_keystroke("down");
16530 cx.assert_excerpts_with_selections(indoc! {"
16531 [EXCERPT]
16532 [FOLDED]
16533 [EXCERPT]
16534 a1
16535 b1
16536 [EXCERPT]
16537 ˇ[FOLDED]
16538 [EXCERPT]
16539 [FOLDED]
16540 "
16541 });
16542 for _ in 0..5 {
16543 cx.simulate_keystroke("down");
16544 cx.assert_excerpts_with_selections(indoc! {"
16545 [EXCERPT]
16546 [FOLDED]
16547 [EXCERPT]
16548 a1
16549 b1
16550 [EXCERPT]
16551 [FOLDED]
16552 [EXCERPT]
16553 ˇ[FOLDED]
16554 "
16555 });
16556 }
16557
16558 cx.simulate_keystroke("up");
16559 cx.assert_excerpts_with_selections(indoc! {"
16560 [EXCERPT]
16561 [FOLDED]
16562 [EXCERPT]
16563 a1
16564 b1
16565 [EXCERPT]
16566 ˇ[FOLDED]
16567 [EXCERPT]
16568 [FOLDED]
16569 "
16570 });
16571 cx.simulate_keystroke("up");
16572 cx.assert_excerpts_with_selections(indoc! {"
16573 [EXCERPT]
16574 [FOLDED]
16575 [EXCERPT]
16576 a1
16577 b1
16578 ˇ[EXCERPT]
16579 [FOLDED]
16580 [EXCERPT]
16581 [FOLDED]
16582 "
16583 });
16584 cx.simulate_keystroke("up");
16585 cx.assert_excerpts_with_selections(indoc! {"
16586 [EXCERPT]
16587 [FOLDED]
16588 [EXCERPT]
16589 a1
16590 ˇb1
16591 [EXCERPT]
16592 [FOLDED]
16593 [EXCERPT]
16594 [FOLDED]
16595 "
16596 });
16597 cx.simulate_keystroke("up");
16598 cx.assert_excerpts_with_selections(indoc! {"
16599 [EXCERPT]
16600 [FOLDED]
16601 [EXCERPT]
16602 ˇa1
16603 b1
16604 [EXCERPT]
16605 [FOLDED]
16606 [EXCERPT]
16607 [FOLDED]
16608 "
16609 });
16610 for _ in 0..5 {
16611 cx.simulate_keystroke("up");
16612 cx.assert_excerpts_with_selections(indoc! {"
16613 [EXCERPT]
16614 ˇ[FOLDED]
16615 [EXCERPT]
16616 a1
16617 b1
16618 [EXCERPT]
16619 [FOLDED]
16620 [EXCERPT]
16621 [FOLDED]
16622 "
16623 });
16624 }
16625}
16626
16627#[gpui::test]
16628async fn test_inline_completion_text(cx: &mut TestAppContext) {
16629 init_test(cx, |_| {});
16630
16631 // Simple insertion
16632 assert_highlighted_edits(
16633 "Hello, world!",
16634 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16635 true,
16636 cx,
16637 |highlighted_edits, cx| {
16638 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16639 assert_eq!(highlighted_edits.highlights.len(), 1);
16640 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16641 assert_eq!(
16642 highlighted_edits.highlights[0].1.background_color,
16643 Some(cx.theme().status().created_background)
16644 );
16645 },
16646 )
16647 .await;
16648
16649 // Replacement
16650 assert_highlighted_edits(
16651 "This is a test.",
16652 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16653 false,
16654 cx,
16655 |highlighted_edits, cx| {
16656 assert_eq!(highlighted_edits.text, "That is a test.");
16657 assert_eq!(highlighted_edits.highlights.len(), 1);
16658 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16659 assert_eq!(
16660 highlighted_edits.highlights[0].1.background_color,
16661 Some(cx.theme().status().created_background)
16662 );
16663 },
16664 )
16665 .await;
16666
16667 // Multiple edits
16668 assert_highlighted_edits(
16669 "Hello, world!",
16670 vec![
16671 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16672 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16673 ],
16674 false,
16675 cx,
16676 |highlighted_edits, cx| {
16677 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16678 assert_eq!(highlighted_edits.highlights.len(), 2);
16679 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16680 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16681 assert_eq!(
16682 highlighted_edits.highlights[0].1.background_color,
16683 Some(cx.theme().status().created_background)
16684 );
16685 assert_eq!(
16686 highlighted_edits.highlights[1].1.background_color,
16687 Some(cx.theme().status().created_background)
16688 );
16689 },
16690 )
16691 .await;
16692
16693 // Multiple lines with edits
16694 assert_highlighted_edits(
16695 "First line\nSecond line\nThird line\nFourth line",
16696 vec![
16697 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16698 (
16699 Point::new(2, 0)..Point::new(2, 10),
16700 "New third line".to_string(),
16701 ),
16702 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16703 ],
16704 false,
16705 cx,
16706 |highlighted_edits, cx| {
16707 assert_eq!(
16708 highlighted_edits.text,
16709 "Second modified\nNew third line\nFourth updated line"
16710 );
16711 assert_eq!(highlighted_edits.highlights.len(), 3);
16712 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16713 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16714 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16715 for highlight in &highlighted_edits.highlights {
16716 assert_eq!(
16717 highlight.1.background_color,
16718 Some(cx.theme().status().created_background)
16719 );
16720 }
16721 },
16722 )
16723 .await;
16724}
16725
16726#[gpui::test]
16727async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16728 init_test(cx, |_| {});
16729
16730 // Deletion
16731 assert_highlighted_edits(
16732 "Hello, world!",
16733 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16734 true,
16735 cx,
16736 |highlighted_edits, cx| {
16737 assert_eq!(highlighted_edits.text, "Hello, world!");
16738 assert_eq!(highlighted_edits.highlights.len(), 1);
16739 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16740 assert_eq!(
16741 highlighted_edits.highlights[0].1.background_color,
16742 Some(cx.theme().status().deleted_background)
16743 );
16744 },
16745 )
16746 .await;
16747
16748 // Insertion
16749 assert_highlighted_edits(
16750 "Hello, world!",
16751 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16752 true,
16753 cx,
16754 |highlighted_edits, cx| {
16755 assert_eq!(highlighted_edits.highlights.len(), 1);
16756 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16757 assert_eq!(
16758 highlighted_edits.highlights[0].1.background_color,
16759 Some(cx.theme().status().created_background)
16760 );
16761 },
16762 )
16763 .await;
16764}
16765
16766async fn assert_highlighted_edits(
16767 text: &str,
16768 edits: Vec<(Range<Point>, String)>,
16769 include_deletions: bool,
16770 cx: &mut TestAppContext,
16771 assertion_fn: impl Fn(HighlightedText, &App),
16772) {
16773 let window = cx.add_window(|window, cx| {
16774 let buffer = MultiBuffer::build_simple(text, cx);
16775 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16776 });
16777 let cx = &mut VisualTestContext::from_window(*window, cx);
16778
16779 let (buffer, snapshot) = window
16780 .update(cx, |editor, _window, cx| {
16781 (
16782 editor.buffer().clone(),
16783 editor.buffer().read(cx).snapshot(cx),
16784 )
16785 })
16786 .unwrap();
16787
16788 let edits = edits
16789 .into_iter()
16790 .map(|(range, edit)| {
16791 (
16792 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16793 edit,
16794 )
16795 })
16796 .collect::<Vec<_>>();
16797
16798 let text_anchor_edits = edits
16799 .clone()
16800 .into_iter()
16801 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16802 .collect::<Vec<_>>();
16803
16804 let edit_preview = window
16805 .update(cx, |_, _window, cx| {
16806 buffer
16807 .read(cx)
16808 .as_singleton()
16809 .unwrap()
16810 .read(cx)
16811 .preview_edits(text_anchor_edits.into(), cx)
16812 })
16813 .unwrap()
16814 .await;
16815
16816 cx.update(|_window, cx| {
16817 let highlighted_edits = inline_completion_edit_text(
16818 &snapshot.as_singleton().unwrap().2,
16819 &edits,
16820 &edit_preview,
16821 include_deletions,
16822 cx,
16823 );
16824 assertion_fn(highlighted_edits, cx)
16825 });
16826}
16827
16828#[gpui::test]
16829async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16830 init_test(cx, |_| {});
16831 let capabilities = lsp::ServerCapabilities {
16832 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16833 prepare_provider: Some(true),
16834 work_done_progress_options: Default::default(),
16835 })),
16836 ..Default::default()
16837 };
16838 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16839
16840 cx.set_state(indoc! {"
16841 struct Fˇoo {}
16842 "});
16843
16844 cx.update_editor(|editor, _, cx| {
16845 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16846 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16847 editor.highlight_background::<DocumentHighlightRead>(
16848 &[highlight_range],
16849 |c| c.editor_document_highlight_read_background,
16850 cx,
16851 );
16852 });
16853
16854 let mut prepare_rename_handler =
16855 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16856 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16857 start: lsp::Position {
16858 line: 0,
16859 character: 7,
16860 },
16861 end: lsp::Position {
16862 line: 0,
16863 character: 10,
16864 },
16865 })))
16866 });
16867 let prepare_rename_task = cx
16868 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16869 .expect("Prepare rename was not started");
16870 prepare_rename_handler.next().await.unwrap();
16871 prepare_rename_task.await.expect("Prepare rename failed");
16872
16873 let mut rename_handler =
16874 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16875 let edit = lsp::TextEdit {
16876 range: lsp::Range {
16877 start: lsp::Position {
16878 line: 0,
16879 character: 7,
16880 },
16881 end: lsp::Position {
16882 line: 0,
16883 character: 10,
16884 },
16885 },
16886 new_text: "FooRenamed".to_string(),
16887 };
16888 Ok(Some(lsp::WorkspaceEdit::new(
16889 // Specify the same edit twice
16890 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16891 )))
16892 });
16893 let rename_task = cx
16894 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16895 .expect("Confirm rename was not started");
16896 rename_handler.next().await.unwrap();
16897 rename_task.await.expect("Confirm rename failed");
16898 cx.run_until_parked();
16899
16900 // Despite two edits, only one is actually applied as those are identical
16901 cx.assert_editor_state(indoc! {"
16902 struct FooRenamedˇ {}
16903 "});
16904}
16905
16906#[gpui::test]
16907async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16908 init_test(cx, |_| {});
16909 // These capabilities indicate that the server does not support prepare rename.
16910 let capabilities = lsp::ServerCapabilities {
16911 rename_provider: Some(lsp::OneOf::Left(true)),
16912 ..Default::default()
16913 };
16914 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16915
16916 cx.set_state(indoc! {"
16917 struct Fˇoo {}
16918 "});
16919
16920 cx.update_editor(|editor, _window, cx| {
16921 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16922 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16923 editor.highlight_background::<DocumentHighlightRead>(
16924 &[highlight_range],
16925 |c| c.editor_document_highlight_read_background,
16926 cx,
16927 );
16928 });
16929
16930 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16931 .expect("Prepare rename was not started")
16932 .await
16933 .expect("Prepare rename failed");
16934
16935 let mut rename_handler =
16936 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16937 let edit = lsp::TextEdit {
16938 range: lsp::Range {
16939 start: lsp::Position {
16940 line: 0,
16941 character: 7,
16942 },
16943 end: lsp::Position {
16944 line: 0,
16945 character: 10,
16946 },
16947 },
16948 new_text: "FooRenamed".to_string(),
16949 };
16950 Ok(Some(lsp::WorkspaceEdit::new(
16951 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16952 )))
16953 });
16954 let rename_task = cx
16955 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16956 .expect("Confirm rename was not started");
16957 rename_handler.next().await.unwrap();
16958 rename_task.await.expect("Confirm rename failed");
16959 cx.run_until_parked();
16960
16961 // Correct range is renamed, as `surrounding_word` is used to find it.
16962 cx.assert_editor_state(indoc! {"
16963 struct FooRenamedˇ {}
16964 "});
16965}
16966
16967#[gpui::test]
16968async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16969 init_test(cx, |_| {});
16970 let mut cx = EditorTestContext::new(cx).await;
16971
16972 let language = Arc::new(
16973 Language::new(
16974 LanguageConfig::default(),
16975 Some(tree_sitter_html::LANGUAGE.into()),
16976 )
16977 .with_brackets_query(
16978 r#"
16979 ("<" @open "/>" @close)
16980 ("</" @open ">" @close)
16981 ("<" @open ">" @close)
16982 ("\"" @open "\"" @close)
16983 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16984 "#,
16985 )
16986 .unwrap(),
16987 );
16988 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16989
16990 cx.set_state(indoc! {"
16991 <span>ˇ</span>
16992 "});
16993 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16994 cx.assert_editor_state(indoc! {"
16995 <span>
16996 ˇ
16997 </span>
16998 "});
16999
17000 cx.set_state(indoc! {"
17001 <span><span></span>ˇ</span>
17002 "});
17003 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17004 cx.assert_editor_state(indoc! {"
17005 <span><span></span>
17006 ˇ</span>
17007 "});
17008
17009 cx.set_state(indoc! {"
17010 <span>ˇ
17011 </span>
17012 "});
17013 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17014 cx.assert_editor_state(indoc! {"
17015 <span>
17016 ˇ
17017 </span>
17018 "});
17019}
17020
17021mod autoclose_tags {
17022 use super::*;
17023 use language::language_settings::JsxTagAutoCloseSettings;
17024 use languages::language;
17025
17026 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17027 init_test(cx, |settings| {
17028 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17029 });
17030
17031 let mut cx = EditorTestContext::new(cx).await;
17032 cx.update_buffer(|buffer, cx| {
17033 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17034
17035 buffer.set_language(Some(language), cx)
17036 });
17037
17038 cx
17039 }
17040
17041 macro_rules! check {
17042 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17043 #[gpui::test]
17044 async fn $name(cx: &mut TestAppContext) {
17045 let mut cx = test_setup(cx).await;
17046 cx.set_state($initial);
17047 cx.run_until_parked();
17048
17049 cx.update_editor(|editor, window, cx| {
17050 editor.handle_input($input, window, cx);
17051 });
17052 cx.run_until_parked();
17053 cx.assert_editor_state($expected);
17054 }
17055 };
17056 }
17057
17058 check!(
17059 test_basic,
17060 "<divˇ" + ">" => "<div>ˇ</div>"
17061 );
17062
17063 check!(
17064 test_basic_nested,
17065 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17066 );
17067
17068 check!(
17069 test_basic_ignore_already_closed,
17070 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17071 );
17072
17073 check!(
17074 test_doesnt_autoclose_closing_tag,
17075 "</divˇ" + ">" => "</div>ˇ"
17076 );
17077
17078 check!(
17079 test_jsx_attr,
17080 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17081 );
17082
17083 check!(
17084 test_ignores_closing_tags_in_expr_block,
17085 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17086 );
17087
17088 check!(
17089 test_doesnt_autoclose_on_gt_in_expr,
17090 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17091 );
17092
17093 check!(
17094 test_ignores_closing_tags_with_different_tag_names,
17095 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17096 );
17097
17098 check!(
17099 test_autocloses_in_jsx_expression,
17100 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17101 );
17102
17103 check!(
17104 test_doesnt_autoclose_already_closed_in_jsx_expression,
17105 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17106 );
17107
17108 check!(
17109 test_autocloses_fragment,
17110 "<ˇ" + ">" => "<>ˇ</>"
17111 );
17112
17113 check!(
17114 test_does_not_include_type_argument_in_autoclose_tag_name,
17115 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17116 );
17117
17118 check!(
17119 test_does_not_autoclose_doctype,
17120 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17121 );
17122
17123 check!(
17124 test_does_not_autoclose_comment,
17125 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17126 );
17127
17128 check!(
17129 test_multi_cursor_autoclose_same_tag,
17130 r#"
17131 <divˇ
17132 <divˇ
17133 "#
17134 + ">" =>
17135 r#"
17136 <div>ˇ</div>
17137 <div>ˇ</div>
17138 "#
17139 );
17140
17141 check!(
17142 test_multi_cursor_autoclose_different_tags,
17143 r#"
17144 <divˇ
17145 <spanˇ
17146 "#
17147 + ">" =>
17148 r#"
17149 <div>ˇ</div>
17150 <span>ˇ</span>
17151 "#
17152 );
17153
17154 check!(
17155 test_multi_cursor_autoclose_some_dont_autoclose_others,
17156 r#"
17157 <divˇ
17158 <div /ˇ
17159 <spanˇ</span>
17160 <!DOCTYPE htmlˇ
17161 </headˇ
17162 <Component<T>ˇ
17163 ˇ
17164 "#
17165 + ">" =>
17166 r#"
17167 <div>ˇ</div>
17168 <div />ˇ
17169 <span>ˇ</span>
17170 <!DOCTYPE html>ˇ
17171 </head>ˇ
17172 <Component<T>>ˇ</Component>
17173 >ˇ
17174 "#
17175 );
17176
17177 check!(
17178 test_doesnt_mess_up_trailing_text,
17179 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17180 );
17181
17182 #[gpui::test]
17183 async fn test_multibuffer(cx: &mut TestAppContext) {
17184 init_test(cx, |settings| {
17185 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17186 });
17187
17188 let buffer_a = cx.new(|cx| {
17189 let mut buf = language::Buffer::local("<div", cx);
17190 buf.set_language(
17191 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17192 cx,
17193 );
17194 buf
17195 });
17196 let buffer_b = cx.new(|cx| {
17197 let mut buf = language::Buffer::local("<pre", cx);
17198 buf.set_language(
17199 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17200 cx,
17201 );
17202 buf
17203 });
17204 let buffer_c = cx.new(|cx| {
17205 let buf = language::Buffer::local("<span", cx);
17206 buf
17207 });
17208 let buffer = cx.new(|cx| {
17209 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17210 buf.push_excerpts(
17211 buffer_a,
17212 [ExcerptRange {
17213 context: text::Anchor::MIN..text::Anchor::MAX,
17214 primary: None,
17215 }],
17216 cx,
17217 );
17218 buf.push_excerpts(
17219 buffer_b,
17220 [ExcerptRange {
17221 context: text::Anchor::MIN..text::Anchor::MAX,
17222 primary: None,
17223 }],
17224 cx,
17225 );
17226 buf.push_excerpts(
17227 buffer_c,
17228 [ExcerptRange {
17229 context: text::Anchor::MIN..text::Anchor::MAX,
17230 primary: None,
17231 }],
17232 cx,
17233 );
17234 buf
17235 });
17236 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17237
17238 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17239
17240 cx.update_editor(|editor, window, cx| {
17241 editor.change_selections(None, window, cx, |selections| {
17242 selections.select(vec![
17243 Selection::from_offset(4),
17244 Selection::from_offset(9),
17245 Selection::from_offset(15),
17246 ])
17247 })
17248 });
17249 cx.run_until_parked();
17250
17251 cx.update_editor(|editor, window, cx| {
17252 editor.handle_input(">", window, cx);
17253 });
17254 cx.run_until_parked();
17255
17256 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17257 }
17258}
17259
17260fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17261 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17262 point..point
17263}
17264
17265fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17266 let (text, ranges) = marked_text_ranges(marked_text, true);
17267 assert_eq!(editor.text(cx), text);
17268 assert_eq!(
17269 editor.selections.ranges(cx),
17270 ranges,
17271 "Assert selections are {}",
17272 marked_text
17273 );
17274}
17275
17276pub fn handle_signature_help_request(
17277 cx: &mut EditorLspTestContext,
17278 mocked_response: lsp::SignatureHelp,
17279) -> impl Future<Output = ()> {
17280 let mut request =
17281 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17282 let mocked_response = mocked_response.clone();
17283 async move { Ok(Some(mocked_response)) }
17284 });
17285
17286 async move {
17287 request.next().await;
17288 }
17289}
17290
17291/// Handle completion request passing a marked string specifying where the completion
17292/// should be triggered from using '|' character, what range should be replaced, and what completions
17293/// should be returned using '<' and '>' to delimit the range
17294pub fn handle_completion_request(
17295 cx: &mut EditorLspTestContext,
17296 marked_string: &str,
17297 completions: Vec<&'static str>,
17298 counter: Arc<AtomicUsize>,
17299) -> impl Future<Output = ()> {
17300 let complete_from_marker: TextRangeMarker = '|'.into();
17301 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17302 let (_, mut marked_ranges) = marked_text_ranges_by(
17303 marked_string,
17304 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17305 );
17306
17307 let complete_from_position =
17308 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17309 let replace_range =
17310 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17311
17312 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17313 let completions = completions.clone();
17314 counter.fetch_add(1, atomic::Ordering::Release);
17315 async move {
17316 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17317 assert_eq!(
17318 params.text_document_position.position,
17319 complete_from_position
17320 );
17321 Ok(Some(lsp::CompletionResponse::Array(
17322 completions
17323 .iter()
17324 .map(|completion_text| lsp::CompletionItem {
17325 label: completion_text.to_string(),
17326 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17327 range: replace_range,
17328 new_text: completion_text.to_string(),
17329 })),
17330 ..Default::default()
17331 })
17332 .collect(),
17333 )))
17334 }
17335 });
17336
17337 async move {
17338 request.next().await;
17339 }
17340}
17341
17342fn handle_resolve_completion_request(
17343 cx: &mut EditorLspTestContext,
17344 edits: Option<Vec<(&'static str, &'static str)>>,
17345) -> impl Future<Output = ()> {
17346 let edits = edits.map(|edits| {
17347 edits
17348 .iter()
17349 .map(|(marked_string, new_text)| {
17350 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17351 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17352 lsp::TextEdit::new(replace_range, new_text.to_string())
17353 })
17354 .collect::<Vec<_>>()
17355 });
17356
17357 let mut request =
17358 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17359 let edits = edits.clone();
17360 async move {
17361 Ok(lsp::CompletionItem {
17362 additional_text_edits: edits,
17363 ..Default::default()
17364 })
17365 }
17366 });
17367
17368 async move {
17369 request.next().await;
17370 }
17371}
17372
17373pub(crate) fn update_test_language_settings(
17374 cx: &mut TestAppContext,
17375 f: impl Fn(&mut AllLanguageSettingsContent),
17376) {
17377 cx.update(|cx| {
17378 SettingsStore::update_global(cx, |store, cx| {
17379 store.update_user_settings::<AllLanguageSettings>(cx, f);
17380 });
17381 });
17382}
17383
17384pub(crate) fn update_test_project_settings(
17385 cx: &mut TestAppContext,
17386 f: impl Fn(&mut ProjectSettings),
17387) {
17388 cx.update(|cx| {
17389 SettingsStore::update_global(cx, |store, cx| {
17390 store.update_user_settings::<ProjectSettings>(cx, f);
17391 });
17392 });
17393}
17394
17395pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17396 cx.update(|cx| {
17397 assets::Assets.load_test_fonts(cx);
17398 let store = SettingsStore::test(cx);
17399 cx.set_global(store);
17400 theme::init(theme::LoadThemes::JustBase, cx);
17401 release_channel::init(SemanticVersion::default(), cx);
17402 client::init_settings(cx);
17403 language::init(cx);
17404 Project::init_settings(cx);
17405 workspace::init_settings(cx);
17406 crate::init(cx);
17407 });
17408
17409 update_test_language_settings(cx, f);
17410}
17411
17412#[track_caller]
17413fn assert_hunk_revert(
17414 not_reverted_text_with_selections: &str,
17415 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17416 expected_reverted_text_with_selections: &str,
17417 base_text: &str,
17418 cx: &mut EditorLspTestContext,
17419) {
17420 cx.set_state(not_reverted_text_with_selections);
17421 cx.set_head_text(base_text);
17422 cx.executor().run_until_parked();
17423
17424 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17425 let snapshot = editor.snapshot(window, cx);
17426 let reverted_hunk_statuses = snapshot
17427 .buffer_snapshot
17428 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17429 .map(|hunk| hunk.status().kind)
17430 .collect::<Vec<_>>();
17431
17432 editor.git_restore(&Default::default(), window, cx);
17433 reverted_hunk_statuses
17434 });
17435 cx.executor().run_until_parked();
17436 cx.assert_editor_state(expected_reverted_text_with_selections);
17437 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17438}