1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
24 Override, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::{IndentGuide, PathKey};
28use parking_lot::Mutex;
29use pretty_assertions::{assert_eq, assert_ne};
30use project::project_settings::{LspSettings, ProjectSettings};
31use project::FakeFs;
32use serde_json::{self, json};
33use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
34use std::{
35 iter,
36 sync::atomic::{self, AtomicUsize},
37};
38use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
39use text::ToPoint as _;
40use unindent::Unindent;
41use util::{
42 assert_set_eq, path,
43 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
44 uri,
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |window, cx| {
65 let entity = cx.entity().clone();
66 cx.subscribe_in(
67 &entity,
68 window,
69 move |_, _, event: &EditorEvent, _, _| match event {
70 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
71 EditorEvent::BufferEdited => {
72 events.borrow_mut().push(("editor1", "buffer edited"))
73 }
74 _ => {}
75 },
76 )
77 .detach();
78 Editor::for_buffer(buffer.clone(), None, window, cx)
79 }
80 });
81
82 let editor2 = cx.add_window({
83 let events = events.clone();
84 |window, cx| {
85 cx.subscribe_in(
86 &cx.entity().clone(),
87 window,
88 move |_, _, event: &EditorEvent, _, _| match event {
89 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
90 EditorEvent::BufferEdited => {
91 events.borrow_mut().push(("editor2", "buffer edited"))
92 }
93 _ => {}
94 },
95 )
96 .detach();
97 Editor::for_buffer(buffer.clone(), None, window, cx)
98 }
99 });
100
101 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
102
103 // Mutating editor 1 will emit an `Edited` event only for that editor.
104 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor1", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Mutating editor 2 will emit an `Edited` event only for that editor.
115 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor2", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Undoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Redoing on editor 1 will emit an `Edited` event only for that editor.
137 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor1", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Undoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // Redoing on editor 2 will emit an `Edited` event only for that editor.
159 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
160 assert_eq!(
161 mem::take(&mut *events.borrow_mut()),
162 [
163 ("editor2", "edited"),
164 ("editor1", "buffer edited"),
165 ("editor2", "buffer edited"),
166 ]
167 );
168
169 // No event is emitted when the mutation is a no-op.
170 _ = editor2.update(cx, |editor, window, cx| {
171 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
172
173 editor.backspace(&Backspace, window, cx);
174 });
175 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
176}
177
178#[gpui::test]
179fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
180 init_test(cx, |_| {});
181
182 let mut now = Instant::now();
183 let group_interval = Duration::from_millis(1);
184 let buffer = cx.new(|cx| {
185 let mut buf = language::Buffer::local("123456", cx);
186 buf.set_group_interval(group_interval);
187 buf
188 });
189 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
190 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
191
192 _ = editor.update(cx, |editor, window, cx| {
193 editor.start_transaction_at(now, window, cx);
194 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
195
196 editor.insert("cd", window, cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cd56");
199 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
200
201 editor.start_transaction_at(now, window, cx);
202 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
203 editor.insert("e", window, cx);
204 editor.end_transaction_at(now, cx);
205 assert_eq!(editor.text(cx), "12cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
207
208 now += group_interval + Duration::from_millis(1);
209 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
210
211 // Simulate an edit in another editor
212 buffer.update(cx, |buffer, cx| {
213 buffer.start_transaction_at(now, cx);
214 buffer.edit([(0..1, "a")], None, cx);
215 buffer.edit([(1..1, "b")], None, cx);
216 buffer.end_transaction_at(now, cx);
217 });
218
219 assert_eq!(editor.text(cx), "ab2cde6");
220 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
221
222 // Last transaction happened past the group interval in a different editor.
223 // Undo it individually and don't restore selections.
224 editor.undo(&Undo, window, cx);
225 assert_eq!(editor.text(cx), "12cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
227
228 // First two transactions happened within the group interval in this editor.
229 // Undo them together and restore selections.
230 editor.undo(&Undo, window, cx);
231 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
232 assert_eq!(editor.text(cx), "123456");
233 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
234
235 // Redo the first two transactions together.
236 editor.redo(&Redo, window, cx);
237 assert_eq!(editor.text(cx), "12cde6");
238 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
239
240 // Redo the last transaction on its own.
241 editor.redo(&Redo, window, cx);
242 assert_eq!(editor.text(cx), "ab2cde6");
243 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
244
245 // Test empty transactions.
246 editor.start_transaction_at(now, window, cx);
247 editor.end_transaction_at(now, cx);
248 editor.undo(&Undo, window, cx);
249 assert_eq!(editor.text(cx), "12cde6");
250 });
251}
252
253#[gpui::test]
254fn test_ime_composition(cx: &mut TestAppContext) {
255 init_test(cx, |_| {});
256
257 let buffer = cx.new(|cx| {
258 let mut buffer = language::Buffer::local("abcde", cx);
259 // Ensure automatic grouping doesn't occur.
260 buffer.set_group_interval(Duration::ZERO);
261 buffer
262 });
263
264 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
265 cx.add_window(|window, cx| {
266 let mut editor = build_editor(buffer.clone(), window, cx);
267
268 // Start a new IME composition.
269 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
270 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
272 assert_eq!(editor.text(cx), "äbcde");
273 assert_eq!(
274 editor.marked_text_ranges(cx),
275 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
276 );
277
278 // Finalize IME composition.
279 editor.replace_text_in_range(None, "ā", window, cx);
280 assert_eq!(editor.text(cx), "ābcde");
281 assert_eq!(editor.marked_text_ranges(cx), None);
282
283 // IME composition edits are grouped and are undone/redone at once.
284 editor.undo(&Default::default(), window, cx);
285 assert_eq!(editor.text(cx), "abcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287 editor.redo(&Default::default(), window, cx);
288 assert_eq!(editor.text(cx), "ābcde");
289 assert_eq!(editor.marked_text_ranges(cx), None);
290
291 // Start a new IME composition.
292 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
293 assert_eq!(
294 editor.marked_text_ranges(cx),
295 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
296 );
297
298 // Undoing during an IME composition cancels it.
299 editor.undo(&Default::default(), window, cx);
300 assert_eq!(editor.text(cx), "ābcde");
301 assert_eq!(editor.marked_text_ranges(cx), None);
302
303 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
304 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
305 assert_eq!(editor.text(cx), "ābcdè");
306 assert_eq!(
307 editor.marked_text_ranges(cx),
308 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
309 );
310
311 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
312 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
313 assert_eq!(editor.text(cx), "ābcdę");
314 assert_eq!(editor.marked_text_ranges(cx), None);
315
316 // Start a new IME composition with multiple cursors.
317 editor.change_selections(None, window, cx, |s| {
318 s.select_ranges([
319 OffsetUtf16(1)..OffsetUtf16(1),
320 OffsetUtf16(3)..OffsetUtf16(3),
321 OffsetUtf16(5)..OffsetUtf16(5),
322 ])
323 });
324 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
325 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
326 assert_eq!(
327 editor.marked_text_ranges(cx),
328 Some(vec![
329 OffsetUtf16(0)..OffsetUtf16(3),
330 OffsetUtf16(4)..OffsetUtf16(7),
331 OffsetUtf16(8)..OffsetUtf16(11)
332 ])
333 );
334
335 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
336 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
337 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
338 assert_eq!(
339 editor.marked_text_ranges(cx),
340 Some(vec![
341 OffsetUtf16(1)..OffsetUtf16(2),
342 OffsetUtf16(5)..OffsetUtf16(6),
343 OffsetUtf16(9)..OffsetUtf16(10)
344 ])
345 );
346
347 // Finalize IME composition with multiple cursors.
348 editor.replace_text_in_range(Some(9..10), "2", window, cx);
349 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
350 assert_eq!(editor.marked_text_ranges(cx), None);
351
352 editor
353 });
354}
355
356#[gpui::test]
357fn test_selection_with_mouse(cx: &mut TestAppContext) {
358 init_test(cx, |_| {});
359
360 let editor = cx.add_window(|window, cx| {
361 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
362 build_editor(buffer, window, cx)
363 });
364
365 _ = editor.update(cx, |editor, window, cx| {
366 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
367 });
368 assert_eq!(
369 editor
370 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
371 .unwrap(),
372 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
373 );
374
375 _ = editor.update(cx, |editor, window, cx| {
376 editor.update_selection(
377 DisplayPoint::new(DisplayRow(3), 3),
378 0,
379 gpui::Point::<f32>::default(),
380 window,
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
390 );
391
392 _ = editor.update(cx, |editor, window, cx| {
393 editor.update_selection(
394 DisplayPoint::new(DisplayRow(1), 1),
395 0,
396 gpui::Point::<f32>::default(),
397 window,
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |editor, window, cx| {
410 editor.end_selection(window, cx);
411 editor.update_selection(
412 DisplayPoint::new(DisplayRow(3), 3),
413 0,
414 gpui::Point::<f32>::default(),
415 window,
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
423 .unwrap(),
424 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
425 );
426
427 _ = editor.update(cx, |editor, window, cx| {
428 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
429 editor.update_selection(
430 DisplayPoint::new(DisplayRow(0), 0),
431 0,
432 gpui::Point::<f32>::default(),
433 window,
434 cx,
435 );
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
441 .unwrap(),
442 [
443 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
444 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
445 ]
446 );
447
448 _ = editor.update(cx, |editor, window, cx| {
449 editor.end_selection(window, cx);
450 });
451
452 assert_eq!(
453 editor
454 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
455 .unwrap(),
456 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
457 );
458}
459
460#[gpui::test]
461fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
462 init_test(cx, |_| {});
463
464 let editor = cx.add_window(|window, cx| {
465 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
466 build_editor(buffer, window, cx)
467 });
468
469 _ = editor.update(cx, |editor, window, cx| {
470 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
471 });
472
473 _ = editor.update(cx, |editor, window, cx| {
474 editor.end_selection(window, cx);
475 });
476
477 _ = editor.update(cx, |editor, window, cx| {
478 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
479 });
480
481 _ = editor.update(cx, |editor, window, cx| {
482 editor.end_selection(window, cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
488 .unwrap(),
489 [
490 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
491 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
492 ]
493 );
494
495 _ = editor.update(cx, |editor, window, cx| {
496 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
497 });
498
499 _ = editor.update(cx, |editor, window, cx| {
500 editor.end_selection(window, cx);
501 });
502
503 assert_eq!(
504 editor
505 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
506 .unwrap(),
507 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
508 );
509}
510
511#[gpui::test]
512fn test_canceling_pending_selection(cx: &mut TestAppContext) {
513 init_test(cx, |_| {});
514
515 let editor = cx.add_window(|window, cx| {
516 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
517 build_editor(buffer, window, cx)
518 });
519
520 _ = editor.update(cx, |editor, window, cx| {
521 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
522 assert_eq!(
523 editor.selections.display_ranges(cx),
524 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
525 );
526 });
527
528 _ = editor.update(cx, |editor, window, cx| {
529 editor.update_selection(
530 DisplayPoint::new(DisplayRow(3), 3),
531 0,
532 gpui::Point::<f32>::default(),
533 window,
534 cx,
535 );
536 assert_eq!(
537 editor.selections.display_ranges(cx),
538 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
539 );
540 });
541
542 _ = editor.update(cx, |editor, window, cx| {
543 editor.cancel(&Cancel, window, cx);
544 editor.update_selection(
545 DisplayPoint::new(DisplayRow(1), 1),
546 0,
547 gpui::Point::<f32>::default(),
548 window,
549 cx,
550 );
551 assert_eq!(
552 editor.selections.display_ranges(cx),
553 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
554 );
555 });
556}
557
558#[gpui::test]
559fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
560 init_test(cx, |_| {});
561
562 let editor = cx.add_window(|window, cx| {
563 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
564 build_editor(buffer, window, cx)
565 });
566
567 _ = editor.update(cx, |editor, window, cx| {
568 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
569 assert_eq!(
570 editor.selections.display_ranges(cx),
571 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
572 );
573
574 editor.move_down(&Default::default(), window, cx);
575 assert_eq!(
576 editor.selections.display_ranges(cx),
577 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
578 );
579
580 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
581 assert_eq!(
582 editor.selections.display_ranges(cx),
583 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
584 );
585
586 editor.move_up(&Default::default(), window, cx);
587 assert_eq!(
588 editor.selections.display_ranges(cx),
589 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
590 );
591 });
592}
593
594#[gpui::test]
595fn test_clone(cx: &mut TestAppContext) {
596 init_test(cx, |_| {});
597
598 let (text, selection_ranges) = marked_text_ranges(
599 indoc! {"
600 one
601 two
602 threeˇ
603 four
604 fiveˇ
605 "},
606 true,
607 );
608
609 let editor = cx.add_window(|window, cx| {
610 let buffer = MultiBuffer::build_simple(&text, cx);
611 build_editor(buffer, window, cx)
612 });
613
614 _ = editor.update(cx, |editor, window, cx| {
615 editor.change_selections(None, window, cx, |s| {
616 s.select_ranges(selection_ranges.clone())
617 });
618 editor.fold_creases(
619 vec![
620 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
621 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
622 ],
623 true,
624 window,
625 cx,
626 );
627 });
628
629 let cloned_editor = editor
630 .update(cx, |editor, _, cx| {
631 cx.open_window(Default::default(), |window, cx| {
632 cx.new(|cx| editor.clone(window, cx))
633 })
634 })
635 .unwrap()
636 .unwrap();
637
638 let snapshot = editor
639 .update(cx, |e, window, cx| e.snapshot(window, cx))
640 .unwrap();
641 let cloned_snapshot = cloned_editor
642 .update(cx, |e, window, cx| e.snapshot(window, cx))
643 .unwrap();
644
645 assert_eq!(
646 cloned_editor
647 .update(cx, |e, _, cx| e.display_text(cx))
648 .unwrap(),
649 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
650 );
651 assert_eq!(
652 cloned_snapshot
653 .folds_in_range(0..text.len())
654 .collect::<Vec<_>>(),
655 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
656 );
657 assert_set_eq!(
658 cloned_editor
659 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
660 .unwrap(),
661 editor
662 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
663 .unwrap()
664 );
665 assert_set_eq!(
666 cloned_editor
667 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
668 .unwrap(),
669 editor
670 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
671 .unwrap()
672 );
673}
674
675#[gpui::test]
676async fn test_navigation_history(cx: &mut TestAppContext) {
677 init_test(cx, |_| {});
678
679 use workspace::item::Item;
680
681 let fs = FakeFs::new(cx.executor());
682 let project = Project::test(fs, [], cx).await;
683 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
684 let pane = workspace
685 .update(cx, |workspace, _, _| workspace.active_pane().clone())
686 .unwrap();
687
688 _ = workspace.update(cx, |_v, window, cx| {
689 cx.new(|cx| {
690 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
691 let mut editor = build_editor(buffer.clone(), window, cx);
692 let handle = cx.entity();
693 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
694
695 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
696 editor.nav_history.as_mut().unwrap().pop_backward(cx)
697 }
698
699 // Move the cursor a small distance.
700 // Nothing is added to the navigation history.
701 editor.change_selections(None, window, cx, |s| {
702 s.select_display_ranges([
703 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
704 ])
705 });
706 editor.change_selections(None, window, cx, |s| {
707 s.select_display_ranges([
708 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
709 ])
710 });
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance.
714 // The history can jump back to the previous position.
715 editor.change_selections(None, window, cx, |s| {
716 s.select_display_ranges([
717 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
718 ])
719 });
720 let nav_entry = pop_history(&mut editor, cx).unwrap();
721 editor.navigate(nav_entry.data.unwrap(), window, cx);
722 assert_eq!(nav_entry.item.id(), cx.entity_id());
723 assert_eq!(
724 editor.selections.display_ranges(cx),
725 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
726 );
727 assert!(pop_history(&mut editor, cx).is_none());
728
729 // Move the cursor a small distance via the mouse.
730 // Nothing is added to the navigation history.
731 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
732 editor.end_selection(window, cx);
733 assert_eq!(
734 editor.selections.display_ranges(cx),
735 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
736 );
737 assert!(pop_history(&mut editor, cx).is_none());
738
739 // Move the cursor a large distance via the mouse.
740 // The history can jump back to the previous position.
741 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
742 editor.end_selection(window, cx);
743 assert_eq!(
744 editor.selections.display_ranges(cx),
745 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
746 );
747 let nav_entry = pop_history(&mut editor, cx).unwrap();
748 editor.navigate(nav_entry.data.unwrap(), window, cx);
749 assert_eq!(nav_entry.item.id(), cx.entity_id());
750 assert_eq!(
751 editor.selections.display_ranges(cx),
752 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
753 );
754 assert!(pop_history(&mut editor, cx).is_none());
755
756 // Set scroll position to check later
757 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
758 let original_scroll_position = editor.scroll_manager.anchor();
759
760 // Jump to the end of the document and adjust scroll
761 editor.move_to_end(&MoveToEnd, window, cx);
762 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
763 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
764
765 let nav_entry = pop_history(&mut editor, cx).unwrap();
766 editor.navigate(nav_entry.data.unwrap(), window, cx);
767 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
768
769 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
770 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
771 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
772 let invalid_point = Point::new(9999, 0);
773 editor.navigate(
774 Box::new(NavigationData {
775 cursor_anchor: invalid_anchor,
776 cursor_position: invalid_point,
777 scroll_anchor: ScrollAnchor {
778 anchor: invalid_anchor,
779 offset: Default::default(),
780 },
781 scroll_top_row: invalid_point.row,
782 }),
783 window,
784 cx,
785 );
786 assert_eq!(
787 editor.selections.display_ranges(cx),
788 &[editor.max_point(cx)..editor.max_point(cx)]
789 );
790 assert_eq!(
791 editor.scroll_position(cx),
792 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
793 );
794
795 editor
796 })
797 });
798}
799
800#[gpui::test]
801fn test_cancel(cx: &mut TestAppContext) {
802 init_test(cx, |_| {});
803
804 let editor = cx.add_window(|window, cx| {
805 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
806 build_editor(buffer, window, cx)
807 });
808
809 _ = editor.update(cx, |editor, window, cx| {
810 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
811 editor.update_selection(
812 DisplayPoint::new(DisplayRow(1), 1),
813 0,
814 gpui::Point::<f32>::default(),
815 window,
816 cx,
817 );
818 editor.end_selection(window, cx);
819
820 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
821 editor.update_selection(
822 DisplayPoint::new(DisplayRow(0), 3),
823 0,
824 gpui::Point::<f32>::default(),
825 window,
826 cx,
827 );
828 editor.end_selection(window, cx);
829 assert_eq!(
830 editor.selections.display_ranges(cx),
831 [
832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
833 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
834 ]
835 );
836 });
837
838 _ = editor.update(cx, |editor, window, cx| {
839 editor.cancel(&Cancel, window, cx);
840 assert_eq!(
841 editor.selections.display_ranges(cx),
842 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
843 );
844 });
845
846 _ = editor.update(cx, |editor, window, cx| {
847 editor.cancel(&Cancel, window, cx);
848 assert_eq!(
849 editor.selections.display_ranges(cx),
850 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
851 );
852 });
853}
854
855#[gpui::test]
856fn test_fold_action(cx: &mut TestAppContext) {
857 init_test(cx, |_| {});
858
859 let editor = cx.add_window(|window, cx| {
860 let buffer = MultiBuffer::build_simple(
861 &"
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {
870 2
871 }
872
873 fn c() {
874 3
875 }
876 }
877 "
878 .unindent(),
879 cx,
880 );
881 build_editor(buffer.clone(), window, cx)
882 });
883
884 _ = editor.update(cx, |editor, window, cx| {
885 editor.change_selections(None, window, cx, |s| {
886 s.select_display_ranges([
887 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
888 ]);
889 });
890 editor.fold(&Fold, window, cx);
891 assert_eq!(
892 editor.display_text(cx),
893 "
894 impl Foo {
895 // Hello!
896
897 fn a() {
898 1
899 }
900
901 fn b() {⋯
902 }
903
904 fn c() {⋯
905 }
906 }
907 "
908 .unindent(),
909 );
910
911 editor.fold(&Fold, window, cx);
912 assert_eq!(
913 editor.display_text(cx),
914 "
915 impl Foo {⋯
916 }
917 "
918 .unindent(),
919 );
920
921 editor.unfold_lines(&UnfoldLines, window, cx);
922 assert_eq!(
923 editor.display_text(cx),
924 "
925 impl Foo {
926 // Hello!
927
928 fn a() {
929 1
930 }
931
932 fn b() {⋯
933 }
934
935 fn c() {⋯
936 }
937 }
938 "
939 .unindent(),
940 );
941
942 editor.unfold_lines(&UnfoldLines, window, cx);
943 assert_eq!(
944 editor.display_text(cx),
945 editor.buffer.read(cx).read(cx).text()
946 );
947 });
948}
949
950#[gpui::test]
951fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
952 init_test(cx, |_| {});
953
954 let editor = cx.add_window(|window, cx| {
955 let buffer = MultiBuffer::build_simple(
956 &"
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():
964 print(2)
965
966 def c():
967 print(3)
968 "
969 .unindent(),
970 cx,
971 );
972 build_editor(buffer.clone(), window, cx)
973 });
974
975 _ = editor.update(cx, |editor, window, cx| {
976 editor.change_selections(None, window, cx, |s| {
977 s.select_display_ranges([
978 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
979 ]);
980 });
981 editor.fold(&Fold, window, cx);
982 assert_eq!(
983 editor.display_text(cx),
984 "
985 class Foo:
986 # Hello!
987
988 def a():
989 print(1)
990
991 def b():⋯
992
993 def c():⋯
994 "
995 .unindent(),
996 );
997
998 editor.fold(&Fold, window, cx);
999 assert_eq!(
1000 editor.display_text(cx),
1001 "
1002 class Foo:⋯
1003 "
1004 .unindent(),
1005 );
1006
1007 editor.unfold_lines(&UnfoldLines, window, cx);
1008 assert_eq!(
1009 editor.display_text(cx),
1010 "
1011 class Foo:
1012 # Hello!
1013
1014 def a():
1015 print(1)
1016
1017 def b():⋯
1018
1019 def c():⋯
1020 "
1021 .unindent(),
1022 );
1023
1024 editor.unfold_lines(&UnfoldLines, window, cx);
1025 assert_eq!(
1026 editor.display_text(cx),
1027 editor.buffer.read(cx).read(cx).text()
1028 );
1029 });
1030}
1031
1032#[gpui::test]
1033fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1034 init_test(cx, |_| {});
1035
1036 let editor = cx.add_window(|window, cx| {
1037 let buffer = MultiBuffer::build_simple(
1038 &"
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():
1046 print(2)
1047
1048
1049 def c():
1050 print(3)
1051
1052
1053 "
1054 .unindent(),
1055 cx,
1056 );
1057 build_editor(buffer.clone(), window, cx)
1058 });
1059
1060 _ = editor.update(cx, |editor, window, cx| {
1061 editor.change_selections(None, window, cx, |s| {
1062 s.select_display_ranges([
1063 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1064 ]);
1065 });
1066 editor.fold(&Fold, window, cx);
1067 assert_eq!(
1068 editor.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 editor.fold(&Fold, window, cx);
1087 assert_eq!(
1088 editor.display_text(cx),
1089 "
1090 class Foo:⋯
1091
1092
1093 "
1094 .unindent(),
1095 );
1096
1097 editor.unfold_lines(&UnfoldLines, window, cx);
1098 assert_eq!(
1099 editor.display_text(cx),
1100 "
1101 class Foo:
1102 # Hello!
1103
1104 def a():
1105 print(1)
1106
1107 def b():⋯
1108
1109
1110 def c():⋯
1111
1112
1113 "
1114 .unindent(),
1115 );
1116
1117 editor.unfold_lines(&UnfoldLines, window, cx);
1118 assert_eq!(
1119 editor.display_text(cx),
1120 editor.buffer.read(cx).read(cx).text()
1121 );
1122 });
1123}
1124
1125#[gpui::test]
1126fn test_fold_at_level(cx: &mut TestAppContext) {
1127 init_test(cx, |_| {});
1128
1129 let editor = cx.add_window(|window, cx| {
1130 let buffer = MultiBuffer::build_simple(
1131 &"
1132 class Foo:
1133 # Hello!
1134
1135 def a():
1136 print(1)
1137
1138 def b():
1139 print(2)
1140
1141
1142 class Bar:
1143 # World!
1144
1145 def a():
1146 print(1)
1147
1148 def b():
1149 print(2)
1150
1151
1152 "
1153 .unindent(),
1154 cx,
1155 );
1156 build_editor(buffer.clone(), window, cx)
1157 });
1158
1159 _ = editor.update(cx, |editor, window, cx| {
1160 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1161 assert_eq!(
1162 editor.display_text(cx),
1163 "
1164 class Foo:
1165 # Hello!
1166
1167 def a():⋯
1168
1169 def b():⋯
1170
1171
1172 class Bar:
1173 # World!
1174
1175 def a():⋯
1176
1177 def b():⋯
1178
1179
1180 "
1181 .unindent(),
1182 );
1183
1184 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1185 assert_eq!(
1186 editor.display_text(cx),
1187 "
1188 class Foo:⋯
1189
1190
1191 class Bar:⋯
1192
1193
1194 "
1195 .unindent(),
1196 );
1197
1198 editor.unfold_all(&UnfoldAll, window, cx);
1199 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1200 assert_eq!(
1201 editor.display_text(cx),
1202 "
1203 class Foo:
1204 # Hello!
1205
1206 def a():
1207 print(1)
1208
1209 def b():
1210 print(2)
1211
1212
1213 class Bar:
1214 # World!
1215
1216 def a():
1217 print(1)
1218
1219 def b():
1220 print(2)
1221
1222
1223 "
1224 .unindent(),
1225 );
1226
1227 assert_eq!(
1228 editor.display_text(cx),
1229 editor.buffer.read(cx).read(cx).text()
1230 );
1231 });
1232}
1233
1234#[gpui::test]
1235fn test_move_cursor(cx: &mut TestAppContext) {
1236 init_test(cx, |_| {});
1237
1238 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1239 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1240
1241 buffer.update(cx, |buffer, cx| {
1242 buffer.edit(
1243 vec![
1244 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1245 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1246 ],
1247 None,
1248 cx,
1249 );
1250 });
1251 _ = editor.update(cx, |editor, window, cx| {
1252 assert_eq!(
1253 editor.selections.display_ranges(cx),
1254 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1255 );
1256
1257 editor.move_down(&MoveDown, window, cx);
1258 assert_eq!(
1259 editor.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1261 );
1262
1263 editor.move_right(&MoveRight, window, cx);
1264 assert_eq!(
1265 editor.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1267 );
1268
1269 editor.move_left(&MoveLeft, window, cx);
1270 assert_eq!(
1271 editor.selections.display_ranges(cx),
1272 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1273 );
1274
1275 editor.move_up(&MoveUp, window, cx);
1276 assert_eq!(
1277 editor.selections.display_ranges(cx),
1278 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1279 );
1280
1281 editor.move_to_end(&MoveToEnd, window, cx);
1282 assert_eq!(
1283 editor.selections.display_ranges(cx),
1284 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1285 );
1286
1287 editor.move_to_beginning(&MoveToBeginning, window, cx);
1288 assert_eq!(
1289 editor.selections.display_ranges(cx),
1290 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1291 );
1292
1293 editor.change_selections(None, window, cx, |s| {
1294 s.select_display_ranges([
1295 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1296 ]);
1297 });
1298 editor.select_to_beginning(&SelectToBeginning, window, cx);
1299 assert_eq!(
1300 editor.selections.display_ranges(cx),
1301 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1302 );
1303
1304 editor.select_to_end(&SelectToEnd, window, cx);
1305 assert_eq!(
1306 editor.selections.display_ranges(cx),
1307 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1308 );
1309 });
1310}
1311
1312// TODO: Re-enable this test
1313#[cfg(target_os = "macos")]
1314#[gpui::test]
1315fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1316 init_test(cx, |_| {});
1317
1318 let editor = cx.add_window(|window, cx| {
1319 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1320 build_editor(buffer.clone(), window, cx)
1321 });
1322
1323 assert_eq!('🟥'.len_utf8(), 4);
1324 assert_eq!('α'.len_utf8(), 2);
1325
1326 _ = editor.update(cx, |editor, window, cx| {
1327 editor.fold_creases(
1328 vec![
1329 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1330 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1332 ],
1333 true,
1334 window,
1335 cx,
1336 );
1337 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1338
1339 editor.move_right(&MoveRight, window, cx);
1340 assert_eq!(
1341 editor.selections.display_ranges(cx),
1342 &[empty_range(0, "🟥".len())]
1343 );
1344 editor.move_right(&MoveRight, window, cx);
1345 assert_eq!(
1346 editor.selections.display_ranges(cx),
1347 &[empty_range(0, "🟥🟧".len())]
1348 );
1349 editor.move_right(&MoveRight, window, cx);
1350 assert_eq!(
1351 editor.selections.display_ranges(cx),
1352 &[empty_range(0, "🟥🟧⋯".len())]
1353 );
1354
1355 editor.move_down(&MoveDown, window, cx);
1356 assert_eq!(
1357 editor.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 editor.move_left(&MoveLeft, window, cx);
1361 assert_eq!(
1362 editor.selections.display_ranges(cx),
1363 &[empty_range(1, "ab⋯".len())]
1364 );
1365 editor.move_left(&MoveLeft, window, cx);
1366 assert_eq!(
1367 editor.selections.display_ranges(cx),
1368 &[empty_range(1, "ab".len())]
1369 );
1370 editor.move_left(&MoveLeft, window, cx);
1371 assert_eq!(
1372 editor.selections.display_ranges(cx),
1373 &[empty_range(1, "a".len())]
1374 );
1375
1376 editor.move_down(&MoveDown, window, cx);
1377 assert_eq!(
1378 editor.selections.display_ranges(cx),
1379 &[empty_range(2, "α".len())]
1380 );
1381 editor.move_right(&MoveRight, window, cx);
1382 assert_eq!(
1383 editor.selections.display_ranges(cx),
1384 &[empty_range(2, "αβ".len())]
1385 );
1386 editor.move_right(&MoveRight, window, cx);
1387 assert_eq!(
1388 editor.selections.display_ranges(cx),
1389 &[empty_range(2, "αβ⋯".len())]
1390 );
1391 editor.move_right(&MoveRight, window, cx);
1392 assert_eq!(
1393 editor.selections.display_ranges(cx),
1394 &[empty_range(2, "αβ⋯ε".len())]
1395 );
1396
1397 editor.move_up(&MoveUp, window, cx);
1398 assert_eq!(
1399 editor.selections.display_ranges(cx),
1400 &[empty_range(1, "ab⋯e".len())]
1401 );
1402 editor.move_down(&MoveDown, window, cx);
1403 assert_eq!(
1404 editor.selections.display_ranges(cx),
1405 &[empty_range(2, "αβ⋯ε".len())]
1406 );
1407 editor.move_up(&MoveUp, window, cx);
1408 assert_eq!(
1409 editor.selections.display_ranges(cx),
1410 &[empty_range(1, "ab⋯e".len())]
1411 );
1412
1413 editor.move_up(&MoveUp, window, cx);
1414 assert_eq!(
1415 editor.selections.display_ranges(cx),
1416 &[empty_range(0, "🟥🟧".len())]
1417 );
1418 editor.move_left(&MoveLeft, window, cx);
1419 assert_eq!(
1420 editor.selections.display_ranges(cx),
1421 &[empty_range(0, "🟥".len())]
1422 );
1423 editor.move_left(&MoveLeft, window, cx);
1424 assert_eq!(
1425 editor.selections.display_ranges(cx),
1426 &[empty_range(0, "".len())]
1427 );
1428 });
1429}
1430
1431#[gpui::test]
1432fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1433 init_test(cx, |_| {});
1434
1435 let editor = cx.add_window(|window, cx| {
1436 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1437 build_editor(buffer.clone(), window, cx)
1438 });
1439 _ = editor.update(cx, |editor, window, cx| {
1440 editor.change_selections(None, window, cx, |s| {
1441 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1442 });
1443
1444 // moving above start of document should move selection to start of document,
1445 // but the next move down should still be at the original goal_x
1446 editor.move_up(&MoveUp, window, cx);
1447 assert_eq!(
1448 editor.selections.display_ranges(cx),
1449 &[empty_range(0, "".len())]
1450 );
1451
1452 editor.move_down(&MoveDown, window, cx);
1453 assert_eq!(
1454 editor.selections.display_ranges(cx),
1455 &[empty_range(1, "abcd".len())]
1456 );
1457
1458 editor.move_down(&MoveDown, window, cx);
1459 assert_eq!(
1460 editor.selections.display_ranges(cx),
1461 &[empty_range(2, "αβγ".len())]
1462 );
1463
1464 editor.move_down(&MoveDown, window, cx);
1465 assert_eq!(
1466 editor.selections.display_ranges(cx),
1467 &[empty_range(3, "abcd".len())]
1468 );
1469
1470 editor.move_down(&MoveDown, window, cx);
1471 assert_eq!(
1472 editor.selections.display_ranges(cx),
1473 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1474 );
1475
1476 // moving past end of document should not change goal_x
1477 editor.move_down(&MoveDown, window, cx);
1478 assert_eq!(
1479 editor.selections.display_ranges(cx),
1480 &[empty_range(5, "".len())]
1481 );
1482
1483 editor.move_down(&MoveDown, window, cx);
1484 assert_eq!(
1485 editor.selections.display_ranges(cx),
1486 &[empty_range(5, "".len())]
1487 );
1488
1489 editor.move_up(&MoveUp, window, cx);
1490 assert_eq!(
1491 editor.selections.display_ranges(cx),
1492 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1493 );
1494
1495 editor.move_up(&MoveUp, window, cx);
1496 assert_eq!(
1497 editor.selections.display_ranges(cx),
1498 &[empty_range(3, "abcd".len())]
1499 );
1500
1501 editor.move_up(&MoveUp, window, cx);
1502 assert_eq!(
1503 editor.selections.display_ranges(cx),
1504 &[empty_range(2, "αβγ".len())]
1505 );
1506 });
1507}
1508
1509#[gpui::test]
1510fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1511 init_test(cx, |_| {});
1512 let move_to_beg = MoveToBeginningOfLine {
1513 stop_at_soft_wraps: true,
1514 stop_at_indent: true,
1515 };
1516
1517 let delete_to_beg = DeleteToBeginningOfLine {
1518 stop_at_indent: false,
1519 };
1520
1521 let move_to_end = MoveToEndOfLine {
1522 stop_at_soft_wraps: true,
1523 };
1524
1525 let editor = cx.add_window(|window, cx| {
1526 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1527 build_editor(buffer, window, cx)
1528 });
1529 _ = editor.update(cx, |editor, window, cx| {
1530 editor.change_selections(None, window, cx, |s| {
1531 s.select_display_ranges([
1532 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1533 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1534 ]);
1535 });
1536 });
1537
1538 _ = editor.update(cx, |editor, window, cx| {
1539 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1540 assert_eq!(
1541 editor.selections.display_ranges(cx),
1542 &[
1543 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1544 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1545 ]
1546 );
1547 });
1548
1549 _ = editor.update(cx, |editor, window, cx| {
1550 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1551 assert_eq!(
1552 editor.selections.display_ranges(cx),
1553 &[
1554 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1555 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1556 ]
1557 );
1558 });
1559
1560 _ = editor.update(cx, |editor, window, cx| {
1561 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1562 assert_eq!(
1563 editor.selections.display_ranges(cx),
1564 &[
1565 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1566 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1567 ]
1568 );
1569 });
1570
1571 _ = editor.update(cx, |editor, window, cx| {
1572 editor.move_to_end_of_line(&move_to_end, window, cx);
1573 assert_eq!(
1574 editor.selections.display_ranges(cx),
1575 &[
1576 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1577 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1578 ]
1579 );
1580 });
1581
1582 // Moving to the end of line again is a no-op.
1583 _ = editor.update(cx, |editor, window, cx| {
1584 editor.move_to_end_of_line(&move_to_end, window, cx);
1585 assert_eq!(
1586 editor.selections.display_ranges(cx),
1587 &[
1588 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1589 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1590 ]
1591 );
1592 });
1593
1594 _ = editor.update(cx, |editor, window, cx| {
1595 editor.move_left(&MoveLeft, window, cx);
1596 editor.select_to_beginning_of_line(
1597 &SelectToBeginningOfLine {
1598 stop_at_soft_wraps: true,
1599 stop_at_indent: true,
1600 },
1601 window,
1602 cx,
1603 );
1604 assert_eq!(
1605 editor.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1609 ]
1610 );
1611 });
1612
1613 _ = editor.update(cx, |editor, window, cx| {
1614 editor.select_to_beginning_of_line(
1615 &SelectToBeginningOfLine {
1616 stop_at_soft_wraps: true,
1617 stop_at_indent: true,
1618 },
1619 window,
1620 cx,
1621 );
1622 assert_eq!(
1623 editor.selections.display_ranges(cx),
1624 &[
1625 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1626 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1627 ]
1628 );
1629 });
1630
1631 _ = editor.update(cx, |editor, window, cx| {
1632 editor.select_to_beginning_of_line(
1633 &SelectToBeginningOfLine {
1634 stop_at_soft_wraps: true,
1635 stop_at_indent: true,
1636 },
1637 window,
1638 cx,
1639 );
1640 assert_eq!(
1641 editor.selections.display_ranges(cx),
1642 &[
1643 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1644 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1645 ]
1646 );
1647 });
1648
1649 _ = editor.update(cx, |editor, window, cx| {
1650 editor.select_to_end_of_line(
1651 &SelectToEndOfLine {
1652 stop_at_soft_wraps: true,
1653 },
1654 window,
1655 cx,
1656 );
1657 assert_eq!(
1658 editor.selections.display_ranges(cx),
1659 &[
1660 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1661 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1662 ]
1663 );
1664 });
1665
1666 _ = editor.update(cx, |editor, window, cx| {
1667 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1668 assert_eq!(editor.display_text(cx), "ab\n de");
1669 assert_eq!(
1670 editor.selections.display_ranges(cx),
1671 &[
1672 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1673 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1674 ]
1675 );
1676 });
1677
1678 _ = editor.update(cx, |editor, window, cx| {
1679 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1680 assert_eq!(editor.display_text(cx), "\n");
1681 assert_eq!(
1682 editor.selections.display_ranges(cx),
1683 &[
1684 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1685 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1686 ]
1687 );
1688 });
1689}
1690
1691#[gpui::test]
1692fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1693 init_test(cx, |_| {});
1694 let move_to_beg = MoveToBeginningOfLine {
1695 stop_at_soft_wraps: false,
1696 stop_at_indent: false,
1697 };
1698
1699 let move_to_end = MoveToEndOfLine {
1700 stop_at_soft_wraps: false,
1701 };
1702
1703 let editor = cx.add_window(|window, cx| {
1704 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1705 build_editor(buffer, window, cx)
1706 });
1707
1708 _ = editor.update(cx, |editor, window, cx| {
1709 editor.set_wrap_width(Some(140.0.into()), cx);
1710
1711 // We expect the following lines after wrapping
1712 // ```
1713 // thequickbrownfox
1714 // jumpedoverthelazydo
1715 // gs
1716 // ```
1717 // The final `gs` was soft-wrapped onto a new line.
1718 assert_eq!(
1719 "thequickbrownfox\njumpedoverthelaz\nydogs",
1720 editor.display_text(cx),
1721 );
1722
1723 // First, let's assert behavior on the first line, that was not soft-wrapped.
1724 // Start the cursor at the `k` on the first line
1725 editor.change_selections(None, window, cx, |s| {
1726 s.select_display_ranges([
1727 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1728 ]);
1729 });
1730
1731 // Moving to the beginning of the line should put us at the beginning of the line.
1732 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1733 assert_eq!(
1734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1735 editor.selections.display_ranges(cx)
1736 );
1737
1738 // Moving to the end of the line should put us at the end of the line.
1739 editor.move_to_end_of_line(&move_to_end, window, cx);
1740 assert_eq!(
1741 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1742 editor.selections.display_ranges(cx)
1743 );
1744
1745 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1746 // Start the cursor at the last line (`y` that was wrapped to a new line)
1747 editor.change_selections(None, window, cx, |s| {
1748 s.select_display_ranges([
1749 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1750 ]);
1751 });
1752
1753 // Moving to the beginning of the line should put us at the start of the second line of
1754 // display text, i.e., the `j`.
1755 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1756 assert_eq!(
1757 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1758 editor.selections.display_ranges(cx)
1759 );
1760
1761 // Moving to the beginning of the line again should be a no-op.
1762 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1763 assert_eq!(
1764 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1765 editor.selections.display_ranges(cx)
1766 );
1767
1768 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1769 // next display line.
1770 editor.move_to_end_of_line(&move_to_end, window, cx);
1771 assert_eq!(
1772 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1773 editor.selections.display_ranges(cx)
1774 );
1775
1776 // Moving to the end of the line again should be a no-op.
1777 editor.move_to_end_of_line(&move_to_end, window, cx);
1778 assert_eq!(
1779 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1780 editor.selections.display_ranges(cx)
1781 );
1782 });
1783}
1784
1785#[gpui::test]
1786fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1787 init_test(cx, |_| {});
1788
1789 let move_to_beg = MoveToBeginningOfLine {
1790 stop_at_soft_wraps: true,
1791 stop_at_indent: true,
1792 };
1793
1794 let select_to_beg = SelectToBeginningOfLine {
1795 stop_at_soft_wraps: true,
1796 stop_at_indent: true,
1797 };
1798
1799 let delete_to_beg = DeleteToBeginningOfLine {
1800 stop_at_indent: true,
1801 };
1802
1803 let move_to_end = MoveToEndOfLine {
1804 stop_at_soft_wraps: false,
1805 };
1806
1807 let editor = cx.add_window(|window, cx| {
1808 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1809 build_editor(buffer, window, cx)
1810 });
1811
1812 _ = editor.update(cx, |editor, window, cx| {
1813 editor.change_selections(None, window, cx, |s| {
1814 s.select_display_ranges([
1815 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1816 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1817 ]);
1818 });
1819
1820 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1821 // and the second cursor at the first non-whitespace character in the line.
1822 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1823 assert_eq!(
1824 editor.selections.display_ranges(cx),
1825 &[
1826 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1827 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1828 ]
1829 );
1830
1831 // Moving to the beginning of the line again should be a no-op for the first cursor,
1832 // and should move the second cursor to the beginning of the line.
1833 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1834 assert_eq!(
1835 editor.selections.display_ranges(cx),
1836 &[
1837 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1838 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1839 ]
1840 );
1841
1842 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1843 // and should move the second cursor back to the first non-whitespace character in the line.
1844 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1845 assert_eq!(
1846 editor.selections.display_ranges(cx),
1847 &[
1848 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1849 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1850 ]
1851 );
1852
1853 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1854 // and to the first non-whitespace character in the line for the second cursor.
1855 editor.move_to_end_of_line(&move_to_end, window, cx);
1856 editor.move_left(&MoveLeft, window, cx);
1857 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1858 assert_eq!(
1859 editor.selections.display_ranges(cx),
1860 &[
1861 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1862 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1863 ]
1864 );
1865
1866 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1867 // and should select to the beginning of the line for the second cursor.
1868 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1869 assert_eq!(
1870 editor.selections.display_ranges(cx),
1871 &[
1872 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1873 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1874 ]
1875 );
1876
1877 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1878 // and should delete to the first non-whitespace character in the line for the second cursor.
1879 editor.move_to_end_of_line(&move_to_end, window, cx);
1880 editor.move_left(&MoveLeft, window, cx);
1881 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1882 assert_eq!(editor.text(cx), "c\n f");
1883 });
1884}
1885
1886#[gpui::test]
1887fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1888 init_test(cx, |_| {});
1889
1890 let editor = cx.add_window(|window, cx| {
1891 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1892 build_editor(buffer, window, cx)
1893 });
1894 _ = editor.update(cx, |editor, window, cx| {
1895 editor.change_selections(None, window, cx, |s| {
1896 s.select_display_ranges([
1897 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1898 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1899 ])
1900 });
1901
1902 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1903 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1904
1905 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1906 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1907
1908 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1909 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1910
1911 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1912 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1913
1914 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1915 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1916
1917 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1918 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1919
1920 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1921 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1922
1923 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1924 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1925
1926 editor.move_right(&MoveRight, window, cx);
1927 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1928 assert_selection_ranges(
1929 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1930 editor,
1931 cx,
1932 );
1933
1934 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1935 assert_selection_ranges(
1936 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1937 editor,
1938 cx,
1939 );
1940
1941 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1942 assert_selection_ranges(
1943 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1944 editor,
1945 cx,
1946 );
1947 });
1948}
1949
1950#[gpui::test]
1951fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1952 init_test(cx, |_| {});
1953
1954 let editor = cx.add_window(|window, cx| {
1955 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1956 build_editor(buffer, window, cx)
1957 });
1958
1959 _ = editor.update(cx, |editor, window, cx| {
1960 editor.set_wrap_width(Some(140.0.into()), cx);
1961 assert_eq!(
1962 editor.display_text(cx),
1963 "use one::{\n two::three::\n four::five\n};"
1964 );
1965
1966 editor.change_selections(None, window, cx, |s| {
1967 s.select_display_ranges([
1968 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1969 ]);
1970 });
1971
1972 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1973 assert_eq!(
1974 editor.selections.display_ranges(cx),
1975 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1976 );
1977
1978 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1979 assert_eq!(
1980 editor.selections.display_ranges(cx),
1981 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1982 );
1983
1984 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1985 assert_eq!(
1986 editor.selections.display_ranges(cx),
1987 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1988 );
1989
1990 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1991 assert_eq!(
1992 editor.selections.display_ranges(cx),
1993 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1994 );
1995
1996 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1997 assert_eq!(
1998 editor.selections.display_ranges(cx),
1999 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2000 );
2001
2002 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2003 assert_eq!(
2004 editor.selections.display_ranges(cx),
2005 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.editor(|editor, window, _| {
2016 editor
2017 .style()
2018 .unwrap()
2019 .text
2020 .line_height_in_pixels(window.rem_size())
2021 });
2022 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2023
2024 cx.set_state(
2025 &r#"ˇone
2026 two
2027
2028 three
2029 fourˇ
2030 five
2031
2032 six"#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, window, cx| {
2037 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2038 });
2039 cx.assert_editor_state(
2040 &r#"one
2041 two
2042 ˇ
2043 three
2044 four
2045 five
2046 ˇ
2047 six"#
2048 .unindent(),
2049 );
2050
2051 cx.update_editor(|editor, window, cx| {
2052 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2053 });
2054 cx.assert_editor_state(
2055 &r#"one
2056 two
2057
2058 three
2059 four
2060 five
2061 ˇ
2062 sixˇ"#
2063 .unindent(),
2064 );
2065
2066 cx.update_editor(|editor, window, cx| {
2067 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2068 });
2069 cx.assert_editor_state(
2070 &r#"one
2071 two
2072
2073 three
2074 four
2075 five
2076
2077 sixˇ"#
2078 .unindent(),
2079 );
2080
2081 cx.update_editor(|editor, window, cx| {
2082 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2083 });
2084 cx.assert_editor_state(
2085 &r#"one
2086 two
2087
2088 three
2089 four
2090 five
2091 ˇ
2092 six"#
2093 .unindent(),
2094 );
2095
2096 cx.update_editor(|editor, window, cx| {
2097 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2098 });
2099 cx.assert_editor_state(
2100 &r#"one
2101 two
2102 ˇ
2103 three
2104 four
2105 five
2106
2107 six"#
2108 .unindent(),
2109 );
2110
2111 cx.update_editor(|editor, window, cx| {
2112 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2113 });
2114 cx.assert_editor_state(
2115 &r#"ˇone
2116 two
2117
2118 three
2119 four
2120 five
2121
2122 six"#
2123 .unindent(),
2124 );
2125}
2126
2127#[gpui::test]
2128async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2129 init_test(cx, |_| {});
2130 let mut cx = EditorTestContext::new(cx).await;
2131 let line_height = cx.editor(|editor, window, _| {
2132 editor
2133 .style()
2134 .unwrap()
2135 .text
2136 .line_height_in_pixels(window.rem_size())
2137 });
2138 let window = cx.window;
2139 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2140
2141 cx.set_state(
2142 r#"ˇone
2143 two
2144 three
2145 four
2146 five
2147 six
2148 seven
2149 eight
2150 nine
2151 ten
2152 "#,
2153 );
2154
2155 cx.update_editor(|editor, window, cx| {
2156 assert_eq!(
2157 editor.snapshot(window, cx).scroll_position(),
2158 gpui::Point::new(0., 0.)
2159 );
2160 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2161 assert_eq!(
2162 editor.snapshot(window, cx).scroll_position(),
2163 gpui::Point::new(0., 3.)
2164 );
2165 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2166 assert_eq!(
2167 editor.snapshot(window, cx).scroll_position(),
2168 gpui::Point::new(0., 6.)
2169 );
2170 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2171 assert_eq!(
2172 editor.snapshot(window, cx).scroll_position(),
2173 gpui::Point::new(0., 3.)
2174 );
2175
2176 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2177 assert_eq!(
2178 editor.snapshot(window, cx).scroll_position(),
2179 gpui::Point::new(0., 1.)
2180 );
2181 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2182 assert_eq!(
2183 editor.snapshot(window, cx).scroll_position(),
2184 gpui::Point::new(0., 3.)
2185 );
2186 });
2187}
2188
2189#[gpui::test]
2190async fn test_autoscroll(cx: &mut TestAppContext) {
2191 init_test(cx, |_| {});
2192 let mut cx = EditorTestContext::new(cx).await;
2193
2194 let line_height = cx.update_editor(|editor, window, cx| {
2195 editor.set_vertical_scroll_margin(2, cx);
2196 editor
2197 .style()
2198 .unwrap()
2199 .text
2200 .line_height_in_pixels(window.rem_size())
2201 });
2202 let window = cx.window;
2203 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2204
2205 cx.set_state(
2206 r#"ˇone
2207 two
2208 three
2209 four
2210 five
2211 six
2212 seven
2213 eight
2214 nine
2215 ten
2216 "#,
2217 );
2218 cx.update_editor(|editor, window, cx| {
2219 assert_eq!(
2220 editor.snapshot(window, cx).scroll_position(),
2221 gpui::Point::new(0., 0.0)
2222 );
2223 });
2224
2225 // Add a cursor below the visible area. Since both cursors cannot fit
2226 // on screen, the editor autoscrolls to reveal the newest cursor, and
2227 // allows the vertical scroll margin below that cursor.
2228 cx.update_editor(|editor, window, cx| {
2229 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2230 selections.select_ranges([
2231 Point::new(0, 0)..Point::new(0, 0),
2232 Point::new(6, 0)..Point::new(6, 0),
2233 ]);
2234 })
2235 });
2236 cx.update_editor(|editor, window, cx| {
2237 assert_eq!(
2238 editor.snapshot(window, cx).scroll_position(),
2239 gpui::Point::new(0., 3.0)
2240 );
2241 });
2242
2243 // Move down. The editor cursor scrolls down to track the newest cursor.
2244 cx.update_editor(|editor, window, cx| {
2245 editor.move_down(&Default::default(), window, cx);
2246 });
2247 cx.update_editor(|editor, window, cx| {
2248 assert_eq!(
2249 editor.snapshot(window, cx).scroll_position(),
2250 gpui::Point::new(0., 4.0)
2251 );
2252 });
2253
2254 // Add a cursor above the visible area. Since both cursors fit on screen,
2255 // the editor scrolls to show both.
2256 cx.update_editor(|editor, window, cx| {
2257 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2258 selections.select_ranges([
2259 Point::new(1, 0)..Point::new(1, 0),
2260 Point::new(6, 0)..Point::new(6, 0),
2261 ]);
2262 })
2263 });
2264 cx.update_editor(|editor, window, cx| {
2265 assert_eq!(
2266 editor.snapshot(window, cx).scroll_position(),
2267 gpui::Point::new(0., 1.0)
2268 );
2269 });
2270}
2271
2272#[gpui::test]
2273async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2274 init_test(cx, |_| {});
2275 let mut cx = EditorTestContext::new(cx).await;
2276
2277 let line_height = cx.editor(|editor, window, _cx| {
2278 editor
2279 .style()
2280 .unwrap()
2281 .text
2282 .line_height_in_pixels(window.rem_size())
2283 });
2284 let window = cx.window;
2285 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2286 cx.set_state(
2287 &r#"
2288 ˇone
2289 two
2290 threeˇ
2291 four
2292 five
2293 six
2294 seven
2295 eight
2296 nine
2297 ten
2298 "#
2299 .unindent(),
2300 );
2301
2302 cx.update_editor(|editor, window, cx| {
2303 editor.move_page_down(&MovePageDown::default(), window, cx)
2304 });
2305 cx.assert_editor_state(
2306 &r#"
2307 one
2308 two
2309 three
2310 ˇfour
2311 five
2312 sixˇ
2313 seven
2314 eight
2315 nine
2316 ten
2317 "#
2318 .unindent(),
2319 );
2320
2321 cx.update_editor(|editor, window, cx| {
2322 editor.move_page_down(&MovePageDown::default(), window, cx)
2323 });
2324 cx.assert_editor_state(
2325 &r#"
2326 one
2327 two
2328 three
2329 four
2330 five
2331 six
2332 ˇseven
2333 eight
2334 nineˇ
2335 ten
2336 "#
2337 .unindent(),
2338 );
2339
2340 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2341 cx.assert_editor_state(
2342 &r#"
2343 one
2344 two
2345 three
2346 ˇfour
2347 five
2348 sixˇ
2349 seven
2350 eight
2351 nine
2352 ten
2353 "#
2354 .unindent(),
2355 );
2356
2357 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2358 cx.assert_editor_state(
2359 &r#"
2360 ˇone
2361 two
2362 threeˇ
2363 four
2364 five
2365 six
2366 seven
2367 eight
2368 nine
2369 ten
2370 "#
2371 .unindent(),
2372 );
2373
2374 // Test select collapsing
2375 cx.update_editor(|editor, window, cx| {
2376 editor.move_page_down(&MovePageDown::default(), window, cx);
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 });
2380 cx.assert_editor_state(
2381 &r#"
2382 one
2383 two
2384 three
2385 four
2386 five
2387 six
2388 seven
2389 eight
2390 nine
2391 ˇten
2392 ˇ"#
2393 .unindent(),
2394 );
2395}
2396
2397#[gpui::test]
2398async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2399 init_test(cx, |_| {});
2400 let mut cx = EditorTestContext::new(cx).await;
2401 cx.set_state("one «two threeˇ» four");
2402 cx.update_editor(|editor, window, cx| {
2403 editor.delete_to_beginning_of_line(
2404 &DeleteToBeginningOfLine {
2405 stop_at_indent: false,
2406 },
2407 window,
2408 cx,
2409 );
2410 assert_eq!(editor.text(cx), " four");
2411 });
2412}
2413
2414#[gpui::test]
2415fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2416 init_test(cx, |_| {});
2417
2418 let editor = cx.add_window(|window, cx| {
2419 let buffer = MultiBuffer::build_simple("one two three four", cx);
2420 build_editor(buffer.clone(), window, cx)
2421 });
2422
2423 _ = editor.update(cx, |editor, window, cx| {
2424 editor.change_selections(None, window, cx, |s| {
2425 s.select_display_ranges([
2426 // an empty selection - the preceding word fragment is deleted
2427 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2428 // characters selected - they are deleted
2429 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2430 ])
2431 });
2432 editor.delete_to_previous_word_start(
2433 &DeleteToPreviousWordStart {
2434 ignore_newlines: false,
2435 },
2436 window,
2437 cx,
2438 );
2439 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2440 });
2441
2442 _ = editor.update(cx, |editor, window, cx| {
2443 editor.change_selections(None, window, cx, |s| {
2444 s.select_display_ranges([
2445 // an empty selection - the following word fragment is deleted
2446 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2447 // characters selected - they are deleted
2448 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2449 ])
2450 });
2451 editor.delete_to_next_word_end(
2452 &DeleteToNextWordEnd {
2453 ignore_newlines: false,
2454 },
2455 window,
2456 cx,
2457 );
2458 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2459 });
2460}
2461
2462#[gpui::test]
2463fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2464 init_test(cx, |_| {});
2465
2466 let editor = cx.add_window(|window, cx| {
2467 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2468 build_editor(buffer.clone(), window, cx)
2469 });
2470 let del_to_prev_word_start = DeleteToPreviousWordStart {
2471 ignore_newlines: false,
2472 };
2473 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2474 ignore_newlines: true,
2475 };
2476
2477 _ = editor.update(cx, |editor, window, cx| {
2478 editor.change_selections(None, window, cx, |s| {
2479 s.select_display_ranges([
2480 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2481 ])
2482 });
2483 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2484 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2485 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2486 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2487 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2488 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2489 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2490 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2491 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2492 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2493 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2494 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2495 });
2496}
2497
2498#[gpui::test]
2499fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2500 init_test(cx, |_| {});
2501
2502 let editor = cx.add_window(|window, cx| {
2503 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2504 build_editor(buffer.clone(), window, cx)
2505 });
2506 let del_to_next_word_end = DeleteToNextWordEnd {
2507 ignore_newlines: false,
2508 };
2509 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2510 ignore_newlines: true,
2511 };
2512
2513 _ = editor.update(cx, |editor, window, cx| {
2514 editor.change_selections(None, window, cx, |s| {
2515 s.select_display_ranges([
2516 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2517 ])
2518 });
2519 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2520 assert_eq!(
2521 editor.buffer.read(cx).read(cx).text(),
2522 "one\n two\nthree\n four"
2523 );
2524 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2525 assert_eq!(
2526 editor.buffer.read(cx).read(cx).text(),
2527 "\n two\nthree\n four"
2528 );
2529 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2530 assert_eq!(
2531 editor.buffer.read(cx).read(cx).text(),
2532 "two\nthree\n four"
2533 );
2534 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2535 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2536 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2537 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2538 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2539 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2540 });
2541}
2542
2543#[gpui::test]
2544fn test_newline(cx: &mut TestAppContext) {
2545 init_test(cx, |_| {});
2546
2547 let editor = cx.add_window(|window, cx| {
2548 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2549 build_editor(buffer.clone(), window, cx)
2550 });
2551
2552 _ = editor.update(cx, |editor, window, cx| {
2553 editor.change_selections(None, window, cx, |s| {
2554 s.select_display_ranges([
2555 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2556 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2557 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2558 ])
2559 });
2560
2561 editor.newline(&Newline, window, cx);
2562 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2563 });
2564}
2565
2566#[gpui::test]
2567fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2568 init_test(cx, |_| {});
2569
2570 let editor = cx.add_window(|window, cx| {
2571 let buffer = MultiBuffer::build_simple(
2572 "
2573 a
2574 b(
2575 X
2576 )
2577 c(
2578 X
2579 )
2580 "
2581 .unindent()
2582 .as_str(),
2583 cx,
2584 );
2585 let mut editor = build_editor(buffer.clone(), window, cx);
2586 editor.change_selections(None, window, cx, |s| {
2587 s.select_ranges([
2588 Point::new(2, 4)..Point::new(2, 5),
2589 Point::new(5, 4)..Point::new(5, 5),
2590 ])
2591 });
2592 editor
2593 });
2594
2595 _ = editor.update(cx, |editor, window, cx| {
2596 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2597 editor.buffer.update(cx, |buffer, cx| {
2598 buffer.edit(
2599 [
2600 (Point::new(1, 2)..Point::new(3, 0), ""),
2601 (Point::new(4, 2)..Point::new(6, 0), ""),
2602 ],
2603 None,
2604 cx,
2605 );
2606 assert_eq!(
2607 buffer.read(cx).text(),
2608 "
2609 a
2610 b()
2611 c()
2612 "
2613 .unindent()
2614 );
2615 });
2616 assert_eq!(
2617 editor.selections.ranges(cx),
2618 &[
2619 Point::new(1, 2)..Point::new(1, 2),
2620 Point::new(2, 2)..Point::new(2, 2),
2621 ],
2622 );
2623
2624 editor.newline(&Newline, window, cx);
2625 assert_eq!(
2626 editor.text(cx),
2627 "
2628 a
2629 b(
2630 )
2631 c(
2632 )
2633 "
2634 .unindent()
2635 );
2636
2637 // The selections are moved after the inserted newlines
2638 assert_eq!(
2639 editor.selections.ranges(cx),
2640 &[
2641 Point::new(2, 0)..Point::new(2, 0),
2642 Point::new(4, 0)..Point::new(4, 0),
2643 ],
2644 );
2645 });
2646}
2647
2648#[gpui::test]
2649async fn test_newline_above(cx: &mut TestAppContext) {
2650 init_test(cx, |settings| {
2651 settings.defaults.tab_size = NonZeroU32::new(4)
2652 });
2653
2654 let language = Arc::new(
2655 Language::new(
2656 LanguageConfig::default(),
2657 Some(tree_sitter_rust::LANGUAGE.into()),
2658 )
2659 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2660 .unwrap(),
2661 );
2662
2663 let mut cx = EditorTestContext::new(cx).await;
2664 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2665 cx.set_state(indoc! {"
2666 const a: ˇA = (
2667 (ˇ
2668 «const_functionˇ»(ˇ),
2669 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2670 )ˇ
2671 ˇ);ˇ
2672 "});
2673
2674 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2675 cx.assert_editor_state(indoc! {"
2676 ˇ
2677 const a: A = (
2678 ˇ
2679 (
2680 ˇ
2681 ˇ
2682 const_function(),
2683 ˇ
2684 ˇ
2685 ˇ
2686 ˇ
2687 something_else,
2688 ˇ
2689 )
2690 ˇ
2691 ˇ
2692 );
2693 "});
2694}
2695
2696#[gpui::test]
2697async fn test_newline_below(cx: &mut TestAppContext) {
2698 init_test(cx, |settings| {
2699 settings.defaults.tab_size = NonZeroU32::new(4)
2700 });
2701
2702 let language = Arc::new(
2703 Language::new(
2704 LanguageConfig::default(),
2705 Some(tree_sitter_rust::LANGUAGE.into()),
2706 )
2707 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2708 .unwrap(),
2709 );
2710
2711 let mut cx = EditorTestContext::new(cx).await;
2712 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2713 cx.set_state(indoc! {"
2714 const a: ˇA = (
2715 (ˇ
2716 «const_functionˇ»(ˇ),
2717 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2718 )ˇ
2719 ˇ);ˇ
2720 "});
2721
2722 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2723 cx.assert_editor_state(indoc! {"
2724 const a: A = (
2725 ˇ
2726 (
2727 ˇ
2728 const_function(),
2729 ˇ
2730 ˇ
2731 something_else,
2732 ˇ
2733 ˇ
2734 ˇ
2735 ˇ
2736 )
2737 ˇ
2738 );
2739 ˇ
2740 ˇ
2741 "});
2742}
2743
2744#[gpui::test]
2745async fn test_newline_comments(cx: &mut TestAppContext) {
2746 init_test(cx, |settings| {
2747 settings.defaults.tab_size = NonZeroU32::new(4)
2748 });
2749
2750 let language = Arc::new(Language::new(
2751 LanguageConfig {
2752 line_comments: vec!["//".into()],
2753 ..LanguageConfig::default()
2754 },
2755 None,
2756 ));
2757 {
2758 let mut cx = EditorTestContext::new(cx).await;
2759 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2760 cx.set_state(indoc! {"
2761 // Fooˇ
2762 "});
2763
2764 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2765 cx.assert_editor_state(indoc! {"
2766 // Foo
2767 //ˇ
2768 "});
2769 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2770 cx.set_state(indoc! {"
2771 ˇ// Foo
2772 "});
2773 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2774 cx.assert_editor_state(indoc! {"
2775
2776 ˇ// Foo
2777 "});
2778 }
2779 // Ensure that comment continuations can be disabled.
2780 update_test_language_settings(cx, |settings| {
2781 settings.defaults.extend_comment_on_newline = Some(false);
2782 });
2783 let mut cx = EditorTestContext::new(cx).await;
2784 cx.set_state(indoc! {"
2785 // Fooˇ
2786 "});
2787 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2788 cx.assert_editor_state(indoc! {"
2789 // Foo
2790 ˇ
2791 "});
2792}
2793
2794#[gpui::test]
2795fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2796 init_test(cx, |_| {});
2797
2798 let editor = cx.add_window(|window, cx| {
2799 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2800 let mut editor = build_editor(buffer.clone(), window, cx);
2801 editor.change_selections(None, window, cx, |s| {
2802 s.select_ranges([3..4, 11..12, 19..20])
2803 });
2804 editor
2805 });
2806
2807 _ = editor.update(cx, |editor, window, cx| {
2808 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2809 editor.buffer.update(cx, |buffer, cx| {
2810 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2811 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2812 });
2813 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2814
2815 editor.insert("Z", window, cx);
2816 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2817
2818 // The selections are moved after the inserted characters
2819 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2820 });
2821}
2822
2823#[gpui::test]
2824async fn test_tab(cx: &mut TestAppContext) {
2825 init_test(cx, |settings| {
2826 settings.defaults.tab_size = NonZeroU32::new(3)
2827 });
2828
2829 let mut cx = EditorTestContext::new(cx).await;
2830 cx.set_state(indoc! {"
2831 ˇabˇc
2832 ˇ🏀ˇ🏀ˇefg
2833 dˇ
2834 "});
2835 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2836 cx.assert_editor_state(indoc! {"
2837 ˇab ˇc
2838 ˇ🏀 ˇ🏀 ˇefg
2839 d ˇ
2840 "});
2841
2842 cx.set_state(indoc! {"
2843 a
2844 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2845 "});
2846 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2847 cx.assert_editor_state(indoc! {"
2848 a
2849 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2850 "});
2851}
2852
2853#[gpui::test]
2854async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2855 init_test(cx, |_| {});
2856
2857 let mut cx = EditorTestContext::new(cx).await;
2858 let language = Arc::new(
2859 Language::new(
2860 LanguageConfig::default(),
2861 Some(tree_sitter_rust::LANGUAGE.into()),
2862 )
2863 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2864 .unwrap(),
2865 );
2866 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2867
2868 // cursors that are already at the suggested indent level insert
2869 // a soft tab. cursors that are to the left of the suggested indent
2870 // auto-indent their line.
2871 cx.set_state(indoc! {"
2872 ˇ
2873 const a: B = (
2874 c(
2875 d(
2876 ˇ
2877 )
2878 ˇ
2879 ˇ )
2880 );
2881 "});
2882 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2883 cx.assert_editor_state(indoc! {"
2884 ˇ
2885 const a: B = (
2886 c(
2887 d(
2888 ˇ
2889 )
2890 ˇ
2891 ˇ)
2892 );
2893 "});
2894
2895 // handle auto-indent when there are multiple cursors on the same line
2896 cx.set_state(indoc! {"
2897 const a: B = (
2898 c(
2899 ˇ ˇ
2900 ˇ )
2901 );
2902 "});
2903 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2904 cx.assert_editor_state(indoc! {"
2905 const a: B = (
2906 c(
2907 ˇ
2908 ˇ)
2909 );
2910 "});
2911}
2912
2913#[gpui::test]
2914async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2915 init_test(cx, |settings| {
2916 settings.defaults.tab_size = NonZeroU32::new(4)
2917 });
2918
2919 let language = Arc::new(
2920 Language::new(
2921 LanguageConfig::default(),
2922 Some(tree_sitter_rust::LANGUAGE.into()),
2923 )
2924 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2925 .unwrap(),
2926 );
2927
2928 let mut cx = EditorTestContext::new(cx).await;
2929 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2930 cx.set_state(indoc! {"
2931 fn a() {
2932 if b {
2933 \t ˇc
2934 }
2935 }
2936 "});
2937
2938 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2939 cx.assert_editor_state(indoc! {"
2940 fn a() {
2941 if b {
2942 ˇc
2943 }
2944 }
2945 "});
2946}
2947
2948#[gpui::test]
2949async fn test_indent_outdent(cx: &mut TestAppContext) {
2950 init_test(cx, |settings| {
2951 settings.defaults.tab_size = NonZeroU32::new(4);
2952 });
2953
2954 let mut cx = EditorTestContext::new(cx).await;
2955
2956 cx.set_state(indoc! {"
2957 «oneˇ» «twoˇ»
2958 three
2959 four
2960 "});
2961 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2962 cx.assert_editor_state(indoc! {"
2963 «oneˇ» «twoˇ»
2964 three
2965 four
2966 "});
2967
2968 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2969 cx.assert_editor_state(indoc! {"
2970 «oneˇ» «twoˇ»
2971 three
2972 four
2973 "});
2974
2975 // select across line ending
2976 cx.set_state(indoc! {"
2977 one two
2978 t«hree
2979 ˇ» four
2980 "});
2981 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2982 cx.assert_editor_state(indoc! {"
2983 one two
2984 t«hree
2985 ˇ» four
2986 "});
2987
2988 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2989 cx.assert_editor_state(indoc! {"
2990 one two
2991 t«hree
2992 ˇ» four
2993 "});
2994
2995 // Ensure that indenting/outdenting works when the cursor is at column 0.
2996 cx.set_state(indoc! {"
2997 one two
2998 ˇthree
2999 four
3000 "});
3001 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3002 cx.assert_editor_state(indoc! {"
3003 one two
3004 ˇthree
3005 four
3006 "});
3007
3008 cx.set_state(indoc! {"
3009 one two
3010 ˇ three
3011 four
3012 "});
3013 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3014 cx.assert_editor_state(indoc! {"
3015 one two
3016 ˇthree
3017 four
3018 "});
3019}
3020
3021#[gpui::test]
3022async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3023 init_test(cx, |settings| {
3024 settings.defaults.hard_tabs = Some(true);
3025 });
3026
3027 let mut cx = EditorTestContext::new(cx).await;
3028
3029 // select two ranges on one line
3030 cx.set_state(indoc! {"
3031 «oneˇ» «twoˇ»
3032 three
3033 four
3034 "});
3035 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3036 cx.assert_editor_state(indoc! {"
3037 \t«oneˇ» «twoˇ»
3038 three
3039 four
3040 "});
3041 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3042 cx.assert_editor_state(indoc! {"
3043 \t\t«oneˇ» «twoˇ»
3044 three
3045 four
3046 "});
3047 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3048 cx.assert_editor_state(indoc! {"
3049 \t«oneˇ» «twoˇ»
3050 three
3051 four
3052 "});
3053 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3054 cx.assert_editor_state(indoc! {"
3055 «oneˇ» «twoˇ»
3056 three
3057 four
3058 "});
3059
3060 // select across a line ending
3061 cx.set_state(indoc! {"
3062 one two
3063 t«hree
3064 ˇ»four
3065 "});
3066 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3067 cx.assert_editor_state(indoc! {"
3068 one two
3069 \tt«hree
3070 ˇ»four
3071 "});
3072 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3073 cx.assert_editor_state(indoc! {"
3074 one two
3075 \t\tt«hree
3076 ˇ»four
3077 "});
3078 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3079 cx.assert_editor_state(indoc! {"
3080 one two
3081 \tt«hree
3082 ˇ»four
3083 "});
3084 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3085 cx.assert_editor_state(indoc! {"
3086 one two
3087 t«hree
3088 ˇ»four
3089 "});
3090
3091 // Ensure that indenting/outdenting works when the cursor is at column 0.
3092 cx.set_state(indoc! {"
3093 one two
3094 ˇthree
3095 four
3096 "});
3097 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3098 cx.assert_editor_state(indoc! {"
3099 one two
3100 ˇthree
3101 four
3102 "});
3103 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3104 cx.assert_editor_state(indoc! {"
3105 one two
3106 \tˇthree
3107 four
3108 "});
3109 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3110 cx.assert_editor_state(indoc! {"
3111 one two
3112 ˇthree
3113 four
3114 "});
3115}
3116
3117#[gpui::test]
3118fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3119 init_test(cx, |settings| {
3120 settings.languages.extend([
3121 (
3122 "TOML".into(),
3123 LanguageSettingsContent {
3124 tab_size: NonZeroU32::new(2),
3125 ..Default::default()
3126 },
3127 ),
3128 (
3129 "Rust".into(),
3130 LanguageSettingsContent {
3131 tab_size: NonZeroU32::new(4),
3132 ..Default::default()
3133 },
3134 ),
3135 ]);
3136 });
3137
3138 let toml_language = Arc::new(Language::new(
3139 LanguageConfig {
3140 name: "TOML".into(),
3141 ..Default::default()
3142 },
3143 None,
3144 ));
3145 let rust_language = Arc::new(Language::new(
3146 LanguageConfig {
3147 name: "Rust".into(),
3148 ..Default::default()
3149 },
3150 None,
3151 ));
3152
3153 let toml_buffer =
3154 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3155 let rust_buffer =
3156 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3157 let multibuffer = cx.new(|cx| {
3158 let mut multibuffer = MultiBuffer::new(ReadWrite);
3159 multibuffer.push_excerpts(
3160 toml_buffer.clone(),
3161 [ExcerptRange {
3162 context: Point::new(0, 0)..Point::new(2, 0),
3163 primary: None,
3164 }],
3165 cx,
3166 );
3167 multibuffer.push_excerpts(
3168 rust_buffer.clone(),
3169 [ExcerptRange {
3170 context: Point::new(0, 0)..Point::new(1, 0),
3171 primary: None,
3172 }],
3173 cx,
3174 );
3175 multibuffer
3176 });
3177
3178 cx.add_window(|window, cx| {
3179 let mut editor = build_editor(multibuffer, window, cx);
3180
3181 assert_eq!(
3182 editor.text(cx),
3183 indoc! {"
3184 a = 1
3185 b = 2
3186
3187 const c: usize = 3;
3188 "}
3189 );
3190
3191 select_ranges(
3192 &mut editor,
3193 indoc! {"
3194 «aˇ» = 1
3195 b = 2
3196
3197 «const c:ˇ» usize = 3;
3198 "},
3199 window,
3200 cx,
3201 );
3202
3203 editor.tab(&Tab, window, cx);
3204 assert_text_with_selections(
3205 &mut editor,
3206 indoc! {"
3207 «aˇ» = 1
3208 b = 2
3209
3210 «const c:ˇ» usize = 3;
3211 "},
3212 cx,
3213 );
3214 editor.backtab(&Backtab, window, cx);
3215 assert_text_with_selections(
3216 &mut editor,
3217 indoc! {"
3218 «aˇ» = 1
3219 b = 2
3220
3221 «const c:ˇ» usize = 3;
3222 "},
3223 cx,
3224 );
3225
3226 editor
3227 });
3228}
3229
3230#[gpui::test]
3231async fn test_backspace(cx: &mut TestAppContext) {
3232 init_test(cx, |_| {});
3233
3234 let mut cx = EditorTestContext::new(cx).await;
3235
3236 // Basic backspace
3237 cx.set_state(indoc! {"
3238 onˇe two three
3239 fou«rˇ» five six
3240 seven «ˇeight nine
3241 »ten
3242 "});
3243 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3244 cx.assert_editor_state(indoc! {"
3245 oˇe two three
3246 fouˇ five six
3247 seven ˇten
3248 "});
3249
3250 // Test backspace inside and around indents
3251 cx.set_state(indoc! {"
3252 zero
3253 ˇone
3254 ˇtwo
3255 ˇ ˇ ˇ three
3256 ˇ ˇ four
3257 "});
3258 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3259 cx.assert_editor_state(indoc! {"
3260 zero
3261 ˇone
3262 ˇtwo
3263 ˇ threeˇ four
3264 "});
3265
3266 // Test backspace with line_mode set to true
3267 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3268 cx.set_state(indoc! {"
3269 The ˇquick ˇbrown
3270 fox jumps over
3271 the lazy dog
3272 ˇThe qu«ick bˇ»rown"});
3273 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3274 cx.assert_editor_state(indoc! {"
3275 ˇfox jumps over
3276 the lazy dogˇ"});
3277}
3278
3279#[gpui::test]
3280async fn test_delete(cx: &mut TestAppContext) {
3281 init_test(cx, |_| {});
3282
3283 let mut cx = EditorTestContext::new(cx).await;
3284 cx.set_state(indoc! {"
3285 onˇe two three
3286 fou«rˇ» five six
3287 seven «ˇeight nine
3288 »ten
3289 "});
3290 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3291 cx.assert_editor_state(indoc! {"
3292 onˇ two three
3293 fouˇ five six
3294 seven ˇten
3295 "});
3296
3297 // Test backspace with line_mode set to true
3298 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3299 cx.set_state(indoc! {"
3300 The ˇquick ˇbrown
3301 fox «ˇjum»ps over
3302 the lazy dog
3303 ˇThe qu«ick bˇ»rown"});
3304 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3305 cx.assert_editor_state("ˇthe lazy dogˇ");
3306}
3307
3308#[gpui::test]
3309fn test_delete_line(cx: &mut TestAppContext) {
3310 init_test(cx, |_| {});
3311
3312 let editor = cx.add_window(|window, cx| {
3313 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3314 build_editor(buffer, window, cx)
3315 });
3316 _ = editor.update(cx, |editor, window, cx| {
3317 editor.change_selections(None, window, cx, |s| {
3318 s.select_display_ranges([
3319 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3320 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3321 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3322 ])
3323 });
3324 editor.delete_line(&DeleteLine, window, cx);
3325 assert_eq!(editor.display_text(cx), "ghi");
3326 assert_eq!(
3327 editor.selections.display_ranges(cx),
3328 vec![
3329 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3330 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3331 ]
3332 );
3333 });
3334
3335 let editor = cx.add_window(|window, cx| {
3336 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3337 build_editor(buffer, window, cx)
3338 });
3339 _ = editor.update(cx, |editor, window, cx| {
3340 editor.change_selections(None, window, cx, |s| {
3341 s.select_display_ranges([
3342 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3343 ])
3344 });
3345 editor.delete_line(&DeleteLine, window, cx);
3346 assert_eq!(editor.display_text(cx), "ghi\n");
3347 assert_eq!(
3348 editor.selections.display_ranges(cx),
3349 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3350 );
3351 });
3352}
3353
3354#[gpui::test]
3355fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3356 init_test(cx, |_| {});
3357
3358 cx.add_window(|window, cx| {
3359 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3360 let mut editor = build_editor(buffer.clone(), window, cx);
3361 let buffer = buffer.read(cx).as_singleton().unwrap();
3362
3363 assert_eq!(
3364 editor.selections.ranges::<Point>(cx),
3365 &[Point::new(0, 0)..Point::new(0, 0)]
3366 );
3367
3368 // When on single line, replace newline at end by space
3369 editor.join_lines(&JoinLines, window, cx);
3370 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3371 assert_eq!(
3372 editor.selections.ranges::<Point>(cx),
3373 &[Point::new(0, 3)..Point::new(0, 3)]
3374 );
3375
3376 // When multiple lines are selected, remove newlines that are spanned by the selection
3377 editor.change_selections(None, window, cx, |s| {
3378 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3379 });
3380 editor.join_lines(&JoinLines, window, cx);
3381 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3382 assert_eq!(
3383 editor.selections.ranges::<Point>(cx),
3384 &[Point::new(0, 11)..Point::new(0, 11)]
3385 );
3386
3387 // Undo should be transactional
3388 editor.undo(&Undo, window, cx);
3389 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3390 assert_eq!(
3391 editor.selections.ranges::<Point>(cx),
3392 &[Point::new(0, 5)..Point::new(2, 2)]
3393 );
3394
3395 // When joining an empty line don't insert a space
3396 editor.change_selections(None, window, cx, |s| {
3397 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3398 });
3399 editor.join_lines(&JoinLines, window, cx);
3400 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3401 assert_eq!(
3402 editor.selections.ranges::<Point>(cx),
3403 [Point::new(2, 3)..Point::new(2, 3)]
3404 );
3405
3406 // We can remove trailing newlines
3407 editor.join_lines(&JoinLines, window, cx);
3408 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3409 assert_eq!(
3410 editor.selections.ranges::<Point>(cx),
3411 [Point::new(2, 3)..Point::new(2, 3)]
3412 );
3413
3414 // We don't blow up on the last line
3415 editor.join_lines(&JoinLines, window, cx);
3416 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3417 assert_eq!(
3418 editor.selections.ranges::<Point>(cx),
3419 [Point::new(2, 3)..Point::new(2, 3)]
3420 );
3421
3422 // reset to test indentation
3423 editor.buffer.update(cx, |buffer, cx| {
3424 buffer.edit(
3425 [
3426 (Point::new(1, 0)..Point::new(1, 2), " "),
3427 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3428 ],
3429 None,
3430 cx,
3431 )
3432 });
3433
3434 // We remove any leading spaces
3435 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3436 editor.change_selections(None, window, cx, |s| {
3437 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3438 });
3439 editor.join_lines(&JoinLines, window, cx);
3440 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3441
3442 // We don't insert a space for a line containing only spaces
3443 editor.join_lines(&JoinLines, window, cx);
3444 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3445
3446 // We ignore any leading tabs
3447 editor.join_lines(&JoinLines, window, cx);
3448 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3449
3450 editor
3451 });
3452}
3453
3454#[gpui::test]
3455fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3456 init_test(cx, |_| {});
3457
3458 cx.add_window(|window, cx| {
3459 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3460 let mut editor = build_editor(buffer.clone(), window, cx);
3461 let buffer = buffer.read(cx).as_singleton().unwrap();
3462
3463 editor.change_selections(None, window, cx, |s| {
3464 s.select_ranges([
3465 Point::new(0, 2)..Point::new(1, 1),
3466 Point::new(1, 2)..Point::new(1, 2),
3467 Point::new(3, 1)..Point::new(3, 2),
3468 ])
3469 });
3470
3471 editor.join_lines(&JoinLines, window, cx);
3472 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3473
3474 assert_eq!(
3475 editor.selections.ranges::<Point>(cx),
3476 [
3477 Point::new(0, 7)..Point::new(0, 7),
3478 Point::new(1, 3)..Point::new(1, 3)
3479 ]
3480 );
3481 editor
3482 });
3483}
3484
3485#[gpui::test]
3486async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3487 init_test(cx, |_| {});
3488
3489 let mut cx = EditorTestContext::new(cx).await;
3490
3491 let diff_base = r#"
3492 Line 0
3493 Line 1
3494 Line 2
3495 Line 3
3496 "#
3497 .unindent();
3498
3499 cx.set_state(
3500 &r#"
3501 ˇLine 0
3502 Line 1
3503 Line 2
3504 Line 3
3505 "#
3506 .unindent(),
3507 );
3508
3509 cx.set_head_text(&diff_base);
3510 executor.run_until_parked();
3511
3512 // Join lines
3513 cx.update_editor(|editor, window, cx| {
3514 editor.join_lines(&JoinLines, window, cx);
3515 });
3516 executor.run_until_parked();
3517
3518 cx.assert_editor_state(
3519 &r#"
3520 Line 0ˇ Line 1
3521 Line 2
3522 Line 3
3523 "#
3524 .unindent(),
3525 );
3526 // Join again
3527 cx.update_editor(|editor, window, cx| {
3528 editor.join_lines(&JoinLines, window, cx);
3529 });
3530 executor.run_until_parked();
3531
3532 cx.assert_editor_state(
3533 &r#"
3534 Line 0 Line 1ˇ Line 2
3535 Line 3
3536 "#
3537 .unindent(),
3538 );
3539}
3540
3541#[gpui::test]
3542async fn test_custom_newlines_cause_no_false_positive_diffs(
3543 executor: BackgroundExecutor,
3544 cx: &mut TestAppContext,
3545) {
3546 init_test(cx, |_| {});
3547 let mut cx = EditorTestContext::new(cx).await;
3548 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3549 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3550 executor.run_until_parked();
3551
3552 cx.update_editor(|editor, window, cx| {
3553 let snapshot = editor.snapshot(window, cx);
3554 assert_eq!(
3555 snapshot
3556 .buffer_snapshot
3557 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3558 .collect::<Vec<_>>(),
3559 Vec::new(),
3560 "Should not have any diffs for files with custom newlines"
3561 );
3562 });
3563}
3564
3565#[gpui::test]
3566async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 // Test sort_lines_case_insensitive()
3572 cx.set_state(indoc! {"
3573 «z
3574 y
3575 x
3576 Z
3577 Y
3578 Xˇ»
3579 "});
3580 cx.update_editor(|e, window, cx| {
3581 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3582 });
3583 cx.assert_editor_state(indoc! {"
3584 «x
3585 X
3586 y
3587 Y
3588 z
3589 Zˇ»
3590 "});
3591
3592 // Test reverse_lines()
3593 cx.set_state(indoc! {"
3594 «5
3595 4
3596 3
3597 2
3598 1ˇ»
3599 "});
3600 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3601 cx.assert_editor_state(indoc! {"
3602 «1
3603 2
3604 3
3605 4
3606 5ˇ»
3607 "});
3608
3609 // Skip testing shuffle_line()
3610
3611 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3612 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3613
3614 // Don't manipulate when cursor is on single line, but expand the selection
3615 cx.set_state(indoc! {"
3616 ddˇdd
3617 ccc
3618 bb
3619 a
3620 "});
3621 cx.update_editor(|e, window, cx| {
3622 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3623 });
3624 cx.assert_editor_state(indoc! {"
3625 «ddddˇ»
3626 ccc
3627 bb
3628 a
3629 "});
3630
3631 // Basic manipulate case
3632 // Start selection moves to column 0
3633 // End of selection shrinks to fit shorter line
3634 cx.set_state(indoc! {"
3635 dd«d
3636 ccc
3637 bb
3638 aaaaaˇ»
3639 "});
3640 cx.update_editor(|e, window, cx| {
3641 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3642 });
3643 cx.assert_editor_state(indoc! {"
3644 «aaaaa
3645 bb
3646 ccc
3647 dddˇ»
3648 "});
3649
3650 // Manipulate case with newlines
3651 cx.set_state(indoc! {"
3652 dd«d
3653 ccc
3654
3655 bb
3656 aaaaa
3657
3658 ˇ»
3659 "});
3660 cx.update_editor(|e, window, cx| {
3661 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3662 });
3663 cx.assert_editor_state(indoc! {"
3664 «
3665
3666 aaaaa
3667 bb
3668 ccc
3669 dddˇ»
3670
3671 "});
3672
3673 // Adding new line
3674 cx.set_state(indoc! {"
3675 aa«a
3676 bbˇ»b
3677 "});
3678 cx.update_editor(|e, window, cx| {
3679 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3680 });
3681 cx.assert_editor_state(indoc! {"
3682 «aaa
3683 bbb
3684 added_lineˇ»
3685 "});
3686
3687 // Removing line
3688 cx.set_state(indoc! {"
3689 aa«a
3690 bbbˇ»
3691 "});
3692 cx.update_editor(|e, window, cx| {
3693 e.manipulate_lines(window, cx, |lines| {
3694 lines.pop();
3695 })
3696 });
3697 cx.assert_editor_state(indoc! {"
3698 «aaaˇ»
3699 "});
3700
3701 // Removing all lines
3702 cx.set_state(indoc! {"
3703 aa«a
3704 bbbˇ»
3705 "});
3706 cx.update_editor(|e, window, cx| {
3707 e.manipulate_lines(window, cx, |lines| {
3708 lines.drain(..);
3709 })
3710 });
3711 cx.assert_editor_state(indoc! {"
3712 ˇ
3713 "});
3714}
3715
3716#[gpui::test]
3717async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3718 init_test(cx, |_| {});
3719
3720 let mut cx = EditorTestContext::new(cx).await;
3721
3722 // Consider continuous selection as single selection
3723 cx.set_state(indoc! {"
3724 Aaa«aa
3725 cˇ»c«c
3726 bb
3727 aaaˇ»aa
3728 "});
3729 cx.update_editor(|e, window, cx| {
3730 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3731 });
3732 cx.assert_editor_state(indoc! {"
3733 «Aaaaa
3734 ccc
3735 bb
3736 aaaaaˇ»
3737 "});
3738
3739 cx.set_state(indoc! {"
3740 Aaa«aa
3741 cˇ»c«c
3742 bb
3743 aaaˇ»aa
3744 "});
3745 cx.update_editor(|e, window, cx| {
3746 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3747 });
3748 cx.assert_editor_state(indoc! {"
3749 «Aaaaa
3750 ccc
3751 bbˇ»
3752 "});
3753
3754 // Consider non continuous selection as distinct dedup operations
3755 cx.set_state(indoc! {"
3756 «aaaaa
3757 bb
3758 aaaaa
3759 aaaaaˇ»
3760
3761 aaa«aaˇ»
3762 "});
3763 cx.update_editor(|e, window, cx| {
3764 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3765 });
3766 cx.assert_editor_state(indoc! {"
3767 «aaaaa
3768 bbˇ»
3769
3770 «aaaaaˇ»
3771 "});
3772}
3773
3774#[gpui::test]
3775async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3776 init_test(cx, |_| {});
3777
3778 let mut cx = EditorTestContext::new(cx).await;
3779
3780 cx.set_state(indoc! {"
3781 «Aaa
3782 aAa
3783 Aaaˇ»
3784 "});
3785 cx.update_editor(|e, window, cx| {
3786 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3787 });
3788 cx.assert_editor_state(indoc! {"
3789 «Aaa
3790 aAaˇ»
3791 "});
3792
3793 cx.set_state(indoc! {"
3794 «Aaa
3795 aAa
3796 aaAˇ»
3797 "});
3798 cx.update_editor(|e, window, cx| {
3799 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3800 });
3801 cx.assert_editor_state(indoc! {"
3802 «Aaaˇ»
3803 "});
3804}
3805
3806#[gpui::test]
3807async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3808 init_test(cx, |_| {});
3809
3810 let mut cx = EditorTestContext::new(cx).await;
3811
3812 // Manipulate with multiple selections on a single line
3813 cx.set_state(indoc! {"
3814 dd«dd
3815 cˇ»c«c
3816 bb
3817 aaaˇ»aa
3818 "});
3819 cx.update_editor(|e, window, cx| {
3820 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3821 });
3822 cx.assert_editor_state(indoc! {"
3823 «aaaaa
3824 bb
3825 ccc
3826 ddddˇ»
3827 "});
3828
3829 // Manipulate with multiple disjoin selections
3830 cx.set_state(indoc! {"
3831 5«
3832 4
3833 3
3834 2
3835 1ˇ»
3836
3837 dd«dd
3838 ccc
3839 bb
3840 aaaˇ»aa
3841 "});
3842 cx.update_editor(|e, window, cx| {
3843 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3844 });
3845 cx.assert_editor_state(indoc! {"
3846 «1
3847 2
3848 3
3849 4
3850 5ˇ»
3851
3852 «aaaaa
3853 bb
3854 ccc
3855 ddddˇ»
3856 "});
3857
3858 // Adding lines on each selection
3859 cx.set_state(indoc! {"
3860 2«
3861 1ˇ»
3862
3863 bb«bb
3864 aaaˇ»aa
3865 "});
3866 cx.update_editor(|e, window, cx| {
3867 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3868 });
3869 cx.assert_editor_state(indoc! {"
3870 «2
3871 1
3872 added lineˇ»
3873
3874 «bbbb
3875 aaaaa
3876 added lineˇ»
3877 "});
3878
3879 // Removing lines on each selection
3880 cx.set_state(indoc! {"
3881 2«
3882 1ˇ»
3883
3884 bb«bb
3885 aaaˇ»aa
3886 "});
3887 cx.update_editor(|e, window, cx| {
3888 e.manipulate_lines(window, cx, |lines| {
3889 lines.pop();
3890 })
3891 });
3892 cx.assert_editor_state(indoc! {"
3893 «2ˇ»
3894
3895 «bbbbˇ»
3896 "});
3897}
3898
3899#[gpui::test]
3900async fn test_manipulate_text(cx: &mut TestAppContext) {
3901 init_test(cx, |_| {});
3902
3903 let mut cx = EditorTestContext::new(cx).await;
3904
3905 // Test convert_to_upper_case()
3906 cx.set_state(indoc! {"
3907 «hello worldˇ»
3908 "});
3909 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3910 cx.assert_editor_state(indoc! {"
3911 «HELLO WORLDˇ»
3912 "});
3913
3914 // Test convert_to_lower_case()
3915 cx.set_state(indoc! {"
3916 «HELLO WORLDˇ»
3917 "});
3918 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3919 cx.assert_editor_state(indoc! {"
3920 «hello worldˇ»
3921 "});
3922
3923 // Test multiple line, single selection case
3924 cx.set_state(indoc! {"
3925 «The quick brown
3926 fox jumps over
3927 the lazy dogˇ»
3928 "});
3929 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3930 cx.assert_editor_state(indoc! {"
3931 «The Quick Brown
3932 Fox Jumps Over
3933 The Lazy Dogˇ»
3934 "});
3935
3936 // Test multiple line, single selection case
3937 cx.set_state(indoc! {"
3938 «The quick brown
3939 fox jumps over
3940 the lazy dogˇ»
3941 "});
3942 cx.update_editor(|e, window, cx| {
3943 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3944 });
3945 cx.assert_editor_state(indoc! {"
3946 «TheQuickBrown
3947 FoxJumpsOver
3948 TheLazyDogˇ»
3949 "});
3950
3951 // From here on out, test more complex cases of manipulate_text()
3952
3953 // Test no selection case - should affect words cursors are in
3954 // Cursor at beginning, middle, and end of word
3955 cx.set_state(indoc! {"
3956 ˇhello big beauˇtiful worldˇ
3957 "});
3958 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3959 cx.assert_editor_state(indoc! {"
3960 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3961 "});
3962
3963 // Test multiple selections on a single line and across multiple lines
3964 cx.set_state(indoc! {"
3965 «Theˇ» quick «brown
3966 foxˇ» jumps «overˇ»
3967 the «lazyˇ» dog
3968 "});
3969 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3970 cx.assert_editor_state(indoc! {"
3971 «THEˇ» quick «BROWN
3972 FOXˇ» jumps «OVERˇ»
3973 the «LAZYˇ» dog
3974 "});
3975
3976 // Test case where text length grows
3977 cx.set_state(indoc! {"
3978 «tschüߡ»
3979 "});
3980 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3981 cx.assert_editor_state(indoc! {"
3982 «TSCHÜSSˇ»
3983 "});
3984
3985 // Test to make sure we don't crash when text shrinks
3986 cx.set_state(indoc! {"
3987 aaa_bbbˇ
3988 "});
3989 cx.update_editor(|e, window, cx| {
3990 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3991 });
3992 cx.assert_editor_state(indoc! {"
3993 «aaaBbbˇ»
3994 "});
3995
3996 // Test to make sure we all aware of the fact that each word can grow and shrink
3997 // Final selections should be aware of this fact
3998 cx.set_state(indoc! {"
3999 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4000 "});
4001 cx.update_editor(|e, window, cx| {
4002 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4003 });
4004 cx.assert_editor_state(indoc! {"
4005 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4006 "});
4007
4008 cx.set_state(indoc! {"
4009 «hElLo, WoRld!ˇ»
4010 "});
4011 cx.update_editor(|e, window, cx| {
4012 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4013 });
4014 cx.assert_editor_state(indoc! {"
4015 «HeLlO, wOrLD!ˇ»
4016 "});
4017}
4018
4019#[gpui::test]
4020fn test_duplicate_line(cx: &mut TestAppContext) {
4021 init_test(cx, |_| {});
4022
4023 let editor = cx.add_window(|window, cx| {
4024 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4025 build_editor(buffer, window, cx)
4026 });
4027 _ = editor.update(cx, |editor, window, cx| {
4028 editor.change_selections(None, window, cx, |s| {
4029 s.select_display_ranges([
4030 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4031 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4032 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4033 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4034 ])
4035 });
4036 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4037 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4038 assert_eq!(
4039 editor.selections.display_ranges(cx),
4040 vec![
4041 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4042 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4043 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4044 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4045 ]
4046 );
4047 });
4048
4049 let editor = cx.add_window(|window, cx| {
4050 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4051 build_editor(buffer, window, cx)
4052 });
4053 _ = editor.update(cx, |editor, window, cx| {
4054 editor.change_selections(None, window, cx, |s| {
4055 s.select_display_ranges([
4056 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4057 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4058 ])
4059 });
4060 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4061 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4062 assert_eq!(
4063 editor.selections.display_ranges(cx),
4064 vec![
4065 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4066 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4067 ]
4068 );
4069 });
4070
4071 // With `move_upwards` the selections stay in place, except for
4072 // the lines inserted above them
4073 let editor = cx.add_window(|window, cx| {
4074 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4075 build_editor(buffer, window, cx)
4076 });
4077 _ = editor.update(cx, |editor, window, cx| {
4078 editor.change_selections(None, window, cx, |s| {
4079 s.select_display_ranges([
4080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4081 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4082 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4083 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4084 ])
4085 });
4086 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4087 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4088 assert_eq!(
4089 editor.selections.display_ranges(cx),
4090 vec![
4091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4092 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4093 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4094 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4095 ]
4096 );
4097 });
4098
4099 let editor = cx.add_window(|window, cx| {
4100 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4101 build_editor(buffer, window, cx)
4102 });
4103 _ = editor.update(cx, |editor, window, cx| {
4104 editor.change_selections(None, window, cx, |s| {
4105 s.select_display_ranges([
4106 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4107 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4108 ])
4109 });
4110 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4111 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4112 assert_eq!(
4113 editor.selections.display_ranges(cx),
4114 vec![
4115 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4116 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4117 ]
4118 );
4119 });
4120
4121 let editor = cx.add_window(|window, cx| {
4122 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4123 build_editor(buffer, window, cx)
4124 });
4125 _ = editor.update(cx, |editor, window, cx| {
4126 editor.change_selections(None, window, cx, |s| {
4127 s.select_display_ranges([
4128 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4129 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4130 ])
4131 });
4132 editor.duplicate_selection(&DuplicateSelection, window, cx);
4133 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4134 assert_eq!(
4135 editor.selections.display_ranges(cx),
4136 vec![
4137 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4138 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4139 ]
4140 );
4141 });
4142}
4143
4144#[gpui::test]
4145fn test_move_line_up_down(cx: &mut TestAppContext) {
4146 init_test(cx, |_| {});
4147
4148 let editor = cx.add_window(|window, cx| {
4149 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4150 build_editor(buffer, window, cx)
4151 });
4152 _ = editor.update(cx, |editor, window, cx| {
4153 editor.fold_creases(
4154 vec![
4155 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4156 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4158 ],
4159 true,
4160 window,
4161 cx,
4162 );
4163 editor.change_selections(None, window, cx, |s| {
4164 s.select_display_ranges([
4165 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4166 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4167 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4168 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4169 ])
4170 });
4171 assert_eq!(
4172 editor.display_text(cx),
4173 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4174 );
4175
4176 editor.move_line_up(&MoveLineUp, window, cx);
4177 assert_eq!(
4178 editor.display_text(cx),
4179 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4180 );
4181 assert_eq!(
4182 editor.selections.display_ranges(cx),
4183 vec![
4184 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4185 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4186 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4187 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4188 ]
4189 );
4190 });
4191
4192 _ = editor.update(cx, |editor, window, cx| {
4193 editor.move_line_down(&MoveLineDown, window, cx);
4194 assert_eq!(
4195 editor.display_text(cx),
4196 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4197 );
4198 assert_eq!(
4199 editor.selections.display_ranges(cx),
4200 vec![
4201 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4202 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4203 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4204 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4205 ]
4206 );
4207 });
4208
4209 _ = editor.update(cx, |editor, window, cx| {
4210 editor.move_line_down(&MoveLineDown, window, cx);
4211 assert_eq!(
4212 editor.display_text(cx),
4213 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4214 );
4215 assert_eq!(
4216 editor.selections.display_ranges(cx),
4217 vec![
4218 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4219 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4220 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4221 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4222 ]
4223 );
4224 });
4225
4226 _ = editor.update(cx, |editor, window, cx| {
4227 editor.move_line_up(&MoveLineUp, window, cx);
4228 assert_eq!(
4229 editor.display_text(cx),
4230 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4231 );
4232 assert_eq!(
4233 editor.selections.display_ranges(cx),
4234 vec![
4235 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4236 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4237 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4238 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4239 ]
4240 );
4241 });
4242}
4243
4244#[gpui::test]
4245fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4246 init_test(cx, |_| {});
4247
4248 let editor = cx.add_window(|window, cx| {
4249 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4250 build_editor(buffer, window, cx)
4251 });
4252 _ = editor.update(cx, |editor, window, cx| {
4253 let snapshot = editor.buffer.read(cx).snapshot(cx);
4254 editor.insert_blocks(
4255 [BlockProperties {
4256 style: BlockStyle::Fixed,
4257 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4258 height: 1,
4259 render: Arc::new(|_| div().into_any()),
4260 priority: 0,
4261 }],
4262 Some(Autoscroll::fit()),
4263 cx,
4264 );
4265 editor.change_selections(None, window, cx, |s| {
4266 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4267 });
4268 editor.move_line_down(&MoveLineDown, window, cx);
4269 });
4270}
4271
4272#[gpui::test]
4273async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4274 init_test(cx, |_| {});
4275
4276 let mut cx = EditorTestContext::new(cx).await;
4277 cx.set_state(
4278 &"
4279 ˇzero
4280 one
4281 two
4282 three
4283 four
4284 five
4285 "
4286 .unindent(),
4287 );
4288
4289 // Create a four-line block that replaces three lines of text.
4290 cx.update_editor(|editor, window, cx| {
4291 let snapshot = editor.snapshot(window, cx);
4292 let snapshot = &snapshot.buffer_snapshot;
4293 let placement = BlockPlacement::Replace(
4294 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4295 );
4296 editor.insert_blocks(
4297 [BlockProperties {
4298 placement,
4299 height: 4,
4300 style: BlockStyle::Sticky,
4301 render: Arc::new(|_| gpui::div().into_any_element()),
4302 priority: 0,
4303 }],
4304 None,
4305 cx,
4306 );
4307 });
4308
4309 // Move down so that the cursor touches the block.
4310 cx.update_editor(|editor, window, cx| {
4311 editor.move_down(&Default::default(), window, cx);
4312 });
4313 cx.assert_editor_state(
4314 &"
4315 zero
4316 «one
4317 two
4318 threeˇ»
4319 four
4320 five
4321 "
4322 .unindent(),
4323 );
4324
4325 // Move down past the block.
4326 cx.update_editor(|editor, window, cx| {
4327 editor.move_down(&Default::default(), window, cx);
4328 });
4329 cx.assert_editor_state(
4330 &"
4331 zero
4332 one
4333 two
4334 three
4335 ˇfour
4336 five
4337 "
4338 .unindent(),
4339 );
4340}
4341
4342#[gpui::test]
4343fn test_transpose(cx: &mut TestAppContext) {
4344 init_test(cx, |_| {});
4345
4346 _ = cx.add_window(|window, cx| {
4347 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4348 editor.set_style(EditorStyle::default(), window, cx);
4349 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4350 editor.transpose(&Default::default(), window, cx);
4351 assert_eq!(editor.text(cx), "bac");
4352 assert_eq!(editor.selections.ranges(cx), [2..2]);
4353
4354 editor.transpose(&Default::default(), window, cx);
4355 assert_eq!(editor.text(cx), "bca");
4356 assert_eq!(editor.selections.ranges(cx), [3..3]);
4357
4358 editor.transpose(&Default::default(), window, cx);
4359 assert_eq!(editor.text(cx), "bac");
4360 assert_eq!(editor.selections.ranges(cx), [3..3]);
4361
4362 editor
4363 });
4364
4365 _ = cx.add_window(|window, cx| {
4366 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4367 editor.set_style(EditorStyle::default(), window, cx);
4368 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4369 editor.transpose(&Default::default(), window, cx);
4370 assert_eq!(editor.text(cx), "acb\nde");
4371 assert_eq!(editor.selections.ranges(cx), [3..3]);
4372
4373 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4374 editor.transpose(&Default::default(), window, cx);
4375 assert_eq!(editor.text(cx), "acbd\ne");
4376 assert_eq!(editor.selections.ranges(cx), [5..5]);
4377
4378 editor.transpose(&Default::default(), window, cx);
4379 assert_eq!(editor.text(cx), "acbde\n");
4380 assert_eq!(editor.selections.ranges(cx), [6..6]);
4381
4382 editor.transpose(&Default::default(), window, cx);
4383 assert_eq!(editor.text(cx), "acbd\ne");
4384 assert_eq!(editor.selections.ranges(cx), [6..6]);
4385
4386 editor
4387 });
4388
4389 _ = cx.add_window(|window, cx| {
4390 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4391 editor.set_style(EditorStyle::default(), window, cx);
4392 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4393 editor.transpose(&Default::default(), window, cx);
4394 assert_eq!(editor.text(cx), "bacd\ne");
4395 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4396
4397 editor.transpose(&Default::default(), window, cx);
4398 assert_eq!(editor.text(cx), "bcade\n");
4399 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4400
4401 editor.transpose(&Default::default(), window, cx);
4402 assert_eq!(editor.text(cx), "bcda\ne");
4403 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4404
4405 editor.transpose(&Default::default(), window, cx);
4406 assert_eq!(editor.text(cx), "bcade\n");
4407 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4408
4409 editor.transpose(&Default::default(), window, cx);
4410 assert_eq!(editor.text(cx), "bcaed\n");
4411 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4412
4413 editor
4414 });
4415
4416 _ = cx.add_window(|window, cx| {
4417 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4418 editor.set_style(EditorStyle::default(), window, cx);
4419 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4420 editor.transpose(&Default::default(), window, cx);
4421 assert_eq!(editor.text(cx), "🏀🍐✋");
4422 assert_eq!(editor.selections.ranges(cx), [8..8]);
4423
4424 editor.transpose(&Default::default(), window, cx);
4425 assert_eq!(editor.text(cx), "🏀✋🍐");
4426 assert_eq!(editor.selections.ranges(cx), [11..11]);
4427
4428 editor.transpose(&Default::default(), window, cx);
4429 assert_eq!(editor.text(cx), "🏀🍐✋");
4430 assert_eq!(editor.selections.ranges(cx), [11..11]);
4431
4432 editor
4433 });
4434}
4435
4436#[gpui::test]
4437async fn test_rewrap(cx: &mut TestAppContext) {
4438 init_test(cx, |settings| {
4439 settings.languages.extend([
4440 (
4441 "Markdown".into(),
4442 LanguageSettingsContent {
4443 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4444 ..Default::default()
4445 },
4446 ),
4447 (
4448 "Plain Text".into(),
4449 LanguageSettingsContent {
4450 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4451 ..Default::default()
4452 },
4453 ),
4454 ])
4455 });
4456
4457 let mut cx = EditorTestContext::new(cx).await;
4458
4459 let language_with_c_comments = Arc::new(Language::new(
4460 LanguageConfig {
4461 line_comments: vec!["// ".into()],
4462 ..LanguageConfig::default()
4463 },
4464 None,
4465 ));
4466 let language_with_pound_comments = Arc::new(Language::new(
4467 LanguageConfig {
4468 line_comments: vec!["# ".into()],
4469 ..LanguageConfig::default()
4470 },
4471 None,
4472 ));
4473 let markdown_language = Arc::new(Language::new(
4474 LanguageConfig {
4475 name: "Markdown".into(),
4476 ..LanguageConfig::default()
4477 },
4478 None,
4479 ));
4480 let language_with_doc_comments = Arc::new(Language::new(
4481 LanguageConfig {
4482 line_comments: vec!["// ".into(), "/// ".into()],
4483 ..LanguageConfig::default()
4484 },
4485 Some(tree_sitter_rust::LANGUAGE.into()),
4486 ));
4487
4488 let plaintext_language = Arc::new(Language::new(
4489 LanguageConfig {
4490 name: "Plain Text".into(),
4491 ..LanguageConfig::default()
4492 },
4493 None,
4494 ));
4495
4496 assert_rewrap(
4497 indoc! {"
4498 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4499 "},
4500 indoc! {"
4501 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4502 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4503 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4504 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4505 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4506 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4507 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4508 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4509 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4510 // porttitor id. Aliquam id accumsan eros.
4511 "},
4512 language_with_c_comments.clone(),
4513 &mut cx,
4514 );
4515
4516 // Test that rewrapping works inside of a selection
4517 assert_rewrap(
4518 indoc! {"
4519 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4520 "},
4521 indoc! {"
4522 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4523 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4524 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4525 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4526 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4527 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4528 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4529 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4530 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4531 // porttitor id. Aliquam id accumsan eros.ˇ»
4532 "},
4533 language_with_c_comments.clone(),
4534 &mut cx,
4535 );
4536
4537 // Test that cursors that expand to the same region are collapsed.
4538 assert_rewrap(
4539 indoc! {"
4540 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4541 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4542 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4543 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4544 "},
4545 indoc! {"
4546 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4547 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4548 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4549 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4550 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4551 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4552 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4553 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4554 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4555 // porttitor id. Aliquam id accumsan eros.
4556 "},
4557 language_with_c_comments.clone(),
4558 &mut cx,
4559 );
4560
4561 // Test that non-contiguous selections are treated separately.
4562 assert_rewrap(
4563 indoc! {"
4564 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4565 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4566 //
4567 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4568 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4569 "},
4570 indoc! {"
4571 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4572 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4573 // auctor, eu lacinia sapien scelerisque.
4574 //
4575 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4576 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4577 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4578 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4579 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4580 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4581 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4582 "},
4583 language_with_c_comments.clone(),
4584 &mut cx,
4585 );
4586
4587 // Test that different comment prefixes are supported.
4588 assert_rewrap(
4589 indoc! {"
4590 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4591 "},
4592 indoc! {"
4593 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4594 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4595 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4596 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4597 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4598 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4599 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4600 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4601 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4602 # accumsan eros.
4603 "},
4604 language_with_pound_comments.clone(),
4605 &mut cx,
4606 );
4607
4608 // Test that rewrapping is ignored outside of comments in most languages.
4609 assert_rewrap(
4610 indoc! {"
4611 /// Adds two numbers.
4612 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4613 fn add(a: u32, b: u32) -> u32 {
4614 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4615 }
4616 "},
4617 indoc! {"
4618 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4619 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4620 fn add(a: u32, b: u32) -> u32 {
4621 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4622 }
4623 "},
4624 language_with_doc_comments.clone(),
4625 &mut cx,
4626 );
4627
4628 // Test that rewrapping works in Markdown and Plain Text languages.
4629 assert_rewrap(
4630 indoc! {"
4631 # Hello
4632
4633 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4634 "},
4635 indoc! {"
4636 # Hello
4637
4638 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4639 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4640 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4641 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4642 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4643 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4644 Integer sit amet scelerisque nisi.
4645 "},
4646 markdown_language,
4647 &mut cx,
4648 );
4649
4650 assert_rewrap(
4651 indoc! {"
4652 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4653 "},
4654 indoc! {"
4655 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4656 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4657 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4658 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4659 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4660 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4661 Integer sit amet scelerisque nisi.
4662 "},
4663 plaintext_language,
4664 &mut cx,
4665 );
4666
4667 // Test rewrapping unaligned comments in a selection.
4668 assert_rewrap(
4669 indoc! {"
4670 fn foo() {
4671 if true {
4672 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4673 // Praesent semper egestas tellus id dignissim.ˇ»
4674 do_something();
4675 } else {
4676 //
4677 }
4678 }
4679 "},
4680 indoc! {"
4681 fn foo() {
4682 if true {
4683 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4684 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4685 // egestas tellus id dignissim.ˇ»
4686 do_something();
4687 } else {
4688 //
4689 }
4690 }
4691 "},
4692 language_with_doc_comments.clone(),
4693 &mut cx,
4694 );
4695
4696 assert_rewrap(
4697 indoc! {"
4698 fn foo() {
4699 if true {
4700 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4701 // Praesent semper egestas tellus id dignissim.»
4702 do_something();
4703 } else {
4704 //
4705 }
4706
4707 }
4708 "},
4709 indoc! {"
4710 fn foo() {
4711 if true {
4712 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4713 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4714 // egestas tellus id dignissim.»
4715 do_something();
4716 } else {
4717 //
4718 }
4719
4720 }
4721 "},
4722 language_with_doc_comments.clone(),
4723 &mut cx,
4724 );
4725
4726 #[track_caller]
4727 fn assert_rewrap(
4728 unwrapped_text: &str,
4729 wrapped_text: &str,
4730 language: Arc<Language>,
4731 cx: &mut EditorTestContext,
4732 ) {
4733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4734 cx.set_state(unwrapped_text);
4735 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4736 cx.assert_editor_state(wrapped_text);
4737 }
4738}
4739
4740#[gpui::test]
4741async fn test_clipboard(cx: &mut TestAppContext) {
4742 init_test(cx, |_| {});
4743
4744 let mut cx = EditorTestContext::new(cx).await;
4745
4746 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4747 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4748 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4749
4750 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4751 cx.set_state("two ˇfour ˇsix ˇ");
4752 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4753 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4754
4755 // Paste again but with only two cursors. Since the number of cursors doesn't
4756 // match the number of slices in the clipboard, the entire clipboard text
4757 // is pasted at each cursor.
4758 cx.set_state("ˇtwo one✅ four three six five ˇ");
4759 cx.update_editor(|e, window, cx| {
4760 e.handle_input("( ", window, cx);
4761 e.paste(&Paste, window, cx);
4762 e.handle_input(") ", window, cx);
4763 });
4764 cx.assert_editor_state(
4765 &([
4766 "( one✅ ",
4767 "three ",
4768 "five ) ˇtwo one✅ four three six five ( one✅ ",
4769 "three ",
4770 "five ) ˇ",
4771 ]
4772 .join("\n")),
4773 );
4774
4775 // Cut with three selections, one of which is full-line.
4776 cx.set_state(indoc! {"
4777 1«2ˇ»3
4778 4ˇ567
4779 «8ˇ»9"});
4780 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4781 cx.assert_editor_state(indoc! {"
4782 1ˇ3
4783 ˇ9"});
4784
4785 // Paste with three selections, noticing how the copied selection that was full-line
4786 // gets inserted before the second cursor.
4787 cx.set_state(indoc! {"
4788 1ˇ3
4789 9ˇ
4790 «oˇ»ne"});
4791 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4792 cx.assert_editor_state(indoc! {"
4793 12ˇ3
4794 4567
4795 9ˇ
4796 8ˇne"});
4797
4798 // Copy with a single cursor only, which writes the whole line into the clipboard.
4799 cx.set_state(indoc! {"
4800 The quick brown
4801 fox juˇmps over
4802 the lazy dog"});
4803 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4804 assert_eq!(
4805 cx.read_from_clipboard()
4806 .and_then(|item| item.text().as_deref().map(str::to_string)),
4807 Some("fox jumps over\n".to_string())
4808 );
4809
4810 // Paste with three selections, noticing how the copied full-line selection is inserted
4811 // before the empty selections but replaces the selection that is non-empty.
4812 cx.set_state(indoc! {"
4813 Tˇhe quick brown
4814 «foˇ»x jumps over
4815 tˇhe lazy dog"});
4816 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4817 cx.assert_editor_state(indoc! {"
4818 fox jumps over
4819 Tˇhe quick brown
4820 fox jumps over
4821 ˇx jumps over
4822 fox jumps over
4823 tˇhe lazy dog"});
4824}
4825
4826#[gpui::test]
4827async fn test_paste_multiline(cx: &mut TestAppContext) {
4828 init_test(cx, |_| {});
4829
4830 let mut cx = EditorTestContext::new(cx).await;
4831 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4832
4833 // Cut an indented block, without the leading whitespace.
4834 cx.set_state(indoc! {"
4835 const a: B = (
4836 c(),
4837 «d(
4838 e,
4839 f
4840 )ˇ»
4841 );
4842 "});
4843 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4844 cx.assert_editor_state(indoc! {"
4845 const a: B = (
4846 c(),
4847 ˇ
4848 );
4849 "});
4850
4851 // Paste it at the same position.
4852 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4853 cx.assert_editor_state(indoc! {"
4854 const a: B = (
4855 c(),
4856 d(
4857 e,
4858 f
4859 )ˇ
4860 );
4861 "});
4862
4863 // Paste it at a line with a lower indent level.
4864 cx.set_state(indoc! {"
4865 ˇ
4866 const a: B = (
4867 c(),
4868 );
4869 "});
4870 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4871 cx.assert_editor_state(indoc! {"
4872 d(
4873 e,
4874 f
4875 )ˇ
4876 const a: B = (
4877 c(),
4878 );
4879 "});
4880
4881 // Cut an indented block, with the leading whitespace.
4882 cx.set_state(indoc! {"
4883 const a: B = (
4884 c(),
4885 « d(
4886 e,
4887 f
4888 )
4889 ˇ»);
4890 "});
4891 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4892 cx.assert_editor_state(indoc! {"
4893 const a: B = (
4894 c(),
4895 ˇ);
4896 "});
4897
4898 // Paste it at the same position.
4899 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4900 cx.assert_editor_state(indoc! {"
4901 const a: B = (
4902 c(),
4903 d(
4904 e,
4905 f
4906 )
4907 ˇ);
4908 "});
4909
4910 // Paste it at a line with a higher indent level.
4911 cx.set_state(indoc! {"
4912 const a: B = (
4913 c(),
4914 d(
4915 e,
4916 fˇ
4917 )
4918 );
4919 "});
4920 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4921 cx.assert_editor_state(indoc! {"
4922 const a: B = (
4923 c(),
4924 d(
4925 e,
4926 f d(
4927 e,
4928 f
4929 )
4930 ˇ
4931 )
4932 );
4933 "});
4934
4935 // Copy an indented block, starting mid-line
4936 cx.set_state(indoc! {"
4937 const a: B = (
4938 c(),
4939 somethin«g(
4940 e,
4941 f
4942 )ˇ»
4943 );
4944 "});
4945 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4946
4947 // Paste it on a line with a lower indent level
4948 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4949 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4950 cx.assert_editor_state(indoc! {"
4951 const a: B = (
4952 c(),
4953 something(
4954 e,
4955 f
4956 )
4957 );
4958 g(
4959 e,
4960 f
4961 )ˇ"});
4962}
4963
4964#[gpui::test]
4965async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4966 init_test(cx, |_| {});
4967
4968 cx.write_to_clipboard(ClipboardItem::new_string(
4969 " d(\n e\n );\n".into(),
4970 ));
4971
4972 let mut cx = EditorTestContext::new(cx).await;
4973 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4974
4975 cx.set_state(indoc! {"
4976 fn a() {
4977 b();
4978 if c() {
4979 ˇ
4980 }
4981 }
4982 "});
4983
4984 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4985 cx.assert_editor_state(indoc! {"
4986 fn a() {
4987 b();
4988 if c() {
4989 d(
4990 e
4991 );
4992 ˇ
4993 }
4994 }
4995 "});
4996
4997 cx.set_state(indoc! {"
4998 fn a() {
4999 b();
5000 ˇ
5001 }
5002 "});
5003
5004 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5005 cx.assert_editor_state(indoc! {"
5006 fn a() {
5007 b();
5008 d(
5009 e
5010 );
5011 ˇ
5012 }
5013 "});
5014}
5015
5016#[gpui::test]
5017fn test_select_all(cx: &mut TestAppContext) {
5018 init_test(cx, |_| {});
5019
5020 let editor = cx.add_window(|window, cx| {
5021 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5022 build_editor(buffer, window, cx)
5023 });
5024 _ = editor.update(cx, |editor, window, cx| {
5025 editor.select_all(&SelectAll, window, cx);
5026 assert_eq!(
5027 editor.selections.display_ranges(cx),
5028 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5029 );
5030 });
5031}
5032
5033#[gpui::test]
5034fn test_select_line(cx: &mut TestAppContext) {
5035 init_test(cx, |_| {});
5036
5037 let editor = cx.add_window(|window, cx| {
5038 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5039 build_editor(buffer, window, cx)
5040 });
5041 _ = editor.update(cx, |editor, window, cx| {
5042 editor.change_selections(None, window, cx, |s| {
5043 s.select_display_ranges([
5044 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5045 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5046 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5047 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5048 ])
5049 });
5050 editor.select_line(&SelectLine, window, cx);
5051 assert_eq!(
5052 editor.selections.display_ranges(cx),
5053 vec![
5054 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5055 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5056 ]
5057 );
5058 });
5059
5060 _ = editor.update(cx, |editor, window, cx| {
5061 editor.select_line(&SelectLine, window, cx);
5062 assert_eq!(
5063 editor.selections.display_ranges(cx),
5064 vec![
5065 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5066 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5067 ]
5068 );
5069 });
5070
5071 _ = editor.update(cx, |editor, window, cx| {
5072 editor.select_line(&SelectLine, window, cx);
5073 assert_eq!(
5074 editor.selections.display_ranges(cx),
5075 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5076 );
5077 });
5078}
5079
5080#[gpui::test]
5081async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5082 init_test(cx, |_| {});
5083 let mut cx = EditorTestContext::new(cx).await;
5084
5085 #[track_caller]
5086 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5087 cx.set_state(initial_state);
5088 cx.update_editor(|e, window, cx| {
5089 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5090 });
5091 cx.assert_editor_state(expected_state);
5092 }
5093
5094 // Selection starts and ends at the middle of lines, left-to-right
5095 test(
5096 &mut cx,
5097 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5098 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5099 );
5100 // Same thing, right-to-left
5101 test(
5102 &mut cx,
5103 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5104 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5105 );
5106
5107 // Whole buffer, left-to-right, last line *doesn't* end with newline
5108 test(
5109 &mut cx,
5110 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5111 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5112 );
5113 // Same thing, right-to-left
5114 test(
5115 &mut cx,
5116 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5117 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5118 );
5119
5120 // Whole buffer, left-to-right, last line ends with newline
5121 test(
5122 &mut cx,
5123 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5124 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5125 );
5126 // Same thing, right-to-left
5127 test(
5128 &mut cx,
5129 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5130 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5131 );
5132
5133 // Starts at the end of a line, ends at the start of another
5134 test(
5135 &mut cx,
5136 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5137 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5138 );
5139}
5140
5141#[gpui::test]
5142async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5143 init_test(cx, |_| {});
5144
5145 let editor = cx.add_window(|window, cx| {
5146 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5147 build_editor(buffer, window, cx)
5148 });
5149
5150 // setup
5151 _ = editor.update(cx, |editor, window, cx| {
5152 editor.fold_creases(
5153 vec![
5154 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5155 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5156 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5157 ],
5158 true,
5159 window,
5160 cx,
5161 );
5162 assert_eq!(
5163 editor.display_text(cx),
5164 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5165 );
5166 });
5167
5168 _ = editor.update(cx, |editor, window, cx| {
5169 editor.change_selections(None, window, cx, |s| {
5170 s.select_display_ranges([
5171 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5172 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5173 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5174 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5175 ])
5176 });
5177 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5178 assert_eq!(
5179 editor.display_text(cx),
5180 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5181 );
5182 });
5183 EditorTestContext::for_editor(editor, cx)
5184 .await
5185 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5186
5187 _ = editor.update(cx, |editor, window, cx| {
5188 editor.change_selections(None, window, cx, |s| {
5189 s.select_display_ranges([
5190 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5191 ])
5192 });
5193 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5194 assert_eq!(
5195 editor.display_text(cx),
5196 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5197 );
5198 assert_eq!(
5199 editor.selections.display_ranges(cx),
5200 [
5201 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5202 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5203 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5204 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5205 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5206 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5207 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5208 ]
5209 );
5210 });
5211 EditorTestContext::for_editor(editor, cx)
5212 .await
5213 .assert_editor_state(
5214 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5215 );
5216}
5217
5218#[gpui::test]
5219async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5220 init_test(cx, |_| {});
5221
5222 let mut cx = EditorTestContext::new(cx).await;
5223
5224 cx.set_state(indoc!(
5225 r#"abc
5226 defˇghi
5227
5228 jk
5229 nlmo
5230 "#
5231 ));
5232
5233 cx.update_editor(|editor, window, cx| {
5234 editor.add_selection_above(&Default::default(), window, cx);
5235 });
5236
5237 cx.assert_editor_state(indoc!(
5238 r#"abcˇ
5239 defˇghi
5240
5241 jk
5242 nlmo
5243 "#
5244 ));
5245
5246 cx.update_editor(|editor, window, cx| {
5247 editor.add_selection_above(&Default::default(), window, cx);
5248 });
5249
5250 cx.assert_editor_state(indoc!(
5251 r#"abcˇ
5252 defˇghi
5253
5254 jk
5255 nlmo
5256 "#
5257 ));
5258
5259 cx.update_editor(|editor, window, cx| {
5260 editor.add_selection_below(&Default::default(), window, cx);
5261 });
5262
5263 cx.assert_editor_state(indoc!(
5264 r#"abc
5265 defˇghi
5266
5267 jk
5268 nlmo
5269 "#
5270 ));
5271
5272 cx.update_editor(|editor, window, cx| {
5273 editor.undo_selection(&Default::default(), window, cx);
5274 });
5275
5276 cx.assert_editor_state(indoc!(
5277 r#"abcˇ
5278 defˇghi
5279
5280 jk
5281 nlmo
5282 "#
5283 ));
5284
5285 cx.update_editor(|editor, window, cx| {
5286 editor.redo_selection(&Default::default(), window, cx);
5287 });
5288
5289 cx.assert_editor_state(indoc!(
5290 r#"abc
5291 defˇghi
5292
5293 jk
5294 nlmo
5295 "#
5296 ));
5297
5298 cx.update_editor(|editor, window, cx| {
5299 editor.add_selection_below(&Default::default(), window, cx);
5300 });
5301
5302 cx.assert_editor_state(indoc!(
5303 r#"abc
5304 defˇghi
5305
5306 jk
5307 nlmˇo
5308 "#
5309 ));
5310
5311 cx.update_editor(|editor, window, cx| {
5312 editor.add_selection_below(&Default::default(), window, cx);
5313 });
5314
5315 cx.assert_editor_state(indoc!(
5316 r#"abc
5317 defˇghi
5318
5319 jk
5320 nlmˇo
5321 "#
5322 ));
5323
5324 // change selections
5325 cx.set_state(indoc!(
5326 r#"abc
5327 def«ˇg»hi
5328
5329 jk
5330 nlmo
5331 "#
5332 ));
5333
5334 cx.update_editor(|editor, window, cx| {
5335 editor.add_selection_below(&Default::default(), window, cx);
5336 });
5337
5338 cx.assert_editor_state(indoc!(
5339 r#"abc
5340 def«ˇg»hi
5341
5342 jk
5343 nlm«ˇo»
5344 "#
5345 ));
5346
5347 cx.update_editor(|editor, window, cx| {
5348 editor.add_selection_below(&Default::default(), window, cx);
5349 });
5350
5351 cx.assert_editor_state(indoc!(
5352 r#"abc
5353 def«ˇg»hi
5354
5355 jk
5356 nlm«ˇo»
5357 "#
5358 ));
5359
5360 cx.update_editor(|editor, window, cx| {
5361 editor.add_selection_above(&Default::default(), window, cx);
5362 });
5363
5364 cx.assert_editor_state(indoc!(
5365 r#"abc
5366 def«ˇg»hi
5367
5368 jk
5369 nlmo
5370 "#
5371 ));
5372
5373 cx.update_editor(|editor, window, cx| {
5374 editor.add_selection_above(&Default::default(), window, cx);
5375 });
5376
5377 cx.assert_editor_state(indoc!(
5378 r#"abc
5379 def«ˇg»hi
5380
5381 jk
5382 nlmo
5383 "#
5384 ));
5385
5386 // Change selections again
5387 cx.set_state(indoc!(
5388 r#"a«bc
5389 defgˇ»hi
5390
5391 jk
5392 nlmo
5393 "#
5394 ));
5395
5396 cx.update_editor(|editor, window, cx| {
5397 editor.add_selection_below(&Default::default(), window, cx);
5398 });
5399
5400 cx.assert_editor_state(indoc!(
5401 r#"a«bcˇ»
5402 d«efgˇ»hi
5403
5404 j«kˇ»
5405 nlmo
5406 "#
5407 ));
5408
5409 cx.update_editor(|editor, window, cx| {
5410 editor.add_selection_below(&Default::default(), window, cx);
5411 });
5412 cx.assert_editor_state(indoc!(
5413 r#"a«bcˇ»
5414 d«efgˇ»hi
5415
5416 j«kˇ»
5417 n«lmoˇ»
5418 "#
5419 ));
5420 cx.update_editor(|editor, window, cx| {
5421 editor.add_selection_above(&Default::default(), window, cx);
5422 });
5423
5424 cx.assert_editor_state(indoc!(
5425 r#"a«bcˇ»
5426 d«efgˇ»hi
5427
5428 j«kˇ»
5429 nlmo
5430 "#
5431 ));
5432
5433 // Change selections again
5434 cx.set_state(indoc!(
5435 r#"abc
5436 d«ˇefghi
5437
5438 jk
5439 nlm»o
5440 "#
5441 ));
5442
5443 cx.update_editor(|editor, window, cx| {
5444 editor.add_selection_above(&Default::default(), window, cx);
5445 });
5446
5447 cx.assert_editor_state(indoc!(
5448 r#"a«ˇbc»
5449 d«ˇef»ghi
5450
5451 j«ˇk»
5452 n«ˇlm»o
5453 "#
5454 ));
5455
5456 cx.update_editor(|editor, window, cx| {
5457 editor.add_selection_below(&Default::default(), window, cx);
5458 });
5459
5460 cx.assert_editor_state(indoc!(
5461 r#"abc
5462 d«ˇef»ghi
5463
5464 j«ˇk»
5465 n«ˇlm»o
5466 "#
5467 ));
5468}
5469
5470#[gpui::test]
5471async fn test_select_next(cx: &mut TestAppContext) {
5472 init_test(cx, |_| {});
5473
5474 let mut cx = EditorTestContext::new(cx).await;
5475 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5476
5477 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5478 .unwrap();
5479 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5480
5481 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5482 .unwrap();
5483 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5484
5485 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5486 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5487
5488 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5489 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5490
5491 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5492 .unwrap();
5493 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5494
5495 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5496 .unwrap();
5497 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5498}
5499
5500#[gpui::test]
5501async fn test_select_all_matches(cx: &mut TestAppContext) {
5502 init_test(cx, |_| {});
5503
5504 let mut cx = EditorTestContext::new(cx).await;
5505
5506 // Test caret-only selections
5507 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5508 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5509 .unwrap();
5510 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5511
5512 // Test left-to-right selections
5513 cx.set_state("abc\n«abcˇ»\nabc");
5514 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5515 .unwrap();
5516 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5517
5518 // Test right-to-left selections
5519 cx.set_state("abc\n«ˇabc»\nabc");
5520 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5521 .unwrap();
5522 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5523
5524 // Test selecting whitespace with caret selection
5525 cx.set_state("abc\nˇ abc\nabc");
5526 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5527 .unwrap();
5528 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5529
5530 // Test selecting whitespace with left-to-right selection
5531 cx.set_state("abc\n«ˇ »abc\nabc");
5532 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5533 .unwrap();
5534 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5535
5536 // Test no matches with right-to-left selection
5537 cx.set_state("abc\n« ˇ»abc\nabc");
5538 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5539 .unwrap();
5540 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5541}
5542
5543#[gpui::test]
5544async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5545 init_test(cx, |_| {});
5546
5547 let mut cx = EditorTestContext::new(cx).await;
5548 cx.set_state(
5549 r#"let foo = 2;
5550lˇet foo = 2;
5551let fooˇ = 2;
5552let foo = 2;
5553let foo = ˇ2;"#,
5554 );
5555
5556 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5557 .unwrap();
5558 cx.assert_editor_state(
5559 r#"let foo = 2;
5560«letˇ» foo = 2;
5561let «fooˇ» = 2;
5562let foo = 2;
5563let foo = «2ˇ»;"#,
5564 );
5565
5566 // noop for multiple selections with different contents
5567 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5568 .unwrap();
5569 cx.assert_editor_state(
5570 r#"let foo = 2;
5571«letˇ» foo = 2;
5572let «fooˇ» = 2;
5573let foo = 2;
5574let foo = «2ˇ»;"#,
5575 );
5576}
5577
5578#[gpui::test]
5579async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5580 init_test(cx, |_| {});
5581
5582 let mut cx =
5583 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5584
5585 cx.assert_editor_state(indoc! {"
5586 ˇbbb
5587 ccc
5588
5589 bbb
5590 ccc
5591 "});
5592 cx.dispatch_action(SelectPrevious::default());
5593 cx.assert_editor_state(indoc! {"
5594 «bbbˇ»
5595 ccc
5596
5597 bbb
5598 ccc
5599 "});
5600 cx.dispatch_action(SelectPrevious::default());
5601 cx.assert_editor_state(indoc! {"
5602 «bbbˇ»
5603 ccc
5604
5605 «bbbˇ»
5606 ccc
5607 "});
5608}
5609
5610#[gpui::test]
5611async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5612 init_test(cx, |_| {});
5613
5614 let mut cx = EditorTestContext::new(cx).await;
5615 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5616
5617 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5618 .unwrap();
5619 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5620
5621 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5622 .unwrap();
5623 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5624
5625 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5626 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5627
5628 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5629 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5630
5631 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5632 .unwrap();
5633 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5634
5635 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5636 .unwrap();
5637 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5638
5639 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5640 .unwrap();
5641 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5642}
5643
5644#[gpui::test]
5645async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5646 init_test(cx, |_| {});
5647
5648 let mut cx = EditorTestContext::new(cx).await;
5649 cx.set_state("aˇ");
5650
5651 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5652 .unwrap();
5653 cx.assert_editor_state("«aˇ»");
5654 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5655 .unwrap();
5656 cx.assert_editor_state("«aˇ»");
5657}
5658
5659#[gpui::test]
5660async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5661 init_test(cx, |_| {});
5662
5663 let mut cx = EditorTestContext::new(cx).await;
5664 cx.set_state(
5665 r#"let foo = 2;
5666lˇet foo = 2;
5667let fooˇ = 2;
5668let foo = 2;
5669let foo = ˇ2;"#,
5670 );
5671
5672 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5673 .unwrap();
5674 cx.assert_editor_state(
5675 r#"let foo = 2;
5676«letˇ» foo = 2;
5677let «fooˇ» = 2;
5678let foo = 2;
5679let foo = «2ˇ»;"#,
5680 );
5681
5682 // noop for multiple selections with different contents
5683 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5684 .unwrap();
5685 cx.assert_editor_state(
5686 r#"let foo = 2;
5687«letˇ» foo = 2;
5688let «fooˇ» = 2;
5689let foo = 2;
5690let foo = «2ˇ»;"#,
5691 );
5692}
5693
5694#[gpui::test]
5695async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5696 init_test(cx, |_| {});
5697
5698 let mut cx = EditorTestContext::new(cx).await;
5699 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5700
5701 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5702 .unwrap();
5703 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5704
5705 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5706 .unwrap();
5707 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5708
5709 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5710 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5711
5712 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5713 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5714
5715 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5716 .unwrap();
5717 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5718
5719 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5720 .unwrap();
5721 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5722}
5723
5724#[gpui::test]
5725async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5726 init_test(cx, |_| {});
5727
5728 let language = Arc::new(Language::new(
5729 LanguageConfig::default(),
5730 Some(tree_sitter_rust::LANGUAGE.into()),
5731 ));
5732
5733 let text = r#"
5734 use mod1::mod2::{mod3, mod4};
5735
5736 fn fn_1(param1: bool, param2: &str) {
5737 let var1 = "text";
5738 }
5739 "#
5740 .unindent();
5741
5742 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5743 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5744 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5745
5746 editor
5747 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5748 .await;
5749
5750 editor.update_in(cx, |editor, window, cx| {
5751 editor.change_selections(None, window, cx, |s| {
5752 s.select_display_ranges([
5753 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5754 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5755 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5756 ]);
5757 });
5758 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5759 });
5760 editor.update(cx, |editor, cx| {
5761 assert_text_with_selections(
5762 editor,
5763 indoc! {r#"
5764 use mod1::mod2::{mod3, «mod4ˇ»};
5765
5766 fn fn_1«ˇ(param1: bool, param2: &str)» {
5767 let var1 = "«textˇ»";
5768 }
5769 "#},
5770 cx,
5771 );
5772 });
5773
5774 editor.update_in(cx, |editor, window, cx| {
5775 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5776 });
5777 editor.update(cx, |editor, cx| {
5778 assert_text_with_selections(
5779 editor,
5780 indoc! {r#"
5781 use mod1::mod2::«{mod3, mod4}ˇ»;
5782
5783 «ˇfn fn_1(param1: bool, param2: &str) {
5784 let var1 = "text";
5785 }»
5786 "#},
5787 cx,
5788 );
5789 });
5790
5791 editor.update_in(cx, |editor, window, cx| {
5792 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5793 });
5794 assert_eq!(
5795 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5796 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5797 );
5798
5799 // Trying to expand the selected syntax node one more time has no effect.
5800 editor.update_in(cx, |editor, window, cx| {
5801 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5802 });
5803 assert_eq!(
5804 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5805 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5806 );
5807
5808 editor.update_in(cx, |editor, window, cx| {
5809 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5810 });
5811 editor.update(cx, |editor, cx| {
5812 assert_text_with_selections(
5813 editor,
5814 indoc! {r#"
5815 use mod1::mod2::«{mod3, mod4}ˇ»;
5816
5817 «ˇfn fn_1(param1: bool, param2: &str) {
5818 let var1 = "text";
5819 }»
5820 "#},
5821 cx,
5822 );
5823 });
5824
5825 editor.update_in(cx, |editor, window, cx| {
5826 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5827 });
5828 editor.update(cx, |editor, cx| {
5829 assert_text_with_selections(
5830 editor,
5831 indoc! {r#"
5832 use mod1::mod2::{mod3, «mod4ˇ»};
5833
5834 fn fn_1«ˇ(param1: bool, param2: &str)» {
5835 let var1 = "«textˇ»";
5836 }
5837 "#},
5838 cx,
5839 );
5840 });
5841
5842 editor.update_in(cx, |editor, window, cx| {
5843 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5844 });
5845 editor.update(cx, |editor, cx| {
5846 assert_text_with_selections(
5847 editor,
5848 indoc! {r#"
5849 use mod1::mod2::{mod3, mo«ˇ»d4};
5850
5851 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5852 let var1 = "te«ˇ»xt";
5853 }
5854 "#},
5855 cx,
5856 );
5857 });
5858
5859 // Trying to shrink the selected syntax node one more time has no effect.
5860 editor.update_in(cx, |editor, window, cx| {
5861 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5862 });
5863 editor.update_in(cx, |editor, _, cx| {
5864 assert_text_with_selections(
5865 editor,
5866 indoc! {r#"
5867 use mod1::mod2::{mod3, mo«ˇ»d4};
5868
5869 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5870 let var1 = "te«ˇ»xt";
5871 }
5872 "#},
5873 cx,
5874 );
5875 });
5876
5877 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5878 // a fold.
5879 editor.update_in(cx, |editor, window, cx| {
5880 editor.fold_creases(
5881 vec![
5882 Crease::simple(
5883 Point::new(0, 21)..Point::new(0, 24),
5884 FoldPlaceholder::test(),
5885 ),
5886 Crease::simple(
5887 Point::new(3, 20)..Point::new(3, 22),
5888 FoldPlaceholder::test(),
5889 ),
5890 ],
5891 true,
5892 window,
5893 cx,
5894 );
5895 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5896 });
5897 editor.update(cx, |editor, cx| {
5898 assert_text_with_selections(
5899 editor,
5900 indoc! {r#"
5901 use mod1::mod2::«{mod3, mod4}ˇ»;
5902
5903 fn fn_1«ˇ(param1: bool, param2: &str)» {
5904 «let var1 = "text";ˇ»
5905 }
5906 "#},
5907 cx,
5908 );
5909 });
5910}
5911
5912#[gpui::test]
5913async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5914 init_test(cx, |_| {});
5915
5916 let base_text = r#"
5917 impl A {
5918 // this is an uncommitted comment
5919
5920 fn b() {
5921 c();
5922 }
5923
5924 // this is another uncommitted comment
5925
5926 fn d() {
5927 // e
5928 // f
5929 }
5930 }
5931
5932 fn g() {
5933 // h
5934 }
5935 "#
5936 .unindent();
5937
5938 let text = r#"
5939 ˇimpl A {
5940
5941 fn b() {
5942 c();
5943 }
5944
5945 fn d() {
5946 // e
5947 // f
5948 }
5949 }
5950
5951 fn g() {
5952 // h
5953 }
5954 "#
5955 .unindent();
5956
5957 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5958 cx.set_state(&text);
5959 cx.set_head_text(&base_text);
5960 cx.update_editor(|editor, window, cx| {
5961 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5962 });
5963
5964 cx.assert_state_with_diff(
5965 "
5966 ˇimpl A {
5967 - // this is an uncommitted comment
5968
5969 fn b() {
5970 c();
5971 }
5972
5973 - // this is another uncommitted comment
5974 -
5975 fn d() {
5976 // e
5977 // f
5978 }
5979 }
5980
5981 fn g() {
5982 // h
5983 }
5984 "
5985 .unindent(),
5986 );
5987
5988 let expected_display_text = "
5989 impl A {
5990 // this is an uncommitted comment
5991
5992 fn b() {
5993 ⋯
5994 }
5995
5996 // this is another uncommitted comment
5997
5998 fn d() {
5999 ⋯
6000 }
6001 }
6002
6003 fn g() {
6004 ⋯
6005 }
6006 "
6007 .unindent();
6008
6009 cx.update_editor(|editor, window, cx| {
6010 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6011 assert_eq!(editor.display_text(cx), expected_display_text);
6012 });
6013}
6014
6015#[gpui::test]
6016async fn test_autoindent(cx: &mut TestAppContext) {
6017 init_test(cx, |_| {});
6018
6019 let language = Arc::new(
6020 Language::new(
6021 LanguageConfig {
6022 brackets: BracketPairConfig {
6023 pairs: vec![
6024 BracketPair {
6025 start: "{".to_string(),
6026 end: "}".to_string(),
6027 close: false,
6028 surround: false,
6029 newline: true,
6030 },
6031 BracketPair {
6032 start: "(".to_string(),
6033 end: ")".to_string(),
6034 close: false,
6035 surround: false,
6036 newline: true,
6037 },
6038 ],
6039 ..Default::default()
6040 },
6041 ..Default::default()
6042 },
6043 Some(tree_sitter_rust::LANGUAGE.into()),
6044 )
6045 .with_indents_query(
6046 r#"
6047 (_ "(" ")" @end) @indent
6048 (_ "{" "}" @end) @indent
6049 "#,
6050 )
6051 .unwrap(),
6052 );
6053
6054 let text = "fn a() {}";
6055
6056 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6057 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6058 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6059 editor
6060 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6061 .await;
6062
6063 editor.update_in(cx, |editor, window, cx| {
6064 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6065 editor.newline(&Newline, window, cx);
6066 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6067 assert_eq!(
6068 editor.selections.ranges(cx),
6069 &[
6070 Point::new(1, 4)..Point::new(1, 4),
6071 Point::new(3, 4)..Point::new(3, 4),
6072 Point::new(5, 0)..Point::new(5, 0)
6073 ]
6074 );
6075 });
6076}
6077
6078#[gpui::test]
6079async fn test_autoindent_selections(cx: &mut TestAppContext) {
6080 init_test(cx, |_| {});
6081
6082 {
6083 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6084 cx.set_state(indoc! {"
6085 impl A {
6086
6087 fn b() {}
6088
6089 «fn c() {
6090
6091 }ˇ»
6092 }
6093 "});
6094
6095 cx.update_editor(|editor, window, cx| {
6096 editor.autoindent(&Default::default(), window, cx);
6097 });
6098
6099 cx.assert_editor_state(indoc! {"
6100 impl A {
6101
6102 fn b() {}
6103
6104 «fn c() {
6105
6106 }ˇ»
6107 }
6108 "});
6109 }
6110
6111 {
6112 let mut cx = EditorTestContext::new_multibuffer(
6113 cx,
6114 [indoc! { "
6115 impl A {
6116 «
6117 // a
6118 fn b(){}
6119 »
6120 «
6121 }
6122 fn c(){}
6123 »
6124 "}],
6125 );
6126
6127 let buffer = cx.update_editor(|editor, _, cx| {
6128 let buffer = editor.buffer().update(cx, |buffer, _| {
6129 buffer.all_buffers().iter().next().unwrap().clone()
6130 });
6131 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6132 buffer
6133 });
6134
6135 cx.run_until_parked();
6136 cx.update_editor(|editor, window, cx| {
6137 editor.select_all(&Default::default(), window, cx);
6138 editor.autoindent(&Default::default(), window, cx)
6139 });
6140 cx.run_until_parked();
6141
6142 cx.update(|_, cx| {
6143 pretty_assertions::assert_eq!(
6144 buffer.read(cx).text(),
6145 indoc! { "
6146 impl A {
6147
6148 // a
6149 fn b(){}
6150
6151
6152 }
6153 fn c(){}
6154
6155 " }
6156 )
6157 });
6158 }
6159}
6160
6161#[gpui::test]
6162async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6163 init_test(cx, |_| {});
6164
6165 let mut cx = EditorTestContext::new(cx).await;
6166
6167 let language = Arc::new(Language::new(
6168 LanguageConfig {
6169 brackets: BracketPairConfig {
6170 pairs: vec![
6171 BracketPair {
6172 start: "{".to_string(),
6173 end: "}".to_string(),
6174 close: true,
6175 surround: true,
6176 newline: true,
6177 },
6178 BracketPair {
6179 start: "(".to_string(),
6180 end: ")".to_string(),
6181 close: true,
6182 surround: true,
6183 newline: true,
6184 },
6185 BracketPair {
6186 start: "/*".to_string(),
6187 end: " */".to_string(),
6188 close: true,
6189 surround: true,
6190 newline: true,
6191 },
6192 BracketPair {
6193 start: "[".to_string(),
6194 end: "]".to_string(),
6195 close: false,
6196 surround: false,
6197 newline: true,
6198 },
6199 BracketPair {
6200 start: "\"".to_string(),
6201 end: "\"".to_string(),
6202 close: true,
6203 surround: true,
6204 newline: false,
6205 },
6206 BracketPair {
6207 start: "<".to_string(),
6208 end: ">".to_string(),
6209 close: false,
6210 surround: true,
6211 newline: true,
6212 },
6213 ],
6214 ..Default::default()
6215 },
6216 autoclose_before: "})]".to_string(),
6217 ..Default::default()
6218 },
6219 Some(tree_sitter_rust::LANGUAGE.into()),
6220 ));
6221
6222 cx.language_registry().add(language.clone());
6223 cx.update_buffer(|buffer, cx| {
6224 buffer.set_language(Some(language), cx);
6225 });
6226
6227 cx.set_state(
6228 &r#"
6229 🏀ˇ
6230 εˇ
6231 ❤️ˇ
6232 "#
6233 .unindent(),
6234 );
6235
6236 // autoclose multiple nested brackets at multiple cursors
6237 cx.update_editor(|editor, window, cx| {
6238 editor.handle_input("{", window, cx);
6239 editor.handle_input("{", window, cx);
6240 editor.handle_input("{", window, cx);
6241 });
6242 cx.assert_editor_state(
6243 &"
6244 🏀{{{ˇ}}}
6245 ε{{{ˇ}}}
6246 ❤️{{{ˇ}}}
6247 "
6248 .unindent(),
6249 );
6250
6251 // insert a different closing bracket
6252 cx.update_editor(|editor, window, cx| {
6253 editor.handle_input(")", window, cx);
6254 });
6255 cx.assert_editor_state(
6256 &"
6257 🏀{{{)ˇ}}}
6258 ε{{{)ˇ}}}
6259 ❤️{{{)ˇ}}}
6260 "
6261 .unindent(),
6262 );
6263
6264 // skip over the auto-closed brackets when typing a closing bracket
6265 cx.update_editor(|editor, window, cx| {
6266 editor.move_right(&MoveRight, window, cx);
6267 editor.handle_input("}", window, cx);
6268 editor.handle_input("}", window, cx);
6269 editor.handle_input("}", window, cx);
6270 });
6271 cx.assert_editor_state(
6272 &"
6273 🏀{{{)}}}}ˇ
6274 ε{{{)}}}}ˇ
6275 ❤️{{{)}}}}ˇ
6276 "
6277 .unindent(),
6278 );
6279
6280 // autoclose multi-character pairs
6281 cx.set_state(
6282 &"
6283 ˇ
6284 ˇ
6285 "
6286 .unindent(),
6287 );
6288 cx.update_editor(|editor, window, cx| {
6289 editor.handle_input("/", window, cx);
6290 editor.handle_input("*", window, cx);
6291 });
6292 cx.assert_editor_state(
6293 &"
6294 /*ˇ */
6295 /*ˇ */
6296 "
6297 .unindent(),
6298 );
6299
6300 // one cursor autocloses a multi-character pair, one cursor
6301 // does not autoclose.
6302 cx.set_state(
6303 &"
6304 /ˇ
6305 ˇ
6306 "
6307 .unindent(),
6308 );
6309 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6310 cx.assert_editor_state(
6311 &"
6312 /*ˇ */
6313 *ˇ
6314 "
6315 .unindent(),
6316 );
6317
6318 // Don't autoclose if the next character isn't whitespace and isn't
6319 // listed in the language's "autoclose_before" section.
6320 cx.set_state("ˇa b");
6321 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6322 cx.assert_editor_state("{ˇa b");
6323
6324 // Don't autoclose if `close` is false for the bracket pair
6325 cx.set_state("ˇ");
6326 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6327 cx.assert_editor_state("[ˇ");
6328
6329 // Surround with brackets if text is selected
6330 cx.set_state("«aˇ» b");
6331 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6332 cx.assert_editor_state("{«aˇ»} b");
6333
6334 // Autclose pair where the start and end characters are the same
6335 cx.set_state("aˇ");
6336 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6337 cx.assert_editor_state("a\"ˇ\"");
6338 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6339 cx.assert_editor_state("a\"\"ˇ");
6340
6341 // Don't autoclose pair if autoclose is disabled
6342 cx.set_state("ˇ");
6343 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6344 cx.assert_editor_state("<ˇ");
6345
6346 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6347 cx.set_state("«aˇ» b");
6348 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6349 cx.assert_editor_state("<«aˇ»> b");
6350}
6351
6352#[gpui::test]
6353async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6354 init_test(cx, |settings| {
6355 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6356 });
6357
6358 let mut cx = EditorTestContext::new(cx).await;
6359
6360 let language = Arc::new(Language::new(
6361 LanguageConfig {
6362 brackets: BracketPairConfig {
6363 pairs: vec![
6364 BracketPair {
6365 start: "{".to_string(),
6366 end: "}".to_string(),
6367 close: true,
6368 surround: true,
6369 newline: true,
6370 },
6371 BracketPair {
6372 start: "(".to_string(),
6373 end: ")".to_string(),
6374 close: true,
6375 surround: true,
6376 newline: true,
6377 },
6378 BracketPair {
6379 start: "[".to_string(),
6380 end: "]".to_string(),
6381 close: false,
6382 surround: false,
6383 newline: true,
6384 },
6385 ],
6386 ..Default::default()
6387 },
6388 autoclose_before: "})]".to_string(),
6389 ..Default::default()
6390 },
6391 Some(tree_sitter_rust::LANGUAGE.into()),
6392 ));
6393
6394 cx.language_registry().add(language.clone());
6395 cx.update_buffer(|buffer, cx| {
6396 buffer.set_language(Some(language), cx);
6397 });
6398
6399 cx.set_state(
6400 &"
6401 ˇ
6402 ˇ
6403 ˇ
6404 "
6405 .unindent(),
6406 );
6407
6408 // ensure only matching closing brackets are skipped over
6409 cx.update_editor(|editor, window, cx| {
6410 editor.handle_input("}", window, cx);
6411 editor.move_left(&MoveLeft, window, cx);
6412 editor.handle_input(")", window, cx);
6413 editor.move_left(&MoveLeft, window, cx);
6414 });
6415 cx.assert_editor_state(
6416 &"
6417 ˇ)}
6418 ˇ)}
6419 ˇ)}
6420 "
6421 .unindent(),
6422 );
6423
6424 // skip-over closing brackets at multiple cursors
6425 cx.update_editor(|editor, window, cx| {
6426 editor.handle_input(")", window, cx);
6427 editor.handle_input("}", window, cx);
6428 });
6429 cx.assert_editor_state(
6430 &"
6431 )}ˇ
6432 )}ˇ
6433 )}ˇ
6434 "
6435 .unindent(),
6436 );
6437
6438 // ignore non-close brackets
6439 cx.update_editor(|editor, window, cx| {
6440 editor.handle_input("]", window, cx);
6441 editor.move_left(&MoveLeft, window, cx);
6442 editor.handle_input("]", window, cx);
6443 });
6444 cx.assert_editor_state(
6445 &"
6446 )}]ˇ]
6447 )}]ˇ]
6448 )}]ˇ]
6449 "
6450 .unindent(),
6451 );
6452}
6453
6454#[gpui::test]
6455async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6456 init_test(cx, |_| {});
6457
6458 let mut cx = EditorTestContext::new(cx).await;
6459
6460 let html_language = Arc::new(
6461 Language::new(
6462 LanguageConfig {
6463 name: "HTML".into(),
6464 brackets: BracketPairConfig {
6465 pairs: vec![
6466 BracketPair {
6467 start: "<".into(),
6468 end: ">".into(),
6469 close: true,
6470 ..Default::default()
6471 },
6472 BracketPair {
6473 start: "{".into(),
6474 end: "}".into(),
6475 close: true,
6476 ..Default::default()
6477 },
6478 BracketPair {
6479 start: "(".into(),
6480 end: ")".into(),
6481 close: true,
6482 ..Default::default()
6483 },
6484 ],
6485 ..Default::default()
6486 },
6487 autoclose_before: "})]>".into(),
6488 ..Default::default()
6489 },
6490 Some(tree_sitter_html::LANGUAGE.into()),
6491 )
6492 .with_injection_query(
6493 r#"
6494 (script_element
6495 (raw_text) @injection.content
6496 (#set! injection.language "javascript"))
6497 "#,
6498 )
6499 .unwrap(),
6500 );
6501
6502 let javascript_language = Arc::new(Language::new(
6503 LanguageConfig {
6504 name: "JavaScript".into(),
6505 brackets: BracketPairConfig {
6506 pairs: vec![
6507 BracketPair {
6508 start: "/*".into(),
6509 end: " */".into(),
6510 close: true,
6511 ..Default::default()
6512 },
6513 BracketPair {
6514 start: "{".into(),
6515 end: "}".into(),
6516 close: true,
6517 ..Default::default()
6518 },
6519 BracketPair {
6520 start: "(".into(),
6521 end: ")".into(),
6522 close: true,
6523 ..Default::default()
6524 },
6525 ],
6526 ..Default::default()
6527 },
6528 autoclose_before: "})]>".into(),
6529 ..Default::default()
6530 },
6531 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6532 ));
6533
6534 cx.language_registry().add(html_language.clone());
6535 cx.language_registry().add(javascript_language.clone());
6536
6537 cx.update_buffer(|buffer, cx| {
6538 buffer.set_language(Some(html_language), cx);
6539 });
6540
6541 cx.set_state(
6542 &r#"
6543 <body>ˇ
6544 <script>
6545 var x = 1;ˇ
6546 </script>
6547 </body>ˇ
6548 "#
6549 .unindent(),
6550 );
6551
6552 // Precondition: different languages are active at different locations.
6553 cx.update_editor(|editor, window, cx| {
6554 let snapshot = editor.snapshot(window, cx);
6555 let cursors = editor.selections.ranges::<usize>(cx);
6556 let languages = cursors
6557 .iter()
6558 .map(|c| snapshot.language_at(c.start).unwrap().name())
6559 .collect::<Vec<_>>();
6560 assert_eq!(
6561 languages,
6562 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6563 );
6564 });
6565
6566 // Angle brackets autoclose in HTML, but not JavaScript.
6567 cx.update_editor(|editor, window, cx| {
6568 editor.handle_input("<", window, cx);
6569 editor.handle_input("a", window, cx);
6570 });
6571 cx.assert_editor_state(
6572 &r#"
6573 <body><aˇ>
6574 <script>
6575 var x = 1;<aˇ
6576 </script>
6577 </body><aˇ>
6578 "#
6579 .unindent(),
6580 );
6581
6582 // Curly braces and parens autoclose in both HTML and JavaScript.
6583 cx.update_editor(|editor, window, cx| {
6584 editor.handle_input(" b=", window, cx);
6585 editor.handle_input("{", window, cx);
6586 editor.handle_input("c", window, cx);
6587 editor.handle_input("(", window, cx);
6588 });
6589 cx.assert_editor_state(
6590 &r#"
6591 <body><a b={c(ˇ)}>
6592 <script>
6593 var x = 1;<a b={c(ˇ)}
6594 </script>
6595 </body><a b={c(ˇ)}>
6596 "#
6597 .unindent(),
6598 );
6599
6600 // Brackets that were already autoclosed are skipped.
6601 cx.update_editor(|editor, window, cx| {
6602 editor.handle_input(")", window, cx);
6603 editor.handle_input("d", window, cx);
6604 editor.handle_input("}", window, cx);
6605 });
6606 cx.assert_editor_state(
6607 &r#"
6608 <body><a b={c()d}ˇ>
6609 <script>
6610 var x = 1;<a b={c()d}ˇ
6611 </script>
6612 </body><a b={c()d}ˇ>
6613 "#
6614 .unindent(),
6615 );
6616 cx.update_editor(|editor, window, cx| {
6617 editor.handle_input(">", window, cx);
6618 });
6619 cx.assert_editor_state(
6620 &r#"
6621 <body><a b={c()d}>ˇ
6622 <script>
6623 var x = 1;<a b={c()d}>ˇ
6624 </script>
6625 </body><a b={c()d}>ˇ
6626 "#
6627 .unindent(),
6628 );
6629
6630 // Reset
6631 cx.set_state(
6632 &r#"
6633 <body>ˇ
6634 <script>
6635 var x = 1;ˇ
6636 </script>
6637 </body>ˇ
6638 "#
6639 .unindent(),
6640 );
6641
6642 cx.update_editor(|editor, window, cx| {
6643 editor.handle_input("<", window, cx);
6644 });
6645 cx.assert_editor_state(
6646 &r#"
6647 <body><ˇ>
6648 <script>
6649 var x = 1;<ˇ
6650 </script>
6651 </body><ˇ>
6652 "#
6653 .unindent(),
6654 );
6655
6656 // When backspacing, the closing angle brackets are removed.
6657 cx.update_editor(|editor, window, cx| {
6658 editor.backspace(&Backspace, window, cx);
6659 });
6660 cx.assert_editor_state(
6661 &r#"
6662 <body>ˇ
6663 <script>
6664 var x = 1;ˇ
6665 </script>
6666 </body>ˇ
6667 "#
6668 .unindent(),
6669 );
6670
6671 // Block comments autoclose in JavaScript, but not HTML.
6672 cx.update_editor(|editor, window, cx| {
6673 editor.handle_input("/", window, cx);
6674 editor.handle_input("*", window, cx);
6675 });
6676 cx.assert_editor_state(
6677 &r#"
6678 <body>/*ˇ
6679 <script>
6680 var x = 1;/*ˇ */
6681 </script>
6682 </body>/*ˇ
6683 "#
6684 .unindent(),
6685 );
6686}
6687
6688#[gpui::test]
6689async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6690 init_test(cx, |_| {});
6691
6692 let mut cx = EditorTestContext::new(cx).await;
6693
6694 let rust_language = Arc::new(
6695 Language::new(
6696 LanguageConfig {
6697 name: "Rust".into(),
6698 brackets: serde_json::from_value(json!([
6699 { "start": "{", "end": "}", "close": true, "newline": true },
6700 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6701 ]))
6702 .unwrap(),
6703 autoclose_before: "})]>".into(),
6704 ..Default::default()
6705 },
6706 Some(tree_sitter_rust::LANGUAGE.into()),
6707 )
6708 .with_override_query("(string_literal) @string")
6709 .unwrap(),
6710 );
6711
6712 cx.language_registry().add(rust_language.clone());
6713 cx.update_buffer(|buffer, cx| {
6714 buffer.set_language(Some(rust_language), cx);
6715 });
6716
6717 cx.set_state(
6718 &r#"
6719 let x = ˇ
6720 "#
6721 .unindent(),
6722 );
6723
6724 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6725 cx.update_editor(|editor, window, cx| {
6726 editor.handle_input("\"", window, cx);
6727 });
6728 cx.assert_editor_state(
6729 &r#"
6730 let x = "ˇ"
6731 "#
6732 .unindent(),
6733 );
6734
6735 // Inserting another quotation mark. The cursor moves across the existing
6736 // automatically-inserted quotation mark.
6737 cx.update_editor(|editor, window, cx| {
6738 editor.handle_input("\"", window, cx);
6739 });
6740 cx.assert_editor_state(
6741 &r#"
6742 let x = ""ˇ
6743 "#
6744 .unindent(),
6745 );
6746
6747 // Reset
6748 cx.set_state(
6749 &r#"
6750 let x = ˇ
6751 "#
6752 .unindent(),
6753 );
6754
6755 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6756 cx.update_editor(|editor, window, cx| {
6757 editor.handle_input("\"", window, cx);
6758 editor.handle_input(" ", window, cx);
6759 editor.move_left(&Default::default(), window, cx);
6760 editor.handle_input("\\", window, cx);
6761 editor.handle_input("\"", window, cx);
6762 });
6763 cx.assert_editor_state(
6764 &r#"
6765 let x = "\"ˇ "
6766 "#
6767 .unindent(),
6768 );
6769
6770 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6771 // mark. Nothing is inserted.
6772 cx.update_editor(|editor, window, cx| {
6773 editor.move_right(&Default::default(), window, cx);
6774 editor.handle_input("\"", window, cx);
6775 });
6776 cx.assert_editor_state(
6777 &r#"
6778 let x = "\" "ˇ
6779 "#
6780 .unindent(),
6781 );
6782}
6783
6784#[gpui::test]
6785async fn test_surround_with_pair(cx: &mut TestAppContext) {
6786 init_test(cx, |_| {});
6787
6788 let language = Arc::new(Language::new(
6789 LanguageConfig {
6790 brackets: BracketPairConfig {
6791 pairs: vec![
6792 BracketPair {
6793 start: "{".to_string(),
6794 end: "}".to_string(),
6795 close: true,
6796 surround: true,
6797 newline: true,
6798 },
6799 BracketPair {
6800 start: "/* ".to_string(),
6801 end: "*/".to_string(),
6802 close: true,
6803 surround: true,
6804 ..Default::default()
6805 },
6806 ],
6807 ..Default::default()
6808 },
6809 ..Default::default()
6810 },
6811 Some(tree_sitter_rust::LANGUAGE.into()),
6812 ));
6813
6814 let text = r#"
6815 a
6816 b
6817 c
6818 "#
6819 .unindent();
6820
6821 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6822 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6823 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6824 editor
6825 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6826 .await;
6827
6828 editor.update_in(cx, |editor, window, cx| {
6829 editor.change_selections(None, window, cx, |s| {
6830 s.select_display_ranges([
6831 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6832 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6833 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6834 ])
6835 });
6836
6837 editor.handle_input("{", window, cx);
6838 editor.handle_input("{", window, cx);
6839 editor.handle_input("{", window, cx);
6840 assert_eq!(
6841 editor.text(cx),
6842 "
6843 {{{a}}}
6844 {{{b}}}
6845 {{{c}}}
6846 "
6847 .unindent()
6848 );
6849 assert_eq!(
6850 editor.selections.display_ranges(cx),
6851 [
6852 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6853 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6854 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6855 ]
6856 );
6857
6858 editor.undo(&Undo, window, cx);
6859 editor.undo(&Undo, window, cx);
6860 editor.undo(&Undo, window, cx);
6861 assert_eq!(
6862 editor.text(cx),
6863 "
6864 a
6865 b
6866 c
6867 "
6868 .unindent()
6869 );
6870 assert_eq!(
6871 editor.selections.display_ranges(cx),
6872 [
6873 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6874 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6875 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6876 ]
6877 );
6878
6879 // Ensure inserting the first character of a multi-byte bracket pair
6880 // doesn't surround the selections with the bracket.
6881 editor.handle_input("/", window, cx);
6882 assert_eq!(
6883 editor.text(cx),
6884 "
6885 /
6886 /
6887 /
6888 "
6889 .unindent()
6890 );
6891 assert_eq!(
6892 editor.selections.display_ranges(cx),
6893 [
6894 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6895 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6896 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6897 ]
6898 );
6899
6900 editor.undo(&Undo, window, cx);
6901 assert_eq!(
6902 editor.text(cx),
6903 "
6904 a
6905 b
6906 c
6907 "
6908 .unindent()
6909 );
6910 assert_eq!(
6911 editor.selections.display_ranges(cx),
6912 [
6913 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6914 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6915 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6916 ]
6917 );
6918
6919 // Ensure inserting the last character of a multi-byte bracket pair
6920 // doesn't surround the selections with the bracket.
6921 editor.handle_input("*", window, cx);
6922 assert_eq!(
6923 editor.text(cx),
6924 "
6925 *
6926 *
6927 *
6928 "
6929 .unindent()
6930 );
6931 assert_eq!(
6932 editor.selections.display_ranges(cx),
6933 [
6934 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6935 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6936 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6937 ]
6938 );
6939 });
6940}
6941
6942#[gpui::test]
6943async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6944 init_test(cx, |_| {});
6945
6946 let language = Arc::new(Language::new(
6947 LanguageConfig {
6948 brackets: BracketPairConfig {
6949 pairs: vec![BracketPair {
6950 start: "{".to_string(),
6951 end: "}".to_string(),
6952 close: true,
6953 surround: true,
6954 newline: true,
6955 }],
6956 ..Default::default()
6957 },
6958 autoclose_before: "}".to_string(),
6959 ..Default::default()
6960 },
6961 Some(tree_sitter_rust::LANGUAGE.into()),
6962 ));
6963
6964 let text = r#"
6965 a
6966 b
6967 c
6968 "#
6969 .unindent();
6970
6971 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6972 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6973 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6974 editor
6975 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6976 .await;
6977
6978 editor.update_in(cx, |editor, window, cx| {
6979 editor.change_selections(None, window, cx, |s| {
6980 s.select_ranges([
6981 Point::new(0, 1)..Point::new(0, 1),
6982 Point::new(1, 1)..Point::new(1, 1),
6983 Point::new(2, 1)..Point::new(2, 1),
6984 ])
6985 });
6986
6987 editor.handle_input("{", window, cx);
6988 editor.handle_input("{", window, cx);
6989 editor.handle_input("_", window, cx);
6990 assert_eq!(
6991 editor.text(cx),
6992 "
6993 a{{_}}
6994 b{{_}}
6995 c{{_}}
6996 "
6997 .unindent()
6998 );
6999 assert_eq!(
7000 editor.selections.ranges::<Point>(cx),
7001 [
7002 Point::new(0, 4)..Point::new(0, 4),
7003 Point::new(1, 4)..Point::new(1, 4),
7004 Point::new(2, 4)..Point::new(2, 4)
7005 ]
7006 );
7007
7008 editor.backspace(&Default::default(), window, cx);
7009 editor.backspace(&Default::default(), window, cx);
7010 assert_eq!(
7011 editor.text(cx),
7012 "
7013 a{}
7014 b{}
7015 c{}
7016 "
7017 .unindent()
7018 );
7019 assert_eq!(
7020 editor.selections.ranges::<Point>(cx),
7021 [
7022 Point::new(0, 2)..Point::new(0, 2),
7023 Point::new(1, 2)..Point::new(1, 2),
7024 Point::new(2, 2)..Point::new(2, 2)
7025 ]
7026 );
7027
7028 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7029 assert_eq!(
7030 editor.text(cx),
7031 "
7032 a
7033 b
7034 c
7035 "
7036 .unindent()
7037 );
7038 assert_eq!(
7039 editor.selections.ranges::<Point>(cx),
7040 [
7041 Point::new(0, 1)..Point::new(0, 1),
7042 Point::new(1, 1)..Point::new(1, 1),
7043 Point::new(2, 1)..Point::new(2, 1)
7044 ]
7045 );
7046 });
7047}
7048
7049#[gpui::test]
7050async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7051 init_test(cx, |settings| {
7052 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7053 });
7054
7055 let mut cx = EditorTestContext::new(cx).await;
7056
7057 let language = Arc::new(Language::new(
7058 LanguageConfig {
7059 brackets: BracketPairConfig {
7060 pairs: vec![
7061 BracketPair {
7062 start: "{".to_string(),
7063 end: "}".to_string(),
7064 close: true,
7065 surround: true,
7066 newline: true,
7067 },
7068 BracketPair {
7069 start: "(".to_string(),
7070 end: ")".to_string(),
7071 close: true,
7072 surround: true,
7073 newline: true,
7074 },
7075 BracketPair {
7076 start: "[".to_string(),
7077 end: "]".to_string(),
7078 close: false,
7079 surround: true,
7080 newline: true,
7081 },
7082 ],
7083 ..Default::default()
7084 },
7085 autoclose_before: "})]".to_string(),
7086 ..Default::default()
7087 },
7088 Some(tree_sitter_rust::LANGUAGE.into()),
7089 ));
7090
7091 cx.language_registry().add(language.clone());
7092 cx.update_buffer(|buffer, cx| {
7093 buffer.set_language(Some(language), cx);
7094 });
7095
7096 cx.set_state(
7097 &"
7098 {(ˇ)}
7099 [[ˇ]]
7100 {(ˇ)}
7101 "
7102 .unindent(),
7103 );
7104
7105 cx.update_editor(|editor, window, cx| {
7106 editor.backspace(&Default::default(), window, cx);
7107 editor.backspace(&Default::default(), window, cx);
7108 });
7109
7110 cx.assert_editor_state(
7111 &"
7112 ˇ
7113 ˇ]]
7114 ˇ
7115 "
7116 .unindent(),
7117 );
7118
7119 cx.update_editor(|editor, window, cx| {
7120 editor.handle_input("{", window, cx);
7121 editor.handle_input("{", window, cx);
7122 editor.move_right(&MoveRight, window, cx);
7123 editor.move_right(&MoveRight, window, cx);
7124 editor.move_left(&MoveLeft, window, cx);
7125 editor.move_left(&MoveLeft, window, cx);
7126 editor.backspace(&Default::default(), window, cx);
7127 });
7128
7129 cx.assert_editor_state(
7130 &"
7131 {ˇ}
7132 {ˇ}]]
7133 {ˇ}
7134 "
7135 .unindent(),
7136 );
7137
7138 cx.update_editor(|editor, window, cx| {
7139 editor.backspace(&Default::default(), window, cx);
7140 });
7141
7142 cx.assert_editor_state(
7143 &"
7144 ˇ
7145 ˇ]]
7146 ˇ
7147 "
7148 .unindent(),
7149 );
7150}
7151
7152#[gpui::test]
7153async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7154 init_test(cx, |_| {});
7155
7156 let language = Arc::new(Language::new(
7157 LanguageConfig::default(),
7158 Some(tree_sitter_rust::LANGUAGE.into()),
7159 ));
7160
7161 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7162 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7163 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7164 editor
7165 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7166 .await;
7167
7168 editor.update_in(cx, |editor, window, cx| {
7169 editor.set_auto_replace_emoji_shortcode(true);
7170
7171 editor.handle_input("Hello ", window, cx);
7172 editor.handle_input(":wave", window, cx);
7173 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7174
7175 editor.handle_input(":", window, cx);
7176 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7177
7178 editor.handle_input(" :smile", window, cx);
7179 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7180
7181 editor.handle_input(":", window, cx);
7182 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7183
7184 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7185 editor.handle_input(":wave", window, cx);
7186 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7187
7188 editor.handle_input(":", window, cx);
7189 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7190
7191 editor.handle_input(":1", window, cx);
7192 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7193
7194 editor.handle_input(":", window, cx);
7195 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7196
7197 // Ensure shortcode does not get replaced when it is part of a word
7198 editor.handle_input(" Test:wave", window, cx);
7199 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7200
7201 editor.handle_input(":", window, cx);
7202 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7203
7204 editor.set_auto_replace_emoji_shortcode(false);
7205
7206 // Ensure shortcode does not get replaced when auto replace is off
7207 editor.handle_input(" :wave", window, cx);
7208 assert_eq!(
7209 editor.text(cx),
7210 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7211 );
7212
7213 editor.handle_input(":", window, cx);
7214 assert_eq!(
7215 editor.text(cx),
7216 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7217 );
7218 });
7219}
7220
7221#[gpui::test]
7222async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7223 init_test(cx, |_| {});
7224
7225 let (text, insertion_ranges) = marked_text_ranges(
7226 indoc! {"
7227 ˇ
7228 "},
7229 false,
7230 );
7231
7232 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7233 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7234
7235 _ = editor.update_in(cx, |editor, window, cx| {
7236 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7237
7238 editor
7239 .insert_snippet(&insertion_ranges, snippet, window, cx)
7240 .unwrap();
7241
7242 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7243 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7244 assert_eq!(editor.text(cx), expected_text);
7245 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7246 }
7247
7248 assert(
7249 editor,
7250 cx,
7251 indoc! {"
7252 type «» =•
7253 "},
7254 );
7255
7256 assert!(editor.context_menu_visible(), "There should be a matches");
7257 });
7258}
7259
7260#[gpui::test]
7261async fn test_snippets(cx: &mut TestAppContext) {
7262 init_test(cx, |_| {});
7263
7264 let (text, insertion_ranges) = marked_text_ranges(
7265 indoc! {"
7266 a.ˇ b
7267 a.ˇ b
7268 a.ˇ b
7269 "},
7270 false,
7271 );
7272
7273 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7274 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7275
7276 editor.update_in(cx, |editor, window, cx| {
7277 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7278
7279 editor
7280 .insert_snippet(&insertion_ranges, snippet, window, cx)
7281 .unwrap();
7282
7283 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7284 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7285 assert_eq!(editor.text(cx), expected_text);
7286 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7287 }
7288
7289 assert(
7290 editor,
7291 cx,
7292 indoc! {"
7293 a.f(«one», two, «three») b
7294 a.f(«one», two, «three») b
7295 a.f(«one», two, «three») b
7296 "},
7297 );
7298
7299 // Can't move earlier than the first tab stop
7300 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7301 assert(
7302 editor,
7303 cx,
7304 indoc! {"
7305 a.f(«one», two, «three») b
7306 a.f(«one», two, «three») b
7307 a.f(«one», two, «three») b
7308 "},
7309 );
7310
7311 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7312 assert(
7313 editor,
7314 cx,
7315 indoc! {"
7316 a.f(one, «two», three) b
7317 a.f(one, «two», three) b
7318 a.f(one, «two», three) b
7319 "},
7320 );
7321
7322 editor.move_to_prev_snippet_tabstop(window, cx);
7323 assert(
7324 editor,
7325 cx,
7326 indoc! {"
7327 a.f(«one», two, «three») b
7328 a.f(«one», two, «three») b
7329 a.f(«one», two, «three») b
7330 "},
7331 );
7332
7333 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7334 assert(
7335 editor,
7336 cx,
7337 indoc! {"
7338 a.f(one, «two», three) b
7339 a.f(one, «two», three) b
7340 a.f(one, «two», three) b
7341 "},
7342 );
7343 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7344 assert(
7345 editor,
7346 cx,
7347 indoc! {"
7348 a.f(one, two, three)ˇ b
7349 a.f(one, two, three)ˇ b
7350 a.f(one, two, three)ˇ b
7351 "},
7352 );
7353
7354 // As soon as the last tab stop is reached, snippet state is gone
7355 editor.move_to_prev_snippet_tabstop(window, cx);
7356 assert(
7357 editor,
7358 cx,
7359 indoc! {"
7360 a.f(one, two, three)ˇ b
7361 a.f(one, two, three)ˇ b
7362 a.f(one, two, three)ˇ b
7363 "},
7364 );
7365 });
7366}
7367
7368#[gpui::test]
7369async fn test_document_format_during_save(cx: &mut TestAppContext) {
7370 init_test(cx, |_| {});
7371
7372 let fs = FakeFs::new(cx.executor());
7373 fs.insert_file(path!("/file.rs"), Default::default()).await;
7374
7375 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7376
7377 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7378 language_registry.add(rust_lang());
7379 let mut fake_servers = language_registry.register_fake_lsp(
7380 "Rust",
7381 FakeLspAdapter {
7382 capabilities: lsp::ServerCapabilities {
7383 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7384 ..Default::default()
7385 },
7386 ..Default::default()
7387 },
7388 );
7389
7390 let buffer = project
7391 .update(cx, |project, cx| {
7392 project.open_local_buffer(path!("/file.rs"), cx)
7393 })
7394 .await
7395 .unwrap();
7396
7397 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7398 let (editor, cx) = cx.add_window_view(|window, cx| {
7399 build_editor_with_project(project.clone(), buffer, window, cx)
7400 });
7401 editor.update_in(cx, |editor, window, cx| {
7402 editor.set_text("one\ntwo\nthree\n", window, cx)
7403 });
7404 assert!(cx.read(|cx| editor.is_dirty(cx)));
7405
7406 cx.executor().start_waiting();
7407 let fake_server = fake_servers.next().await.unwrap();
7408
7409 let save = editor
7410 .update_in(cx, |editor, window, cx| {
7411 editor.save(true, project.clone(), window, cx)
7412 })
7413 .unwrap();
7414 fake_server
7415 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7416 assert_eq!(
7417 params.text_document.uri,
7418 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7419 );
7420 assert_eq!(params.options.tab_size, 4);
7421 Ok(Some(vec![lsp::TextEdit::new(
7422 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7423 ", ".to_string(),
7424 )]))
7425 })
7426 .next()
7427 .await;
7428 cx.executor().start_waiting();
7429 save.await;
7430
7431 assert_eq!(
7432 editor.update(cx, |editor, cx| editor.text(cx)),
7433 "one, two\nthree\n"
7434 );
7435 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7436
7437 editor.update_in(cx, |editor, window, cx| {
7438 editor.set_text("one\ntwo\nthree\n", window, cx)
7439 });
7440 assert!(cx.read(|cx| editor.is_dirty(cx)));
7441
7442 // Ensure we can still save even if formatting hangs.
7443 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7444 assert_eq!(
7445 params.text_document.uri,
7446 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7447 );
7448 futures::future::pending::<()>().await;
7449 unreachable!()
7450 });
7451 let save = editor
7452 .update_in(cx, |editor, window, cx| {
7453 editor.save(true, project.clone(), window, cx)
7454 })
7455 .unwrap();
7456 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7457 cx.executor().start_waiting();
7458 save.await;
7459 assert_eq!(
7460 editor.update(cx, |editor, cx| editor.text(cx)),
7461 "one\ntwo\nthree\n"
7462 );
7463 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7464
7465 // For non-dirty buffer, no formatting request should be sent
7466 let save = editor
7467 .update_in(cx, |editor, window, cx| {
7468 editor.save(true, project.clone(), window, cx)
7469 })
7470 .unwrap();
7471 let _pending_format_request = fake_server
7472 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7473 panic!("Should not be invoked on non-dirty buffer");
7474 })
7475 .next();
7476 cx.executor().start_waiting();
7477 save.await;
7478
7479 // Set rust language override and assert overridden tabsize is sent to language server
7480 update_test_language_settings(cx, |settings| {
7481 settings.languages.insert(
7482 "Rust".into(),
7483 LanguageSettingsContent {
7484 tab_size: NonZeroU32::new(8),
7485 ..Default::default()
7486 },
7487 );
7488 });
7489
7490 editor.update_in(cx, |editor, window, cx| {
7491 editor.set_text("somehting_new\n", window, cx)
7492 });
7493 assert!(cx.read(|cx| editor.is_dirty(cx)));
7494 let save = editor
7495 .update_in(cx, |editor, window, cx| {
7496 editor.save(true, project.clone(), window, cx)
7497 })
7498 .unwrap();
7499 fake_server
7500 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7501 assert_eq!(
7502 params.text_document.uri,
7503 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7504 );
7505 assert_eq!(params.options.tab_size, 8);
7506 Ok(Some(vec![]))
7507 })
7508 .next()
7509 .await;
7510 cx.executor().start_waiting();
7511 save.await;
7512}
7513
7514#[gpui::test]
7515async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7516 init_test(cx, |_| {});
7517
7518 let cols = 4;
7519 let rows = 10;
7520 let sample_text_1 = sample_text(rows, cols, 'a');
7521 assert_eq!(
7522 sample_text_1,
7523 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7524 );
7525 let sample_text_2 = sample_text(rows, cols, 'l');
7526 assert_eq!(
7527 sample_text_2,
7528 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7529 );
7530 let sample_text_3 = sample_text(rows, cols, 'v');
7531 assert_eq!(
7532 sample_text_3,
7533 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7534 );
7535
7536 let fs = FakeFs::new(cx.executor());
7537 fs.insert_tree(
7538 path!("/a"),
7539 json!({
7540 "main.rs": sample_text_1,
7541 "other.rs": sample_text_2,
7542 "lib.rs": sample_text_3,
7543 }),
7544 )
7545 .await;
7546
7547 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7548 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7549 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7550
7551 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7552 language_registry.add(rust_lang());
7553 let mut fake_servers = language_registry.register_fake_lsp(
7554 "Rust",
7555 FakeLspAdapter {
7556 capabilities: lsp::ServerCapabilities {
7557 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7558 ..Default::default()
7559 },
7560 ..Default::default()
7561 },
7562 );
7563
7564 let worktree = project.update(cx, |project, cx| {
7565 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7566 assert_eq!(worktrees.len(), 1);
7567 worktrees.pop().unwrap()
7568 });
7569 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7570
7571 let buffer_1 = project
7572 .update(cx, |project, cx| {
7573 project.open_buffer((worktree_id, "main.rs"), cx)
7574 })
7575 .await
7576 .unwrap();
7577 let buffer_2 = project
7578 .update(cx, |project, cx| {
7579 project.open_buffer((worktree_id, "other.rs"), cx)
7580 })
7581 .await
7582 .unwrap();
7583 let buffer_3 = project
7584 .update(cx, |project, cx| {
7585 project.open_buffer((worktree_id, "lib.rs"), cx)
7586 })
7587 .await
7588 .unwrap();
7589
7590 let multi_buffer = cx.new(|cx| {
7591 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7592 multi_buffer.push_excerpts(
7593 buffer_1.clone(),
7594 [
7595 ExcerptRange {
7596 context: Point::new(0, 0)..Point::new(3, 0),
7597 primary: None,
7598 },
7599 ExcerptRange {
7600 context: Point::new(5, 0)..Point::new(7, 0),
7601 primary: None,
7602 },
7603 ExcerptRange {
7604 context: Point::new(9, 0)..Point::new(10, 4),
7605 primary: None,
7606 },
7607 ],
7608 cx,
7609 );
7610 multi_buffer.push_excerpts(
7611 buffer_2.clone(),
7612 [
7613 ExcerptRange {
7614 context: Point::new(0, 0)..Point::new(3, 0),
7615 primary: None,
7616 },
7617 ExcerptRange {
7618 context: Point::new(5, 0)..Point::new(7, 0),
7619 primary: None,
7620 },
7621 ExcerptRange {
7622 context: Point::new(9, 0)..Point::new(10, 4),
7623 primary: None,
7624 },
7625 ],
7626 cx,
7627 );
7628 multi_buffer.push_excerpts(
7629 buffer_3.clone(),
7630 [
7631 ExcerptRange {
7632 context: Point::new(0, 0)..Point::new(3, 0),
7633 primary: None,
7634 },
7635 ExcerptRange {
7636 context: Point::new(5, 0)..Point::new(7, 0),
7637 primary: None,
7638 },
7639 ExcerptRange {
7640 context: Point::new(9, 0)..Point::new(10, 4),
7641 primary: None,
7642 },
7643 ],
7644 cx,
7645 );
7646 multi_buffer
7647 });
7648 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7649 Editor::new(
7650 EditorMode::Full,
7651 multi_buffer,
7652 Some(project.clone()),
7653 true,
7654 window,
7655 cx,
7656 )
7657 });
7658
7659 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7660 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7661 s.select_ranges(Some(1..2))
7662 });
7663 editor.insert("|one|two|three|", window, cx);
7664 });
7665 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7666 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7667 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7668 s.select_ranges(Some(60..70))
7669 });
7670 editor.insert("|four|five|six|", window, cx);
7671 });
7672 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7673
7674 // First two buffers should be edited, but not the third one.
7675 assert_eq!(
7676 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7677 "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}",
7678 );
7679 buffer_1.update(cx, |buffer, _| {
7680 assert!(buffer.is_dirty());
7681 assert_eq!(
7682 buffer.text(),
7683 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7684 )
7685 });
7686 buffer_2.update(cx, |buffer, _| {
7687 assert!(buffer.is_dirty());
7688 assert_eq!(
7689 buffer.text(),
7690 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7691 )
7692 });
7693 buffer_3.update(cx, |buffer, _| {
7694 assert!(!buffer.is_dirty());
7695 assert_eq!(buffer.text(), sample_text_3,)
7696 });
7697 cx.executor().run_until_parked();
7698
7699 cx.executor().start_waiting();
7700 let save = multi_buffer_editor
7701 .update_in(cx, |editor, window, cx| {
7702 editor.save(true, project.clone(), window, cx)
7703 })
7704 .unwrap();
7705
7706 let fake_server = fake_servers.next().await.unwrap();
7707 fake_server
7708 .server
7709 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7710 Ok(Some(vec![lsp::TextEdit::new(
7711 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7712 format!("[{} formatted]", params.text_document.uri),
7713 )]))
7714 })
7715 .detach();
7716 save.await;
7717
7718 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7719 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7720 assert_eq!(
7721 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7722 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}"),
7723 );
7724 buffer_1.update(cx, |buffer, _| {
7725 assert!(!buffer.is_dirty());
7726 assert_eq!(
7727 buffer.text(),
7728 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7729 )
7730 });
7731 buffer_2.update(cx, |buffer, _| {
7732 assert!(!buffer.is_dirty());
7733 assert_eq!(
7734 buffer.text(),
7735 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7736 )
7737 });
7738 buffer_3.update(cx, |buffer, _| {
7739 assert!(!buffer.is_dirty());
7740 assert_eq!(buffer.text(), sample_text_3,)
7741 });
7742}
7743
7744#[gpui::test]
7745async fn test_range_format_during_save(cx: &mut TestAppContext) {
7746 init_test(cx, |_| {});
7747
7748 let fs = FakeFs::new(cx.executor());
7749 fs.insert_file(path!("/file.rs"), Default::default()).await;
7750
7751 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7752
7753 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7754 language_registry.add(rust_lang());
7755 let mut fake_servers = language_registry.register_fake_lsp(
7756 "Rust",
7757 FakeLspAdapter {
7758 capabilities: lsp::ServerCapabilities {
7759 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7760 ..Default::default()
7761 },
7762 ..Default::default()
7763 },
7764 );
7765
7766 let buffer = project
7767 .update(cx, |project, cx| {
7768 project.open_local_buffer(path!("/file.rs"), cx)
7769 })
7770 .await
7771 .unwrap();
7772
7773 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7774 let (editor, cx) = cx.add_window_view(|window, cx| {
7775 build_editor_with_project(project.clone(), buffer, window, cx)
7776 });
7777 editor.update_in(cx, |editor, window, cx| {
7778 editor.set_text("one\ntwo\nthree\n", window, cx)
7779 });
7780 assert!(cx.read(|cx| editor.is_dirty(cx)));
7781
7782 cx.executor().start_waiting();
7783 let fake_server = fake_servers.next().await.unwrap();
7784
7785 let save = editor
7786 .update_in(cx, |editor, window, cx| {
7787 editor.save(true, project.clone(), window, cx)
7788 })
7789 .unwrap();
7790 fake_server
7791 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7792 assert_eq!(
7793 params.text_document.uri,
7794 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7795 );
7796 assert_eq!(params.options.tab_size, 4);
7797 Ok(Some(vec![lsp::TextEdit::new(
7798 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7799 ", ".to_string(),
7800 )]))
7801 })
7802 .next()
7803 .await;
7804 cx.executor().start_waiting();
7805 save.await;
7806 assert_eq!(
7807 editor.update(cx, |editor, cx| editor.text(cx)),
7808 "one, two\nthree\n"
7809 );
7810 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7811
7812 editor.update_in(cx, |editor, window, cx| {
7813 editor.set_text("one\ntwo\nthree\n", window, cx)
7814 });
7815 assert!(cx.read(|cx| editor.is_dirty(cx)));
7816
7817 // Ensure we can still save even if formatting hangs.
7818 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7819 move |params, _| async move {
7820 assert_eq!(
7821 params.text_document.uri,
7822 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7823 );
7824 futures::future::pending::<()>().await;
7825 unreachable!()
7826 },
7827 );
7828 let save = editor
7829 .update_in(cx, |editor, window, cx| {
7830 editor.save(true, project.clone(), window, cx)
7831 })
7832 .unwrap();
7833 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7834 cx.executor().start_waiting();
7835 save.await;
7836 assert_eq!(
7837 editor.update(cx, |editor, cx| editor.text(cx)),
7838 "one\ntwo\nthree\n"
7839 );
7840 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7841
7842 // For non-dirty buffer, no formatting request should be sent
7843 let save = editor
7844 .update_in(cx, |editor, window, cx| {
7845 editor.save(true, project.clone(), window, cx)
7846 })
7847 .unwrap();
7848 let _pending_format_request = fake_server
7849 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7850 panic!("Should not be invoked on non-dirty buffer");
7851 })
7852 .next();
7853 cx.executor().start_waiting();
7854 save.await;
7855
7856 // Set Rust language override and assert overridden tabsize is sent to language server
7857 update_test_language_settings(cx, |settings| {
7858 settings.languages.insert(
7859 "Rust".into(),
7860 LanguageSettingsContent {
7861 tab_size: NonZeroU32::new(8),
7862 ..Default::default()
7863 },
7864 );
7865 });
7866
7867 editor.update_in(cx, |editor, window, cx| {
7868 editor.set_text("somehting_new\n", window, cx)
7869 });
7870 assert!(cx.read(|cx| editor.is_dirty(cx)));
7871 let save = editor
7872 .update_in(cx, |editor, window, cx| {
7873 editor.save(true, project.clone(), window, cx)
7874 })
7875 .unwrap();
7876 fake_server
7877 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7878 assert_eq!(
7879 params.text_document.uri,
7880 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7881 );
7882 assert_eq!(params.options.tab_size, 8);
7883 Ok(Some(vec![]))
7884 })
7885 .next()
7886 .await;
7887 cx.executor().start_waiting();
7888 save.await;
7889}
7890
7891#[gpui::test]
7892async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7893 init_test(cx, |settings| {
7894 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7895 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7896 ))
7897 });
7898
7899 let fs = FakeFs::new(cx.executor());
7900 fs.insert_file(path!("/file.rs"), Default::default()).await;
7901
7902 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7903
7904 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7905 language_registry.add(Arc::new(Language::new(
7906 LanguageConfig {
7907 name: "Rust".into(),
7908 matcher: LanguageMatcher {
7909 path_suffixes: vec!["rs".to_string()],
7910 ..Default::default()
7911 },
7912 ..LanguageConfig::default()
7913 },
7914 Some(tree_sitter_rust::LANGUAGE.into()),
7915 )));
7916 update_test_language_settings(cx, |settings| {
7917 // Enable Prettier formatting for the same buffer, and ensure
7918 // LSP is called instead of Prettier.
7919 settings.defaults.prettier = Some(PrettierSettings {
7920 allowed: true,
7921 ..PrettierSettings::default()
7922 });
7923 });
7924 let mut fake_servers = language_registry.register_fake_lsp(
7925 "Rust",
7926 FakeLspAdapter {
7927 capabilities: lsp::ServerCapabilities {
7928 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7929 ..Default::default()
7930 },
7931 ..Default::default()
7932 },
7933 );
7934
7935 let buffer = project
7936 .update(cx, |project, cx| {
7937 project.open_local_buffer(path!("/file.rs"), cx)
7938 })
7939 .await
7940 .unwrap();
7941
7942 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7943 let (editor, cx) = cx.add_window_view(|window, cx| {
7944 build_editor_with_project(project.clone(), buffer, window, cx)
7945 });
7946 editor.update_in(cx, |editor, window, cx| {
7947 editor.set_text("one\ntwo\nthree\n", window, cx)
7948 });
7949
7950 cx.executor().start_waiting();
7951 let fake_server = fake_servers.next().await.unwrap();
7952
7953 let format = editor
7954 .update_in(cx, |editor, window, cx| {
7955 editor.perform_format(
7956 project.clone(),
7957 FormatTrigger::Manual,
7958 FormatTarget::Buffers,
7959 window,
7960 cx,
7961 )
7962 })
7963 .unwrap();
7964 fake_server
7965 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7966 assert_eq!(
7967 params.text_document.uri,
7968 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7969 );
7970 assert_eq!(params.options.tab_size, 4);
7971 Ok(Some(vec![lsp::TextEdit::new(
7972 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7973 ", ".to_string(),
7974 )]))
7975 })
7976 .next()
7977 .await;
7978 cx.executor().start_waiting();
7979 format.await;
7980 assert_eq!(
7981 editor.update(cx, |editor, cx| editor.text(cx)),
7982 "one, two\nthree\n"
7983 );
7984
7985 editor.update_in(cx, |editor, window, cx| {
7986 editor.set_text("one\ntwo\nthree\n", window, cx)
7987 });
7988 // Ensure we don't lock if formatting hangs.
7989 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7990 assert_eq!(
7991 params.text_document.uri,
7992 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7993 );
7994 futures::future::pending::<()>().await;
7995 unreachable!()
7996 });
7997 let format = editor
7998 .update_in(cx, |editor, window, cx| {
7999 editor.perform_format(
8000 project,
8001 FormatTrigger::Manual,
8002 FormatTarget::Buffers,
8003 window,
8004 cx,
8005 )
8006 })
8007 .unwrap();
8008 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8009 cx.executor().start_waiting();
8010 format.await;
8011 assert_eq!(
8012 editor.update(cx, |editor, cx| editor.text(cx)),
8013 "one\ntwo\nthree\n"
8014 );
8015}
8016
8017#[gpui::test]
8018async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8019 init_test(cx, |settings| {
8020 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8021 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8022 ))
8023 });
8024
8025 let fs = FakeFs::new(cx.executor());
8026 fs.insert_file(path!("/file.ts"), Default::default()).await;
8027
8028 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8029
8030 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8031 language_registry.add(Arc::new(Language::new(
8032 LanguageConfig {
8033 name: "TypeScript".into(),
8034 matcher: LanguageMatcher {
8035 path_suffixes: vec!["ts".to_string()],
8036 ..Default::default()
8037 },
8038 ..LanguageConfig::default()
8039 },
8040 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8041 )));
8042 update_test_language_settings(cx, |settings| {
8043 settings.defaults.prettier = Some(PrettierSettings {
8044 allowed: true,
8045 ..PrettierSettings::default()
8046 });
8047 });
8048 let mut fake_servers = language_registry.register_fake_lsp(
8049 "TypeScript",
8050 FakeLspAdapter {
8051 capabilities: lsp::ServerCapabilities {
8052 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8053 ..Default::default()
8054 },
8055 ..Default::default()
8056 },
8057 );
8058
8059 let buffer = project
8060 .update(cx, |project, cx| {
8061 project.open_local_buffer(path!("/file.ts"), cx)
8062 })
8063 .await
8064 .unwrap();
8065
8066 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8067 let (editor, cx) = cx.add_window_view(|window, cx| {
8068 build_editor_with_project(project.clone(), buffer, window, cx)
8069 });
8070 editor.update_in(cx, |editor, window, cx| {
8071 editor.set_text(
8072 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8073 window,
8074 cx,
8075 )
8076 });
8077
8078 cx.executor().start_waiting();
8079 let fake_server = fake_servers.next().await.unwrap();
8080
8081 let format = editor
8082 .update_in(cx, |editor, window, cx| {
8083 editor.perform_code_action_kind(
8084 project.clone(),
8085 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8086 window,
8087 cx,
8088 )
8089 })
8090 .unwrap();
8091 fake_server
8092 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8093 assert_eq!(
8094 params.text_document.uri,
8095 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8096 );
8097 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8098 lsp::CodeAction {
8099 title: "Organize Imports".to_string(),
8100 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8101 edit: Some(lsp::WorkspaceEdit {
8102 changes: Some(
8103 [(
8104 params.text_document.uri.clone(),
8105 vec![lsp::TextEdit::new(
8106 lsp::Range::new(
8107 lsp::Position::new(1, 0),
8108 lsp::Position::new(2, 0),
8109 ),
8110 "".to_string(),
8111 )],
8112 )]
8113 .into_iter()
8114 .collect(),
8115 ),
8116 ..Default::default()
8117 }),
8118 ..Default::default()
8119 },
8120 )]))
8121 })
8122 .next()
8123 .await;
8124 cx.executor().start_waiting();
8125 format.await;
8126 assert_eq!(
8127 editor.update(cx, |editor, cx| editor.text(cx)),
8128 "import { a } from 'module';\n\nconst x = a;\n"
8129 );
8130
8131 editor.update_in(cx, |editor, window, cx| {
8132 editor.set_text(
8133 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8134 window,
8135 cx,
8136 )
8137 });
8138 // Ensure we don't lock if code action hangs.
8139 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8140 move |params, _| async move {
8141 assert_eq!(
8142 params.text_document.uri,
8143 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8144 );
8145 futures::future::pending::<()>().await;
8146 unreachable!()
8147 },
8148 );
8149 let format = editor
8150 .update_in(cx, |editor, window, cx| {
8151 editor.perform_code_action_kind(
8152 project,
8153 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8154 window,
8155 cx,
8156 )
8157 })
8158 .unwrap();
8159 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8160 cx.executor().start_waiting();
8161 format.await;
8162 assert_eq!(
8163 editor.update(cx, |editor, cx| editor.text(cx)),
8164 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8165 );
8166}
8167
8168#[gpui::test]
8169async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8170 init_test(cx, |_| {});
8171
8172 let mut cx = EditorLspTestContext::new_rust(
8173 lsp::ServerCapabilities {
8174 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8175 ..Default::default()
8176 },
8177 cx,
8178 )
8179 .await;
8180
8181 cx.set_state(indoc! {"
8182 one.twoˇ
8183 "});
8184
8185 // The format request takes a long time. When it completes, it inserts
8186 // a newline and an indent before the `.`
8187 cx.lsp
8188 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8189 let executor = cx.background_executor().clone();
8190 async move {
8191 executor.timer(Duration::from_millis(100)).await;
8192 Ok(Some(vec![lsp::TextEdit {
8193 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8194 new_text: "\n ".into(),
8195 }]))
8196 }
8197 });
8198
8199 // Submit a format request.
8200 let format_1 = cx
8201 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8202 .unwrap();
8203 cx.executor().run_until_parked();
8204
8205 // Submit a second format request.
8206 let format_2 = cx
8207 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8208 .unwrap();
8209 cx.executor().run_until_parked();
8210
8211 // Wait for both format requests to complete
8212 cx.executor().advance_clock(Duration::from_millis(200));
8213 cx.executor().start_waiting();
8214 format_1.await.unwrap();
8215 cx.executor().start_waiting();
8216 format_2.await.unwrap();
8217
8218 // The formatting edits only happens once.
8219 cx.assert_editor_state(indoc! {"
8220 one
8221 .twoˇ
8222 "});
8223}
8224
8225#[gpui::test]
8226async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8227 init_test(cx, |settings| {
8228 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8229 });
8230
8231 let mut cx = EditorLspTestContext::new_rust(
8232 lsp::ServerCapabilities {
8233 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8234 ..Default::default()
8235 },
8236 cx,
8237 )
8238 .await;
8239
8240 // Set up a buffer white some trailing whitespace and no trailing newline.
8241 cx.set_state(
8242 &[
8243 "one ", //
8244 "twoˇ", //
8245 "three ", //
8246 "four", //
8247 ]
8248 .join("\n"),
8249 );
8250
8251 // Submit a format request.
8252 let format = cx
8253 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8254 .unwrap();
8255
8256 // Record which buffer changes have been sent to the language server
8257 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8258 cx.lsp
8259 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8260 let buffer_changes = buffer_changes.clone();
8261 move |params, _| {
8262 buffer_changes.lock().extend(
8263 params
8264 .content_changes
8265 .into_iter()
8266 .map(|e| (e.range.unwrap(), e.text)),
8267 );
8268 }
8269 });
8270
8271 // Handle formatting requests to the language server.
8272 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8273 let buffer_changes = buffer_changes.clone();
8274 move |_, _| {
8275 // When formatting is requested, trailing whitespace has already been stripped,
8276 // and the trailing newline has already been added.
8277 assert_eq!(
8278 &buffer_changes.lock()[1..],
8279 &[
8280 (
8281 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8282 "".into()
8283 ),
8284 (
8285 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8286 "".into()
8287 ),
8288 (
8289 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8290 "\n".into()
8291 ),
8292 ]
8293 );
8294
8295 // Insert blank lines between each line of the buffer.
8296 async move {
8297 Ok(Some(vec![
8298 lsp::TextEdit {
8299 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8300 new_text: "\n".into(),
8301 },
8302 lsp::TextEdit {
8303 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8304 new_text: "\n".into(),
8305 },
8306 ]))
8307 }
8308 }
8309 });
8310
8311 // After formatting the buffer, the trailing whitespace is stripped,
8312 // a newline is appended, and the edits provided by the language server
8313 // have been applied.
8314 format.await.unwrap();
8315 cx.assert_editor_state(
8316 &[
8317 "one", //
8318 "", //
8319 "twoˇ", //
8320 "", //
8321 "three", //
8322 "four", //
8323 "", //
8324 ]
8325 .join("\n"),
8326 );
8327
8328 // Undoing the formatting undoes the trailing whitespace removal, the
8329 // trailing newline, and the LSP edits.
8330 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8331 cx.assert_editor_state(
8332 &[
8333 "one ", //
8334 "twoˇ", //
8335 "three ", //
8336 "four", //
8337 ]
8338 .join("\n"),
8339 );
8340}
8341
8342#[gpui::test]
8343async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8344 cx: &mut TestAppContext,
8345) {
8346 init_test(cx, |_| {});
8347
8348 cx.update(|cx| {
8349 cx.update_global::<SettingsStore, _>(|settings, cx| {
8350 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8351 settings.auto_signature_help = Some(true);
8352 });
8353 });
8354 });
8355
8356 let mut cx = EditorLspTestContext::new_rust(
8357 lsp::ServerCapabilities {
8358 signature_help_provider: Some(lsp::SignatureHelpOptions {
8359 ..Default::default()
8360 }),
8361 ..Default::default()
8362 },
8363 cx,
8364 )
8365 .await;
8366
8367 let language = Language::new(
8368 LanguageConfig {
8369 name: "Rust".into(),
8370 brackets: BracketPairConfig {
8371 pairs: vec![
8372 BracketPair {
8373 start: "{".to_string(),
8374 end: "}".to_string(),
8375 close: true,
8376 surround: true,
8377 newline: true,
8378 },
8379 BracketPair {
8380 start: "(".to_string(),
8381 end: ")".to_string(),
8382 close: true,
8383 surround: true,
8384 newline: true,
8385 },
8386 BracketPair {
8387 start: "/*".to_string(),
8388 end: " */".to_string(),
8389 close: true,
8390 surround: true,
8391 newline: true,
8392 },
8393 BracketPair {
8394 start: "[".to_string(),
8395 end: "]".to_string(),
8396 close: false,
8397 surround: false,
8398 newline: true,
8399 },
8400 BracketPair {
8401 start: "\"".to_string(),
8402 end: "\"".to_string(),
8403 close: true,
8404 surround: true,
8405 newline: false,
8406 },
8407 BracketPair {
8408 start: "<".to_string(),
8409 end: ">".to_string(),
8410 close: false,
8411 surround: true,
8412 newline: true,
8413 },
8414 ],
8415 ..Default::default()
8416 },
8417 autoclose_before: "})]".to_string(),
8418 ..Default::default()
8419 },
8420 Some(tree_sitter_rust::LANGUAGE.into()),
8421 );
8422 let language = Arc::new(language);
8423
8424 cx.language_registry().add(language.clone());
8425 cx.update_buffer(|buffer, cx| {
8426 buffer.set_language(Some(language), cx);
8427 });
8428
8429 cx.set_state(
8430 &r#"
8431 fn main() {
8432 sampleˇ
8433 }
8434 "#
8435 .unindent(),
8436 );
8437
8438 cx.update_editor(|editor, window, cx| {
8439 editor.handle_input("(", window, cx);
8440 });
8441 cx.assert_editor_state(
8442 &"
8443 fn main() {
8444 sample(ˇ)
8445 }
8446 "
8447 .unindent(),
8448 );
8449
8450 let mocked_response = lsp::SignatureHelp {
8451 signatures: vec![lsp::SignatureInformation {
8452 label: "fn sample(param1: u8, param2: u8)".to_string(),
8453 documentation: None,
8454 parameters: Some(vec![
8455 lsp::ParameterInformation {
8456 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8457 documentation: None,
8458 },
8459 lsp::ParameterInformation {
8460 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8461 documentation: None,
8462 },
8463 ]),
8464 active_parameter: None,
8465 }],
8466 active_signature: Some(0),
8467 active_parameter: Some(0),
8468 };
8469 handle_signature_help_request(&mut cx, mocked_response).await;
8470
8471 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8472 .await;
8473
8474 cx.editor(|editor, _, _| {
8475 let signature_help_state = editor.signature_help_state.popover().cloned();
8476 assert_eq!(
8477 signature_help_state.unwrap().label,
8478 "param1: u8, param2: u8"
8479 );
8480 });
8481}
8482
8483#[gpui::test]
8484async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8485 init_test(cx, |_| {});
8486
8487 cx.update(|cx| {
8488 cx.update_global::<SettingsStore, _>(|settings, cx| {
8489 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8490 settings.auto_signature_help = Some(false);
8491 settings.show_signature_help_after_edits = Some(false);
8492 });
8493 });
8494 });
8495
8496 let mut cx = EditorLspTestContext::new_rust(
8497 lsp::ServerCapabilities {
8498 signature_help_provider: Some(lsp::SignatureHelpOptions {
8499 ..Default::default()
8500 }),
8501 ..Default::default()
8502 },
8503 cx,
8504 )
8505 .await;
8506
8507 let language = Language::new(
8508 LanguageConfig {
8509 name: "Rust".into(),
8510 brackets: BracketPairConfig {
8511 pairs: vec![
8512 BracketPair {
8513 start: "{".to_string(),
8514 end: "}".to_string(),
8515 close: true,
8516 surround: true,
8517 newline: true,
8518 },
8519 BracketPair {
8520 start: "(".to_string(),
8521 end: ")".to_string(),
8522 close: true,
8523 surround: true,
8524 newline: true,
8525 },
8526 BracketPair {
8527 start: "/*".to_string(),
8528 end: " */".to_string(),
8529 close: true,
8530 surround: true,
8531 newline: true,
8532 },
8533 BracketPair {
8534 start: "[".to_string(),
8535 end: "]".to_string(),
8536 close: false,
8537 surround: false,
8538 newline: true,
8539 },
8540 BracketPair {
8541 start: "\"".to_string(),
8542 end: "\"".to_string(),
8543 close: true,
8544 surround: true,
8545 newline: false,
8546 },
8547 BracketPair {
8548 start: "<".to_string(),
8549 end: ">".to_string(),
8550 close: false,
8551 surround: true,
8552 newline: true,
8553 },
8554 ],
8555 ..Default::default()
8556 },
8557 autoclose_before: "})]".to_string(),
8558 ..Default::default()
8559 },
8560 Some(tree_sitter_rust::LANGUAGE.into()),
8561 );
8562 let language = Arc::new(language);
8563
8564 cx.language_registry().add(language.clone());
8565 cx.update_buffer(|buffer, cx| {
8566 buffer.set_language(Some(language), cx);
8567 });
8568
8569 // Ensure that signature_help is not called when no signature help is enabled.
8570 cx.set_state(
8571 &r#"
8572 fn main() {
8573 sampleˇ
8574 }
8575 "#
8576 .unindent(),
8577 );
8578 cx.update_editor(|editor, window, cx| {
8579 editor.handle_input("(", window, cx);
8580 });
8581 cx.assert_editor_state(
8582 &"
8583 fn main() {
8584 sample(ˇ)
8585 }
8586 "
8587 .unindent(),
8588 );
8589 cx.editor(|editor, _, _| {
8590 assert!(editor.signature_help_state.task().is_none());
8591 });
8592
8593 let mocked_response = lsp::SignatureHelp {
8594 signatures: vec![lsp::SignatureInformation {
8595 label: "fn sample(param1: u8, param2: u8)".to_string(),
8596 documentation: None,
8597 parameters: Some(vec![
8598 lsp::ParameterInformation {
8599 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8600 documentation: None,
8601 },
8602 lsp::ParameterInformation {
8603 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8604 documentation: None,
8605 },
8606 ]),
8607 active_parameter: None,
8608 }],
8609 active_signature: Some(0),
8610 active_parameter: Some(0),
8611 };
8612
8613 // Ensure that signature_help is called when enabled afte edits
8614 cx.update(|_, cx| {
8615 cx.update_global::<SettingsStore, _>(|settings, cx| {
8616 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8617 settings.auto_signature_help = Some(false);
8618 settings.show_signature_help_after_edits = Some(true);
8619 });
8620 });
8621 });
8622 cx.set_state(
8623 &r#"
8624 fn main() {
8625 sampleˇ
8626 }
8627 "#
8628 .unindent(),
8629 );
8630 cx.update_editor(|editor, window, cx| {
8631 editor.handle_input("(", window, cx);
8632 });
8633 cx.assert_editor_state(
8634 &"
8635 fn main() {
8636 sample(ˇ)
8637 }
8638 "
8639 .unindent(),
8640 );
8641 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8642 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8643 .await;
8644 cx.update_editor(|editor, _, _| {
8645 let signature_help_state = editor.signature_help_state.popover().cloned();
8646 assert!(signature_help_state.is_some());
8647 assert_eq!(
8648 signature_help_state.unwrap().label,
8649 "param1: u8, param2: u8"
8650 );
8651 editor.signature_help_state = SignatureHelpState::default();
8652 });
8653
8654 // Ensure that signature_help is called when auto signature help override is enabled
8655 cx.update(|_, cx| {
8656 cx.update_global::<SettingsStore, _>(|settings, cx| {
8657 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8658 settings.auto_signature_help = Some(true);
8659 settings.show_signature_help_after_edits = Some(false);
8660 });
8661 });
8662 });
8663 cx.set_state(
8664 &r#"
8665 fn main() {
8666 sampleˇ
8667 }
8668 "#
8669 .unindent(),
8670 );
8671 cx.update_editor(|editor, window, cx| {
8672 editor.handle_input("(", window, cx);
8673 });
8674 cx.assert_editor_state(
8675 &"
8676 fn main() {
8677 sample(ˇ)
8678 }
8679 "
8680 .unindent(),
8681 );
8682 handle_signature_help_request(&mut cx, mocked_response).await;
8683 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8684 .await;
8685 cx.editor(|editor, _, _| {
8686 let signature_help_state = editor.signature_help_state.popover().cloned();
8687 assert!(signature_help_state.is_some());
8688 assert_eq!(
8689 signature_help_state.unwrap().label,
8690 "param1: u8, param2: u8"
8691 );
8692 });
8693}
8694
8695#[gpui::test]
8696async fn test_signature_help(cx: &mut TestAppContext) {
8697 init_test(cx, |_| {});
8698 cx.update(|cx| {
8699 cx.update_global::<SettingsStore, _>(|settings, cx| {
8700 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8701 settings.auto_signature_help = Some(true);
8702 });
8703 });
8704 });
8705
8706 let mut cx = EditorLspTestContext::new_rust(
8707 lsp::ServerCapabilities {
8708 signature_help_provider: Some(lsp::SignatureHelpOptions {
8709 ..Default::default()
8710 }),
8711 ..Default::default()
8712 },
8713 cx,
8714 )
8715 .await;
8716
8717 // A test that directly calls `show_signature_help`
8718 cx.update_editor(|editor, window, cx| {
8719 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8720 });
8721
8722 let mocked_response = lsp::SignatureHelp {
8723 signatures: vec![lsp::SignatureInformation {
8724 label: "fn sample(param1: u8, param2: u8)".to_string(),
8725 documentation: None,
8726 parameters: Some(vec![
8727 lsp::ParameterInformation {
8728 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8729 documentation: None,
8730 },
8731 lsp::ParameterInformation {
8732 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8733 documentation: None,
8734 },
8735 ]),
8736 active_parameter: None,
8737 }],
8738 active_signature: Some(0),
8739 active_parameter: Some(0),
8740 };
8741 handle_signature_help_request(&mut cx, mocked_response).await;
8742
8743 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8744 .await;
8745
8746 cx.editor(|editor, _, _| {
8747 let signature_help_state = editor.signature_help_state.popover().cloned();
8748 assert!(signature_help_state.is_some());
8749 assert_eq!(
8750 signature_help_state.unwrap().label,
8751 "param1: u8, param2: u8"
8752 );
8753 });
8754
8755 // When exiting outside from inside the brackets, `signature_help` is closed.
8756 cx.set_state(indoc! {"
8757 fn main() {
8758 sample(ˇ);
8759 }
8760
8761 fn sample(param1: u8, param2: u8) {}
8762 "});
8763
8764 cx.update_editor(|editor, window, cx| {
8765 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8766 });
8767
8768 let mocked_response = lsp::SignatureHelp {
8769 signatures: Vec::new(),
8770 active_signature: None,
8771 active_parameter: None,
8772 };
8773 handle_signature_help_request(&mut cx, mocked_response).await;
8774
8775 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8776 .await;
8777
8778 cx.editor(|editor, _, _| {
8779 assert!(!editor.signature_help_state.is_shown());
8780 });
8781
8782 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8783 cx.set_state(indoc! {"
8784 fn main() {
8785 sample(ˇ);
8786 }
8787
8788 fn sample(param1: u8, param2: u8) {}
8789 "});
8790
8791 let mocked_response = lsp::SignatureHelp {
8792 signatures: vec![lsp::SignatureInformation {
8793 label: "fn sample(param1: u8, param2: u8)".to_string(),
8794 documentation: None,
8795 parameters: Some(vec![
8796 lsp::ParameterInformation {
8797 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8798 documentation: None,
8799 },
8800 lsp::ParameterInformation {
8801 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8802 documentation: None,
8803 },
8804 ]),
8805 active_parameter: None,
8806 }],
8807 active_signature: Some(0),
8808 active_parameter: Some(0),
8809 };
8810 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8811 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8812 .await;
8813 cx.editor(|editor, _, _| {
8814 assert!(editor.signature_help_state.is_shown());
8815 });
8816
8817 // Restore the popover with more parameter input
8818 cx.set_state(indoc! {"
8819 fn main() {
8820 sample(param1, param2ˇ);
8821 }
8822
8823 fn sample(param1: u8, param2: u8) {}
8824 "});
8825
8826 let mocked_response = lsp::SignatureHelp {
8827 signatures: vec![lsp::SignatureInformation {
8828 label: "fn sample(param1: u8, param2: u8)".to_string(),
8829 documentation: None,
8830 parameters: Some(vec![
8831 lsp::ParameterInformation {
8832 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8833 documentation: None,
8834 },
8835 lsp::ParameterInformation {
8836 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8837 documentation: None,
8838 },
8839 ]),
8840 active_parameter: None,
8841 }],
8842 active_signature: Some(0),
8843 active_parameter: Some(1),
8844 };
8845 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8846 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8847 .await;
8848
8849 // When selecting a range, the popover is gone.
8850 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8851 cx.update_editor(|editor, window, cx| {
8852 editor.change_selections(None, window, cx, |s| {
8853 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8854 })
8855 });
8856 cx.assert_editor_state(indoc! {"
8857 fn main() {
8858 sample(param1, «ˇparam2»);
8859 }
8860
8861 fn sample(param1: u8, param2: u8) {}
8862 "});
8863 cx.editor(|editor, _, _| {
8864 assert!(!editor.signature_help_state.is_shown());
8865 });
8866
8867 // When unselecting again, the popover is back if within the brackets.
8868 cx.update_editor(|editor, window, cx| {
8869 editor.change_selections(None, window, cx, |s| {
8870 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8871 })
8872 });
8873 cx.assert_editor_state(indoc! {"
8874 fn main() {
8875 sample(param1, ˇparam2);
8876 }
8877
8878 fn sample(param1: u8, param2: u8) {}
8879 "});
8880 handle_signature_help_request(&mut cx, mocked_response).await;
8881 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8882 .await;
8883 cx.editor(|editor, _, _| {
8884 assert!(editor.signature_help_state.is_shown());
8885 });
8886
8887 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8888 cx.update_editor(|editor, window, cx| {
8889 editor.change_selections(None, window, cx, |s| {
8890 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8891 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8892 })
8893 });
8894 cx.assert_editor_state(indoc! {"
8895 fn main() {
8896 sample(param1, ˇparam2);
8897 }
8898
8899 fn sample(param1: u8, param2: u8) {}
8900 "});
8901
8902 let mocked_response = lsp::SignatureHelp {
8903 signatures: vec![lsp::SignatureInformation {
8904 label: "fn sample(param1: u8, param2: u8)".to_string(),
8905 documentation: None,
8906 parameters: Some(vec![
8907 lsp::ParameterInformation {
8908 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8909 documentation: None,
8910 },
8911 lsp::ParameterInformation {
8912 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8913 documentation: None,
8914 },
8915 ]),
8916 active_parameter: None,
8917 }],
8918 active_signature: Some(0),
8919 active_parameter: Some(1),
8920 };
8921 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8922 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8923 .await;
8924 cx.update_editor(|editor, _, cx| {
8925 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8926 });
8927 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8928 .await;
8929 cx.update_editor(|editor, window, cx| {
8930 editor.change_selections(None, window, cx, |s| {
8931 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8932 })
8933 });
8934 cx.assert_editor_state(indoc! {"
8935 fn main() {
8936 sample(param1, «ˇparam2»);
8937 }
8938
8939 fn sample(param1: u8, param2: u8) {}
8940 "});
8941 cx.update_editor(|editor, window, cx| {
8942 editor.change_selections(None, window, cx, |s| {
8943 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8944 })
8945 });
8946 cx.assert_editor_state(indoc! {"
8947 fn main() {
8948 sample(param1, ˇparam2);
8949 }
8950
8951 fn sample(param1: u8, param2: u8) {}
8952 "});
8953 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8954 .await;
8955}
8956
8957#[gpui::test]
8958async fn test_completion(cx: &mut TestAppContext) {
8959 init_test(cx, |_| {});
8960
8961 let mut cx = EditorLspTestContext::new_rust(
8962 lsp::ServerCapabilities {
8963 completion_provider: Some(lsp::CompletionOptions {
8964 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8965 resolve_provider: Some(true),
8966 ..Default::default()
8967 }),
8968 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8969 ..Default::default()
8970 },
8971 cx,
8972 )
8973 .await;
8974 let counter = Arc::new(AtomicUsize::new(0));
8975
8976 cx.set_state(indoc! {"
8977 oneˇ
8978 two
8979 three
8980 "});
8981 cx.simulate_keystroke(".");
8982 handle_completion_request(
8983 &mut cx,
8984 indoc! {"
8985 one.|<>
8986 two
8987 three
8988 "},
8989 vec!["first_completion", "second_completion"],
8990 counter.clone(),
8991 )
8992 .await;
8993 cx.condition(|editor, _| editor.context_menu_visible())
8994 .await;
8995 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8996
8997 let _handler = handle_signature_help_request(
8998 &mut cx,
8999 lsp::SignatureHelp {
9000 signatures: vec![lsp::SignatureInformation {
9001 label: "test signature".to_string(),
9002 documentation: None,
9003 parameters: Some(vec![lsp::ParameterInformation {
9004 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9005 documentation: None,
9006 }]),
9007 active_parameter: None,
9008 }],
9009 active_signature: None,
9010 active_parameter: None,
9011 },
9012 );
9013 cx.update_editor(|editor, window, cx| {
9014 assert!(
9015 !editor.signature_help_state.is_shown(),
9016 "No signature help was called for"
9017 );
9018 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9019 });
9020 cx.run_until_parked();
9021 cx.update_editor(|editor, _, _| {
9022 assert!(
9023 !editor.signature_help_state.is_shown(),
9024 "No signature help should be shown when completions menu is open"
9025 );
9026 });
9027
9028 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9029 editor.context_menu_next(&Default::default(), window, cx);
9030 editor
9031 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9032 .unwrap()
9033 });
9034 cx.assert_editor_state(indoc! {"
9035 one.second_completionˇ
9036 two
9037 three
9038 "});
9039
9040 handle_resolve_completion_request(
9041 &mut cx,
9042 Some(vec![
9043 (
9044 //This overlaps with the primary completion edit which is
9045 //misbehavior from the LSP spec, test that we filter it out
9046 indoc! {"
9047 one.second_ˇcompletion
9048 two
9049 threeˇ
9050 "},
9051 "overlapping additional edit",
9052 ),
9053 (
9054 indoc! {"
9055 one.second_completion
9056 two
9057 threeˇ
9058 "},
9059 "\nadditional edit",
9060 ),
9061 ]),
9062 )
9063 .await;
9064 apply_additional_edits.await.unwrap();
9065 cx.assert_editor_state(indoc! {"
9066 one.second_completionˇ
9067 two
9068 three
9069 additional edit
9070 "});
9071
9072 cx.set_state(indoc! {"
9073 one.second_completion
9074 twoˇ
9075 threeˇ
9076 additional edit
9077 "});
9078 cx.simulate_keystroke(" ");
9079 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9080 cx.simulate_keystroke("s");
9081 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9082
9083 cx.assert_editor_state(indoc! {"
9084 one.second_completion
9085 two sˇ
9086 three sˇ
9087 additional edit
9088 "});
9089 handle_completion_request(
9090 &mut cx,
9091 indoc! {"
9092 one.second_completion
9093 two s
9094 three <s|>
9095 additional edit
9096 "},
9097 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9098 counter.clone(),
9099 )
9100 .await;
9101 cx.condition(|editor, _| editor.context_menu_visible())
9102 .await;
9103 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9104
9105 cx.simulate_keystroke("i");
9106
9107 handle_completion_request(
9108 &mut cx,
9109 indoc! {"
9110 one.second_completion
9111 two si
9112 three <si|>
9113 additional edit
9114 "},
9115 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9116 counter.clone(),
9117 )
9118 .await;
9119 cx.condition(|editor, _| editor.context_menu_visible())
9120 .await;
9121 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9122
9123 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9124 editor
9125 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9126 .unwrap()
9127 });
9128 cx.assert_editor_state(indoc! {"
9129 one.second_completion
9130 two sixth_completionˇ
9131 three sixth_completionˇ
9132 additional edit
9133 "});
9134
9135 apply_additional_edits.await.unwrap();
9136
9137 update_test_language_settings(&mut cx, |settings| {
9138 settings.defaults.show_completions_on_input = Some(false);
9139 });
9140 cx.set_state("editorˇ");
9141 cx.simulate_keystroke(".");
9142 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9143 cx.simulate_keystroke("c");
9144 cx.simulate_keystroke("l");
9145 cx.simulate_keystroke("o");
9146 cx.assert_editor_state("editor.cloˇ");
9147 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9148 cx.update_editor(|editor, window, cx| {
9149 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9150 });
9151 handle_completion_request(
9152 &mut cx,
9153 "editor.<clo|>",
9154 vec!["close", "clobber"],
9155 counter.clone(),
9156 )
9157 .await;
9158 cx.condition(|editor, _| editor.context_menu_visible())
9159 .await;
9160 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9161
9162 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9163 editor
9164 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9165 .unwrap()
9166 });
9167 cx.assert_editor_state("editor.closeˇ");
9168 handle_resolve_completion_request(&mut cx, None).await;
9169 apply_additional_edits.await.unwrap();
9170}
9171
9172#[gpui::test]
9173async fn test_multiline_completion(cx: &mut TestAppContext) {
9174 init_test(cx, |_| {});
9175
9176 let fs = FakeFs::new(cx.executor());
9177 fs.insert_tree(
9178 path!("/a"),
9179 json!({
9180 "main.ts": "a",
9181 }),
9182 )
9183 .await;
9184
9185 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9186 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9187 let typescript_language = Arc::new(Language::new(
9188 LanguageConfig {
9189 name: "TypeScript".into(),
9190 matcher: LanguageMatcher {
9191 path_suffixes: vec!["ts".to_string()],
9192 ..LanguageMatcher::default()
9193 },
9194 line_comments: vec!["// ".into()],
9195 ..LanguageConfig::default()
9196 },
9197 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9198 ));
9199 language_registry.add(typescript_language.clone());
9200 let mut fake_servers = language_registry.register_fake_lsp(
9201 "TypeScript",
9202 FakeLspAdapter {
9203 capabilities: lsp::ServerCapabilities {
9204 completion_provider: Some(lsp::CompletionOptions {
9205 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9206 ..lsp::CompletionOptions::default()
9207 }),
9208 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9209 ..lsp::ServerCapabilities::default()
9210 },
9211 // Emulate vtsls label generation
9212 label_for_completion: Some(Box::new(|item, _| {
9213 let text = if let Some(description) = item
9214 .label_details
9215 .as_ref()
9216 .and_then(|label_details| label_details.description.as_ref())
9217 {
9218 format!("{} {}", item.label, description)
9219 } else if let Some(detail) = &item.detail {
9220 format!("{} {}", item.label, detail)
9221 } else {
9222 item.label.clone()
9223 };
9224 let len = text.len();
9225 Some(language::CodeLabel {
9226 text,
9227 runs: Vec::new(),
9228 filter_range: 0..len,
9229 })
9230 })),
9231 ..FakeLspAdapter::default()
9232 },
9233 );
9234 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9235 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9236 let worktree_id = workspace
9237 .update(cx, |workspace, _window, cx| {
9238 workspace.project().update(cx, |project, cx| {
9239 project.worktrees(cx).next().unwrap().read(cx).id()
9240 })
9241 })
9242 .unwrap();
9243 let _buffer = project
9244 .update(cx, |project, cx| {
9245 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9246 })
9247 .await
9248 .unwrap();
9249 let editor = workspace
9250 .update(cx, |workspace, window, cx| {
9251 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9252 })
9253 .unwrap()
9254 .await
9255 .unwrap()
9256 .downcast::<Editor>()
9257 .unwrap();
9258 let fake_server = fake_servers.next().await.unwrap();
9259
9260 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9261 let multiline_label_2 = "a\nb\nc\n";
9262 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9263 let multiline_description = "d\ne\nf\n";
9264 let multiline_detail_2 = "g\nh\ni\n";
9265
9266 let mut completion_handle =
9267 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9268 Ok(Some(lsp::CompletionResponse::Array(vec![
9269 lsp::CompletionItem {
9270 label: multiline_label.to_string(),
9271 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9272 range: lsp::Range {
9273 start: lsp::Position {
9274 line: params.text_document_position.position.line,
9275 character: params.text_document_position.position.character,
9276 },
9277 end: lsp::Position {
9278 line: params.text_document_position.position.line,
9279 character: params.text_document_position.position.character,
9280 },
9281 },
9282 new_text: "new_text_1".to_string(),
9283 })),
9284 ..lsp::CompletionItem::default()
9285 },
9286 lsp::CompletionItem {
9287 label: "single line label 1".to_string(),
9288 detail: Some(multiline_detail.to_string()),
9289 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9290 range: lsp::Range {
9291 start: lsp::Position {
9292 line: params.text_document_position.position.line,
9293 character: params.text_document_position.position.character,
9294 },
9295 end: lsp::Position {
9296 line: params.text_document_position.position.line,
9297 character: params.text_document_position.position.character,
9298 },
9299 },
9300 new_text: "new_text_2".to_string(),
9301 })),
9302 ..lsp::CompletionItem::default()
9303 },
9304 lsp::CompletionItem {
9305 label: "single line label 2".to_string(),
9306 label_details: Some(lsp::CompletionItemLabelDetails {
9307 description: Some(multiline_description.to_string()),
9308 detail: None,
9309 }),
9310 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9311 range: lsp::Range {
9312 start: lsp::Position {
9313 line: params.text_document_position.position.line,
9314 character: params.text_document_position.position.character,
9315 },
9316 end: lsp::Position {
9317 line: params.text_document_position.position.line,
9318 character: params.text_document_position.position.character,
9319 },
9320 },
9321 new_text: "new_text_2".to_string(),
9322 })),
9323 ..lsp::CompletionItem::default()
9324 },
9325 lsp::CompletionItem {
9326 label: multiline_label_2.to_string(),
9327 detail: Some(multiline_detail_2.to_string()),
9328 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9329 range: lsp::Range {
9330 start: lsp::Position {
9331 line: params.text_document_position.position.line,
9332 character: params.text_document_position.position.character,
9333 },
9334 end: lsp::Position {
9335 line: params.text_document_position.position.line,
9336 character: params.text_document_position.position.character,
9337 },
9338 },
9339 new_text: "new_text_3".to_string(),
9340 })),
9341 ..lsp::CompletionItem::default()
9342 },
9343 lsp::CompletionItem {
9344 label: "Label with many spaces and \t but without newlines".to_string(),
9345 detail: Some(
9346 "Details with many spaces and \t but without newlines".to_string(),
9347 ),
9348 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9349 range: lsp::Range {
9350 start: lsp::Position {
9351 line: params.text_document_position.position.line,
9352 character: params.text_document_position.position.character,
9353 },
9354 end: lsp::Position {
9355 line: params.text_document_position.position.line,
9356 character: params.text_document_position.position.character,
9357 },
9358 },
9359 new_text: "new_text_4".to_string(),
9360 })),
9361 ..lsp::CompletionItem::default()
9362 },
9363 ])))
9364 });
9365
9366 editor.update_in(cx, |editor, window, cx| {
9367 cx.focus_self(window);
9368 editor.move_to_end(&MoveToEnd, window, cx);
9369 editor.handle_input(".", window, cx);
9370 });
9371 cx.run_until_parked();
9372 completion_handle.next().await.unwrap();
9373
9374 editor.update(cx, |editor, _| {
9375 assert!(editor.context_menu_visible());
9376 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9377 {
9378 let completion_labels = menu
9379 .completions
9380 .borrow()
9381 .iter()
9382 .map(|c| c.label.text.clone())
9383 .collect::<Vec<_>>();
9384 assert_eq!(
9385 completion_labels,
9386 &[
9387 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9388 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9389 "single line label 2 d e f ",
9390 "a b c g h i ",
9391 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9392 ],
9393 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9394 );
9395
9396 for completion in menu
9397 .completions
9398 .borrow()
9399 .iter() {
9400 assert_eq!(
9401 completion.label.filter_range,
9402 0..completion.label.text.len(),
9403 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9404 );
9405 }
9406
9407 } else {
9408 panic!("expected completion menu to be open");
9409 }
9410 });
9411}
9412
9413#[gpui::test]
9414async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9415 init_test(cx, |_| {});
9416 let mut cx = EditorLspTestContext::new_rust(
9417 lsp::ServerCapabilities {
9418 completion_provider: Some(lsp::CompletionOptions {
9419 trigger_characters: Some(vec![".".to_string()]),
9420 ..Default::default()
9421 }),
9422 ..Default::default()
9423 },
9424 cx,
9425 )
9426 .await;
9427 cx.lsp
9428 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9429 Ok(Some(lsp::CompletionResponse::Array(vec![
9430 lsp::CompletionItem {
9431 label: "first".into(),
9432 ..Default::default()
9433 },
9434 lsp::CompletionItem {
9435 label: "last".into(),
9436 ..Default::default()
9437 },
9438 ])))
9439 });
9440 cx.set_state("variableˇ");
9441 cx.simulate_keystroke(".");
9442 cx.executor().run_until_parked();
9443
9444 cx.update_editor(|editor, _, _| {
9445 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9446 {
9447 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9448 } else {
9449 panic!("expected completion menu to be open");
9450 }
9451 });
9452
9453 cx.update_editor(|editor, window, cx| {
9454 editor.move_page_down(&MovePageDown::default(), window, cx);
9455 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9456 {
9457 assert!(
9458 menu.selected_item == 1,
9459 "expected PageDown to select the last item from the context menu"
9460 );
9461 } else {
9462 panic!("expected completion menu to stay open after PageDown");
9463 }
9464 });
9465
9466 cx.update_editor(|editor, window, cx| {
9467 editor.move_page_up(&MovePageUp::default(), window, cx);
9468 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9469 {
9470 assert!(
9471 menu.selected_item == 0,
9472 "expected PageUp to select the first item from the context menu"
9473 );
9474 } else {
9475 panic!("expected completion menu to stay open after PageUp");
9476 }
9477 });
9478}
9479
9480#[gpui::test]
9481async fn test_completion_sort(cx: &mut TestAppContext) {
9482 init_test(cx, |_| {});
9483 let mut cx = EditorLspTestContext::new_rust(
9484 lsp::ServerCapabilities {
9485 completion_provider: Some(lsp::CompletionOptions {
9486 trigger_characters: Some(vec![".".to_string()]),
9487 ..Default::default()
9488 }),
9489 ..Default::default()
9490 },
9491 cx,
9492 )
9493 .await;
9494 cx.lsp
9495 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9496 Ok(Some(lsp::CompletionResponse::Array(vec![
9497 lsp::CompletionItem {
9498 label: "Range".into(),
9499 sort_text: Some("a".into()),
9500 ..Default::default()
9501 },
9502 lsp::CompletionItem {
9503 label: "r".into(),
9504 sort_text: Some("b".into()),
9505 ..Default::default()
9506 },
9507 lsp::CompletionItem {
9508 label: "ret".into(),
9509 sort_text: Some("c".into()),
9510 ..Default::default()
9511 },
9512 lsp::CompletionItem {
9513 label: "return".into(),
9514 sort_text: Some("d".into()),
9515 ..Default::default()
9516 },
9517 lsp::CompletionItem {
9518 label: "slice".into(),
9519 sort_text: Some("d".into()),
9520 ..Default::default()
9521 },
9522 ])))
9523 });
9524 cx.set_state("rˇ");
9525 cx.executor().run_until_parked();
9526 cx.update_editor(|editor, window, cx| {
9527 editor.show_completions(
9528 &ShowCompletions {
9529 trigger: Some("r".into()),
9530 },
9531 window,
9532 cx,
9533 );
9534 });
9535 cx.executor().run_until_parked();
9536
9537 cx.update_editor(|editor, _, _| {
9538 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9539 {
9540 assert_eq!(
9541 completion_menu_entries(&menu),
9542 &["r", "ret", "Range", "return"]
9543 );
9544 } else {
9545 panic!("expected completion menu to be open");
9546 }
9547 });
9548}
9549
9550#[gpui::test]
9551async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9552 init_test(cx, |_| {});
9553
9554 let mut cx = EditorLspTestContext::new_rust(
9555 lsp::ServerCapabilities {
9556 completion_provider: Some(lsp::CompletionOptions {
9557 trigger_characters: Some(vec![".".to_string()]),
9558 resolve_provider: Some(true),
9559 ..Default::default()
9560 }),
9561 ..Default::default()
9562 },
9563 cx,
9564 )
9565 .await;
9566
9567 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9568 cx.simulate_keystroke(".");
9569 let completion_item = lsp::CompletionItem {
9570 label: "Some".into(),
9571 kind: Some(lsp::CompletionItemKind::SNIPPET),
9572 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9573 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9574 kind: lsp::MarkupKind::Markdown,
9575 value: "```rust\nSome(2)\n```".to_string(),
9576 })),
9577 deprecated: Some(false),
9578 sort_text: Some("Some".to_string()),
9579 filter_text: Some("Some".to_string()),
9580 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9581 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9582 range: lsp::Range {
9583 start: lsp::Position {
9584 line: 0,
9585 character: 22,
9586 },
9587 end: lsp::Position {
9588 line: 0,
9589 character: 22,
9590 },
9591 },
9592 new_text: "Some(2)".to_string(),
9593 })),
9594 additional_text_edits: Some(vec![lsp::TextEdit {
9595 range: lsp::Range {
9596 start: lsp::Position {
9597 line: 0,
9598 character: 20,
9599 },
9600 end: lsp::Position {
9601 line: 0,
9602 character: 22,
9603 },
9604 },
9605 new_text: "".to_string(),
9606 }]),
9607 ..Default::default()
9608 };
9609
9610 let closure_completion_item = completion_item.clone();
9611 let counter = Arc::new(AtomicUsize::new(0));
9612 let counter_clone = counter.clone();
9613 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9614 let task_completion_item = closure_completion_item.clone();
9615 counter_clone.fetch_add(1, atomic::Ordering::Release);
9616 async move {
9617 Ok(Some(lsp::CompletionResponse::Array(vec![
9618 task_completion_item,
9619 ])))
9620 }
9621 });
9622
9623 cx.condition(|editor, _| editor.context_menu_visible())
9624 .await;
9625 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9626 assert!(request.next().await.is_some());
9627 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9628
9629 cx.simulate_keystroke("S");
9630 cx.simulate_keystroke("o");
9631 cx.simulate_keystroke("m");
9632 cx.condition(|editor, _| editor.context_menu_visible())
9633 .await;
9634 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9635 assert!(request.next().await.is_some());
9636 assert!(request.next().await.is_some());
9637 assert!(request.next().await.is_some());
9638 request.close();
9639 assert!(request.next().await.is_none());
9640 assert_eq!(
9641 counter.load(atomic::Ordering::Acquire),
9642 4,
9643 "With the completions menu open, only one LSP request should happen per input"
9644 );
9645}
9646
9647#[gpui::test]
9648async fn test_toggle_comment(cx: &mut TestAppContext) {
9649 init_test(cx, |_| {});
9650 let mut cx = EditorTestContext::new(cx).await;
9651 let language = Arc::new(Language::new(
9652 LanguageConfig {
9653 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9654 ..Default::default()
9655 },
9656 Some(tree_sitter_rust::LANGUAGE.into()),
9657 ));
9658 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9659
9660 // If multiple selections intersect a line, the line is only toggled once.
9661 cx.set_state(indoc! {"
9662 fn a() {
9663 «//b();
9664 ˇ»// «c();
9665 //ˇ» d();
9666 }
9667 "});
9668
9669 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9670
9671 cx.assert_editor_state(indoc! {"
9672 fn a() {
9673 «b();
9674 c();
9675 ˇ» d();
9676 }
9677 "});
9678
9679 // The comment prefix is inserted at the same column for every line in a
9680 // selection.
9681 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9682
9683 cx.assert_editor_state(indoc! {"
9684 fn a() {
9685 // «b();
9686 // c();
9687 ˇ»// d();
9688 }
9689 "});
9690
9691 // If a selection ends at the beginning of a line, that line is not toggled.
9692 cx.set_selections_state(indoc! {"
9693 fn a() {
9694 // b();
9695 «// c();
9696 ˇ» // d();
9697 }
9698 "});
9699
9700 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9701
9702 cx.assert_editor_state(indoc! {"
9703 fn a() {
9704 // b();
9705 «c();
9706 ˇ» // d();
9707 }
9708 "});
9709
9710 // If a selection span a single line and is empty, the line is toggled.
9711 cx.set_state(indoc! {"
9712 fn a() {
9713 a();
9714 b();
9715 ˇ
9716 }
9717 "});
9718
9719 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9720
9721 cx.assert_editor_state(indoc! {"
9722 fn a() {
9723 a();
9724 b();
9725 //•ˇ
9726 }
9727 "});
9728
9729 // If a selection span multiple lines, empty lines are not toggled.
9730 cx.set_state(indoc! {"
9731 fn a() {
9732 «a();
9733
9734 c();ˇ»
9735 }
9736 "});
9737
9738 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9739
9740 cx.assert_editor_state(indoc! {"
9741 fn a() {
9742 // «a();
9743
9744 // c();ˇ»
9745 }
9746 "});
9747
9748 // If a selection includes multiple comment prefixes, all lines are uncommented.
9749 cx.set_state(indoc! {"
9750 fn a() {
9751 «// a();
9752 /// b();
9753 //! c();ˇ»
9754 }
9755 "});
9756
9757 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9758
9759 cx.assert_editor_state(indoc! {"
9760 fn a() {
9761 «a();
9762 b();
9763 c();ˇ»
9764 }
9765 "});
9766}
9767
9768#[gpui::test]
9769async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9770 init_test(cx, |_| {});
9771 let mut cx = EditorTestContext::new(cx).await;
9772 let language = Arc::new(Language::new(
9773 LanguageConfig {
9774 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9775 ..Default::default()
9776 },
9777 Some(tree_sitter_rust::LANGUAGE.into()),
9778 ));
9779 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9780
9781 let toggle_comments = &ToggleComments {
9782 advance_downwards: false,
9783 ignore_indent: true,
9784 };
9785
9786 // If multiple selections intersect a line, the line is only toggled once.
9787 cx.set_state(indoc! {"
9788 fn a() {
9789 // «b();
9790 // c();
9791 // ˇ» d();
9792 }
9793 "});
9794
9795 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9796
9797 cx.assert_editor_state(indoc! {"
9798 fn a() {
9799 «b();
9800 c();
9801 ˇ» d();
9802 }
9803 "});
9804
9805 // The comment prefix is inserted at the beginning of each line
9806 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9807
9808 cx.assert_editor_state(indoc! {"
9809 fn a() {
9810 // «b();
9811 // c();
9812 // ˇ» d();
9813 }
9814 "});
9815
9816 // If a selection ends at the beginning of a line, that line is not toggled.
9817 cx.set_selections_state(indoc! {"
9818 fn a() {
9819 // b();
9820 // «c();
9821 ˇ»// d();
9822 }
9823 "});
9824
9825 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9826
9827 cx.assert_editor_state(indoc! {"
9828 fn a() {
9829 // b();
9830 «c();
9831 ˇ»// d();
9832 }
9833 "});
9834
9835 // If a selection span a single line and is empty, the line is toggled.
9836 cx.set_state(indoc! {"
9837 fn a() {
9838 a();
9839 b();
9840 ˇ
9841 }
9842 "});
9843
9844 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9845
9846 cx.assert_editor_state(indoc! {"
9847 fn a() {
9848 a();
9849 b();
9850 //ˇ
9851 }
9852 "});
9853
9854 // If a selection span multiple lines, empty lines are not toggled.
9855 cx.set_state(indoc! {"
9856 fn a() {
9857 «a();
9858
9859 c();ˇ»
9860 }
9861 "});
9862
9863 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9864
9865 cx.assert_editor_state(indoc! {"
9866 fn a() {
9867 // «a();
9868
9869 // c();ˇ»
9870 }
9871 "});
9872
9873 // If a selection includes multiple comment prefixes, all lines are uncommented.
9874 cx.set_state(indoc! {"
9875 fn a() {
9876 // «a();
9877 /// b();
9878 //! c();ˇ»
9879 }
9880 "});
9881
9882 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9883
9884 cx.assert_editor_state(indoc! {"
9885 fn a() {
9886 «a();
9887 b();
9888 c();ˇ»
9889 }
9890 "});
9891}
9892
9893#[gpui::test]
9894async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
9895 init_test(cx, |_| {});
9896
9897 let language = Arc::new(Language::new(
9898 LanguageConfig {
9899 line_comments: vec!["// ".into()],
9900 ..Default::default()
9901 },
9902 Some(tree_sitter_rust::LANGUAGE.into()),
9903 ));
9904
9905 let mut cx = EditorTestContext::new(cx).await;
9906
9907 cx.language_registry().add(language.clone());
9908 cx.update_buffer(|buffer, cx| {
9909 buffer.set_language(Some(language), cx);
9910 });
9911
9912 let toggle_comments = &ToggleComments {
9913 advance_downwards: true,
9914 ignore_indent: false,
9915 };
9916
9917 // Single cursor on one line -> advance
9918 // Cursor moves horizontally 3 characters as well on non-blank line
9919 cx.set_state(indoc!(
9920 "fn a() {
9921 ˇdog();
9922 cat();
9923 }"
9924 ));
9925 cx.update_editor(|editor, window, cx| {
9926 editor.toggle_comments(toggle_comments, window, cx);
9927 });
9928 cx.assert_editor_state(indoc!(
9929 "fn a() {
9930 // dog();
9931 catˇ();
9932 }"
9933 ));
9934
9935 // Single selection on one line -> don't advance
9936 cx.set_state(indoc!(
9937 "fn a() {
9938 «dog()ˇ»;
9939 cat();
9940 }"
9941 ));
9942 cx.update_editor(|editor, window, cx| {
9943 editor.toggle_comments(toggle_comments, window, cx);
9944 });
9945 cx.assert_editor_state(indoc!(
9946 "fn a() {
9947 // «dog()ˇ»;
9948 cat();
9949 }"
9950 ));
9951
9952 // Multiple cursors on one line -> advance
9953 cx.set_state(indoc!(
9954 "fn a() {
9955 ˇdˇog();
9956 cat();
9957 }"
9958 ));
9959 cx.update_editor(|editor, window, cx| {
9960 editor.toggle_comments(toggle_comments, window, cx);
9961 });
9962 cx.assert_editor_state(indoc!(
9963 "fn a() {
9964 // dog();
9965 catˇ(ˇ);
9966 }"
9967 ));
9968
9969 // Multiple cursors on one line, with selection -> don't advance
9970 cx.set_state(indoc!(
9971 "fn a() {
9972 ˇdˇog«()ˇ»;
9973 cat();
9974 }"
9975 ));
9976 cx.update_editor(|editor, window, cx| {
9977 editor.toggle_comments(toggle_comments, window, cx);
9978 });
9979 cx.assert_editor_state(indoc!(
9980 "fn a() {
9981 // ˇdˇog«()ˇ»;
9982 cat();
9983 }"
9984 ));
9985
9986 // Single cursor on one line -> advance
9987 // Cursor moves to column 0 on blank line
9988 cx.set_state(indoc!(
9989 "fn a() {
9990 ˇdog();
9991
9992 cat();
9993 }"
9994 ));
9995 cx.update_editor(|editor, window, cx| {
9996 editor.toggle_comments(toggle_comments, window, cx);
9997 });
9998 cx.assert_editor_state(indoc!(
9999 "fn a() {
10000 // dog();
10001 ˇ
10002 cat();
10003 }"
10004 ));
10005
10006 // Single cursor on one line -> advance
10007 // Cursor starts and ends at column 0
10008 cx.set_state(indoc!(
10009 "fn a() {
10010 ˇ dog();
10011 cat();
10012 }"
10013 ));
10014 cx.update_editor(|editor, window, cx| {
10015 editor.toggle_comments(toggle_comments, window, cx);
10016 });
10017 cx.assert_editor_state(indoc!(
10018 "fn a() {
10019 // dog();
10020 ˇ cat();
10021 }"
10022 ));
10023}
10024
10025#[gpui::test]
10026async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10027 init_test(cx, |_| {});
10028
10029 let mut cx = EditorTestContext::new(cx).await;
10030
10031 let html_language = Arc::new(
10032 Language::new(
10033 LanguageConfig {
10034 name: "HTML".into(),
10035 block_comment: Some(("<!-- ".into(), " -->".into())),
10036 ..Default::default()
10037 },
10038 Some(tree_sitter_html::LANGUAGE.into()),
10039 )
10040 .with_injection_query(
10041 r#"
10042 (script_element
10043 (raw_text) @injection.content
10044 (#set! injection.language "javascript"))
10045 "#,
10046 )
10047 .unwrap(),
10048 );
10049
10050 let javascript_language = Arc::new(Language::new(
10051 LanguageConfig {
10052 name: "JavaScript".into(),
10053 line_comments: vec!["// ".into()],
10054 ..Default::default()
10055 },
10056 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10057 ));
10058
10059 cx.language_registry().add(html_language.clone());
10060 cx.language_registry().add(javascript_language.clone());
10061 cx.update_buffer(|buffer, cx| {
10062 buffer.set_language(Some(html_language), cx);
10063 });
10064
10065 // Toggle comments for empty selections
10066 cx.set_state(
10067 &r#"
10068 <p>A</p>ˇ
10069 <p>B</p>ˇ
10070 <p>C</p>ˇ
10071 "#
10072 .unindent(),
10073 );
10074 cx.update_editor(|editor, window, cx| {
10075 editor.toggle_comments(&ToggleComments::default(), window, cx)
10076 });
10077 cx.assert_editor_state(
10078 &r#"
10079 <!-- <p>A</p>ˇ -->
10080 <!-- <p>B</p>ˇ -->
10081 <!-- <p>C</p>ˇ -->
10082 "#
10083 .unindent(),
10084 );
10085 cx.update_editor(|editor, window, cx| {
10086 editor.toggle_comments(&ToggleComments::default(), window, cx)
10087 });
10088 cx.assert_editor_state(
10089 &r#"
10090 <p>A</p>ˇ
10091 <p>B</p>ˇ
10092 <p>C</p>ˇ
10093 "#
10094 .unindent(),
10095 );
10096
10097 // Toggle comments for mixture of empty and non-empty selections, where
10098 // multiple selections occupy a given line.
10099 cx.set_state(
10100 &r#"
10101 <p>A«</p>
10102 <p>ˇ»B</p>ˇ
10103 <p>C«</p>
10104 <p>ˇ»D</p>ˇ
10105 "#
10106 .unindent(),
10107 );
10108
10109 cx.update_editor(|editor, window, cx| {
10110 editor.toggle_comments(&ToggleComments::default(), window, cx)
10111 });
10112 cx.assert_editor_state(
10113 &r#"
10114 <!-- <p>A«</p>
10115 <p>ˇ»B</p>ˇ -->
10116 <!-- <p>C«</p>
10117 <p>ˇ»D</p>ˇ -->
10118 "#
10119 .unindent(),
10120 );
10121 cx.update_editor(|editor, window, cx| {
10122 editor.toggle_comments(&ToggleComments::default(), window, cx)
10123 });
10124 cx.assert_editor_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 // Toggle comments when different languages are active for different
10135 // selections.
10136 cx.set_state(
10137 &r#"
10138 ˇ<script>
10139 ˇvar x = new Y();
10140 ˇ</script>
10141 "#
10142 .unindent(),
10143 );
10144 cx.executor().run_until_parked();
10145 cx.update_editor(|editor, window, cx| {
10146 editor.toggle_comments(&ToggleComments::default(), window, cx)
10147 });
10148 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10149 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10150 cx.assert_editor_state(
10151 &r#"
10152 <!-- ˇ<script> -->
10153 // ˇvar x = new Y();
10154 <!-- ˇ</script> -->
10155 "#
10156 .unindent(),
10157 );
10158}
10159
10160#[gpui::test]
10161fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10162 init_test(cx, |_| {});
10163
10164 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10165 let multibuffer = cx.new(|cx| {
10166 let mut multibuffer = MultiBuffer::new(ReadWrite);
10167 multibuffer.push_excerpts(
10168 buffer.clone(),
10169 [
10170 ExcerptRange {
10171 context: Point::new(0, 0)..Point::new(0, 4),
10172 primary: None,
10173 },
10174 ExcerptRange {
10175 context: Point::new(1, 0)..Point::new(1, 4),
10176 primary: None,
10177 },
10178 ],
10179 cx,
10180 );
10181 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10182 multibuffer
10183 });
10184
10185 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10186 editor.update_in(cx, |editor, window, cx| {
10187 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10188 editor.change_selections(None, window, cx, |s| {
10189 s.select_ranges([
10190 Point::new(0, 0)..Point::new(0, 0),
10191 Point::new(1, 0)..Point::new(1, 0),
10192 ])
10193 });
10194
10195 editor.handle_input("X", window, cx);
10196 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10197 assert_eq!(
10198 editor.selections.ranges(cx),
10199 [
10200 Point::new(0, 1)..Point::new(0, 1),
10201 Point::new(1, 1)..Point::new(1, 1),
10202 ]
10203 );
10204
10205 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10206 editor.change_selections(None, window, cx, |s| {
10207 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10208 });
10209 editor.backspace(&Default::default(), window, cx);
10210 assert_eq!(editor.text(cx), "Xa\nbbb");
10211 assert_eq!(
10212 editor.selections.ranges(cx),
10213 [Point::new(1, 0)..Point::new(1, 0)]
10214 );
10215
10216 editor.change_selections(None, window, cx, |s| {
10217 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10218 });
10219 editor.backspace(&Default::default(), window, cx);
10220 assert_eq!(editor.text(cx), "X\nbb");
10221 assert_eq!(
10222 editor.selections.ranges(cx),
10223 [Point::new(0, 1)..Point::new(0, 1)]
10224 );
10225 });
10226}
10227
10228#[gpui::test]
10229fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10230 init_test(cx, |_| {});
10231
10232 let markers = vec![('[', ']').into(), ('(', ')').into()];
10233 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10234 indoc! {"
10235 [aaaa
10236 (bbbb]
10237 cccc)",
10238 },
10239 markers.clone(),
10240 );
10241 let excerpt_ranges = markers.into_iter().map(|marker| {
10242 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10243 ExcerptRange {
10244 context,
10245 primary: None,
10246 }
10247 });
10248 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10249 let multibuffer = cx.new(|cx| {
10250 let mut multibuffer = MultiBuffer::new(ReadWrite);
10251 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10252 multibuffer
10253 });
10254
10255 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10256 editor.update_in(cx, |editor, window, cx| {
10257 let (expected_text, selection_ranges) = marked_text_ranges(
10258 indoc! {"
10259 aaaa
10260 bˇbbb
10261 bˇbbˇb
10262 cccc"
10263 },
10264 true,
10265 );
10266 assert_eq!(editor.text(cx), expected_text);
10267 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10268
10269 editor.handle_input("X", window, cx);
10270
10271 let (expected_text, expected_selections) = marked_text_ranges(
10272 indoc! {"
10273 aaaa
10274 bXˇbbXb
10275 bXˇbbXˇb
10276 cccc"
10277 },
10278 false,
10279 );
10280 assert_eq!(editor.text(cx), expected_text);
10281 assert_eq!(editor.selections.ranges(cx), expected_selections);
10282
10283 editor.newline(&Newline, window, cx);
10284 let (expected_text, expected_selections) = marked_text_ranges(
10285 indoc! {"
10286 aaaa
10287 bX
10288 ˇbbX
10289 b
10290 bX
10291 ˇbbX
10292 ˇb
10293 cccc"
10294 },
10295 false,
10296 );
10297 assert_eq!(editor.text(cx), expected_text);
10298 assert_eq!(editor.selections.ranges(cx), expected_selections);
10299 });
10300}
10301
10302#[gpui::test]
10303fn test_refresh_selections(cx: &mut TestAppContext) {
10304 init_test(cx, |_| {});
10305
10306 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10307 let mut excerpt1_id = None;
10308 let multibuffer = cx.new(|cx| {
10309 let mut multibuffer = MultiBuffer::new(ReadWrite);
10310 excerpt1_id = multibuffer
10311 .push_excerpts(
10312 buffer.clone(),
10313 [
10314 ExcerptRange {
10315 context: Point::new(0, 0)..Point::new(1, 4),
10316 primary: None,
10317 },
10318 ExcerptRange {
10319 context: Point::new(1, 0)..Point::new(2, 4),
10320 primary: None,
10321 },
10322 ],
10323 cx,
10324 )
10325 .into_iter()
10326 .next();
10327 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10328 multibuffer
10329 });
10330
10331 let editor = cx.add_window(|window, cx| {
10332 let mut editor = build_editor(multibuffer.clone(), window, cx);
10333 let snapshot = editor.snapshot(window, cx);
10334 editor.change_selections(None, window, cx, |s| {
10335 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10336 });
10337 editor.begin_selection(
10338 Point::new(2, 1).to_display_point(&snapshot),
10339 true,
10340 1,
10341 window,
10342 cx,
10343 );
10344 assert_eq!(
10345 editor.selections.ranges(cx),
10346 [
10347 Point::new(1, 3)..Point::new(1, 3),
10348 Point::new(2, 1)..Point::new(2, 1),
10349 ]
10350 );
10351 editor
10352 });
10353
10354 // Refreshing selections is a no-op when excerpts haven't changed.
10355 _ = editor.update(cx, |editor, window, cx| {
10356 editor.change_selections(None, window, cx, |s| s.refresh());
10357 assert_eq!(
10358 editor.selections.ranges(cx),
10359 [
10360 Point::new(1, 3)..Point::new(1, 3),
10361 Point::new(2, 1)..Point::new(2, 1),
10362 ]
10363 );
10364 });
10365
10366 multibuffer.update(cx, |multibuffer, cx| {
10367 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10368 });
10369 _ = editor.update(cx, |editor, window, cx| {
10370 // Removing an excerpt causes the first selection to become degenerate.
10371 assert_eq!(
10372 editor.selections.ranges(cx),
10373 [
10374 Point::new(0, 0)..Point::new(0, 0),
10375 Point::new(0, 1)..Point::new(0, 1)
10376 ]
10377 );
10378
10379 // Refreshing selections will relocate the first selection to the original buffer
10380 // location.
10381 editor.change_selections(None, window, cx, |s| s.refresh());
10382 assert_eq!(
10383 editor.selections.ranges(cx),
10384 [
10385 Point::new(0, 1)..Point::new(0, 1),
10386 Point::new(0, 3)..Point::new(0, 3)
10387 ]
10388 );
10389 assert!(editor.selections.pending_anchor().is_some());
10390 });
10391}
10392
10393#[gpui::test]
10394fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10395 init_test(cx, |_| {});
10396
10397 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10398 let mut excerpt1_id = None;
10399 let multibuffer = cx.new(|cx| {
10400 let mut multibuffer = MultiBuffer::new(ReadWrite);
10401 excerpt1_id = multibuffer
10402 .push_excerpts(
10403 buffer.clone(),
10404 [
10405 ExcerptRange {
10406 context: Point::new(0, 0)..Point::new(1, 4),
10407 primary: None,
10408 },
10409 ExcerptRange {
10410 context: Point::new(1, 0)..Point::new(2, 4),
10411 primary: None,
10412 },
10413 ],
10414 cx,
10415 )
10416 .into_iter()
10417 .next();
10418 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10419 multibuffer
10420 });
10421
10422 let editor = cx.add_window(|window, cx| {
10423 let mut editor = build_editor(multibuffer.clone(), window, cx);
10424 let snapshot = editor.snapshot(window, cx);
10425 editor.begin_selection(
10426 Point::new(1, 3).to_display_point(&snapshot),
10427 false,
10428 1,
10429 window,
10430 cx,
10431 );
10432 assert_eq!(
10433 editor.selections.ranges(cx),
10434 [Point::new(1, 3)..Point::new(1, 3)]
10435 );
10436 editor
10437 });
10438
10439 multibuffer.update(cx, |multibuffer, cx| {
10440 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10441 });
10442 _ = editor.update(cx, |editor, window, cx| {
10443 assert_eq!(
10444 editor.selections.ranges(cx),
10445 [Point::new(0, 0)..Point::new(0, 0)]
10446 );
10447
10448 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10449 editor.change_selections(None, window, cx, |s| s.refresh());
10450 assert_eq!(
10451 editor.selections.ranges(cx),
10452 [Point::new(0, 3)..Point::new(0, 3)]
10453 );
10454 assert!(editor.selections.pending_anchor().is_some());
10455 });
10456}
10457
10458#[gpui::test]
10459async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10460 init_test(cx, |_| {});
10461
10462 let language = Arc::new(
10463 Language::new(
10464 LanguageConfig {
10465 brackets: BracketPairConfig {
10466 pairs: vec![
10467 BracketPair {
10468 start: "{".to_string(),
10469 end: "}".to_string(),
10470 close: true,
10471 surround: true,
10472 newline: true,
10473 },
10474 BracketPair {
10475 start: "/* ".to_string(),
10476 end: " */".to_string(),
10477 close: true,
10478 surround: true,
10479 newline: true,
10480 },
10481 ],
10482 ..Default::default()
10483 },
10484 ..Default::default()
10485 },
10486 Some(tree_sitter_rust::LANGUAGE.into()),
10487 )
10488 .with_indents_query("")
10489 .unwrap(),
10490 );
10491
10492 let text = concat!(
10493 "{ }\n", //
10494 " x\n", //
10495 " /* */\n", //
10496 "x\n", //
10497 "{{} }\n", //
10498 );
10499
10500 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10501 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10502 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10503 editor
10504 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10505 .await;
10506
10507 editor.update_in(cx, |editor, window, cx| {
10508 editor.change_selections(None, window, cx, |s| {
10509 s.select_display_ranges([
10510 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10511 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10512 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10513 ])
10514 });
10515 editor.newline(&Newline, window, cx);
10516
10517 assert_eq!(
10518 editor.buffer().read(cx).read(cx).text(),
10519 concat!(
10520 "{ \n", // Suppress rustfmt
10521 "\n", //
10522 "}\n", //
10523 " x\n", //
10524 " /* \n", //
10525 " \n", //
10526 " */\n", //
10527 "x\n", //
10528 "{{} \n", //
10529 "}\n", //
10530 )
10531 );
10532 });
10533}
10534
10535#[gpui::test]
10536fn test_highlighted_ranges(cx: &mut TestAppContext) {
10537 init_test(cx, |_| {});
10538
10539 let editor = cx.add_window(|window, cx| {
10540 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10541 build_editor(buffer.clone(), window, cx)
10542 });
10543
10544 _ = editor.update(cx, |editor, window, cx| {
10545 struct Type1;
10546 struct Type2;
10547
10548 let buffer = editor.buffer.read(cx).snapshot(cx);
10549
10550 let anchor_range =
10551 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10552
10553 editor.highlight_background::<Type1>(
10554 &[
10555 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10556 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10557 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10558 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10559 ],
10560 |_| Hsla::red(),
10561 cx,
10562 );
10563 editor.highlight_background::<Type2>(
10564 &[
10565 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10566 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10567 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10568 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10569 ],
10570 |_| Hsla::green(),
10571 cx,
10572 );
10573
10574 let snapshot = editor.snapshot(window, cx);
10575 let mut highlighted_ranges = editor.background_highlights_in_range(
10576 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10577 &snapshot,
10578 cx.theme().colors(),
10579 );
10580 // Enforce a consistent ordering based on color without relying on the ordering of the
10581 // highlight's `TypeId` which is non-executor.
10582 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10583 assert_eq!(
10584 highlighted_ranges,
10585 &[
10586 (
10587 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10588 Hsla::red(),
10589 ),
10590 (
10591 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10592 Hsla::red(),
10593 ),
10594 (
10595 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10596 Hsla::green(),
10597 ),
10598 (
10599 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10600 Hsla::green(),
10601 ),
10602 ]
10603 );
10604 assert_eq!(
10605 editor.background_highlights_in_range(
10606 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10607 &snapshot,
10608 cx.theme().colors(),
10609 ),
10610 &[(
10611 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10612 Hsla::red(),
10613 )]
10614 );
10615 });
10616}
10617
10618#[gpui::test]
10619async fn test_following(cx: &mut TestAppContext) {
10620 init_test(cx, |_| {});
10621
10622 let fs = FakeFs::new(cx.executor());
10623 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10624
10625 let buffer = project.update(cx, |project, cx| {
10626 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10627 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10628 });
10629 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10630 let follower = cx.update(|cx| {
10631 cx.open_window(
10632 WindowOptions {
10633 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10634 gpui::Point::new(px(0.), px(0.)),
10635 gpui::Point::new(px(10.), px(80.)),
10636 ))),
10637 ..Default::default()
10638 },
10639 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10640 )
10641 .unwrap()
10642 });
10643
10644 let is_still_following = Rc::new(RefCell::new(true));
10645 let follower_edit_event_count = Rc::new(RefCell::new(0));
10646 let pending_update = Rc::new(RefCell::new(None));
10647 let leader_entity = leader.root(cx).unwrap();
10648 let follower_entity = follower.root(cx).unwrap();
10649 _ = follower.update(cx, {
10650 let update = pending_update.clone();
10651 let is_still_following = is_still_following.clone();
10652 let follower_edit_event_count = follower_edit_event_count.clone();
10653 |_, window, cx| {
10654 cx.subscribe_in(
10655 &leader_entity,
10656 window,
10657 move |_, leader, event, window, cx| {
10658 leader.read(cx).add_event_to_update_proto(
10659 event,
10660 &mut update.borrow_mut(),
10661 window,
10662 cx,
10663 );
10664 },
10665 )
10666 .detach();
10667
10668 cx.subscribe_in(
10669 &follower_entity,
10670 window,
10671 move |_, _, event: &EditorEvent, _window, _cx| {
10672 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10673 *is_still_following.borrow_mut() = false;
10674 }
10675
10676 if let EditorEvent::BufferEdited = event {
10677 *follower_edit_event_count.borrow_mut() += 1;
10678 }
10679 },
10680 )
10681 .detach();
10682 }
10683 });
10684
10685 // Update the selections only
10686 _ = leader.update(cx, |leader, window, cx| {
10687 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10688 });
10689 follower
10690 .update(cx, |follower, window, cx| {
10691 follower.apply_update_proto(
10692 &project,
10693 pending_update.borrow_mut().take().unwrap(),
10694 window,
10695 cx,
10696 )
10697 })
10698 .unwrap()
10699 .await
10700 .unwrap();
10701 _ = follower.update(cx, |follower, _, cx| {
10702 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10703 });
10704 assert!(*is_still_following.borrow());
10705 assert_eq!(*follower_edit_event_count.borrow(), 0);
10706
10707 // Update the scroll position only
10708 _ = leader.update(cx, |leader, window, cx| {
10709 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10710 });
10711 follower
10712 .update(cx, |follower, window, cx| {
10713 follower.apply_update_proto(
10714 &project,
10715 pending_update.borrow_mut().take().unwrap(),
10716 window,
10717 cx,
10718 )
10719 })
10720 .unwrap()
10721 .await
10722 .unwrap();
10723 assert_eq!(
10724 follower
10725 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10726 .unwrap(),
10727 gpui::Point::new(1.5, 3.5)
10728 );
10729 assert!(*is_still_following.borrow());
10730 assert_eq!(*follower_edit_event_count.borrow(), 0);
10731
10732 // Update the selections and scroll position. The follower's scroll position is updated
10733 // via autoscroll, not via the leader's exact scroll position.
10734 _ = leader.update(cx, |leader, window, cx| {
10735 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10736 leader.request_autoscroll(Autoscroll::newest(), cx);
10737 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10738 });
10739 follower
10740 .update(cx, |follower, window, cx| {
10741 follower.apply_update_proto(
10742 &project,
10743 pending_update.borrow_mut().take().unwrap(),
10744 window,
10745 cx,
10746 )
10747 })
10748 .unwrap()
10749 .await
10750 .unwrap();
10751 _ = follower.update(cx, |follower, _, cx| {
10752 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10753 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10754 });
10755 assert!(*is_still_following.borrow());
10756
10757 // Creating a pending selection that precedes another selection
10758 _ = leader.update(cx, |leader, window, cx| {
10759 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10760 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10761 });
10762 follower
10763 .update(cx, |follower, window, cx| {
10764 follower.apply_update_proto(
10765 &project,
10766 pending_update.borrow_mut().take().unwrap(),
10767 window,
10768 cx,
10769 )
10770 })
10771 .unwrap()
10772 .await
10773 .unwrap();
10774 _ = follower.update(cx, |follower, _, cx| {
10775 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10776 });
10777 assert!(*is_still_following.borrow());
10778
10779 // Extend the pending selection so that it surrounds another selection
10780 _ = leader.update(cx, |leader, window, cx| {
10781 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10782 });
10783 follower
10784 .update(cx, |follower, window, cx| {
10785 follower.apply_update_proto(
10786 &project,
10787 pending_update.borrow_mut().take().unwrap(),
10788 window,
10789 cx,
10790 )
10791 })
10792 .unwrap()
10793 .await
10794 .unwrap();
10795 _ = follower.update(cx, |follower, _, cx| {
10796 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10797 });
10798
10799 // Scrolling locally breaks the follow
10800 _ = follower.update(cx, |follower, window, cx| {
10801 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10802 follower.set_scroll_anchor(
10803 ScrollAnchor {
10804 anchor: top_anchor,
10805 offset: gpui::Point::new(0.0, 0.5),
10806 },
10807 window,
10808 cx,
10809 );
10810 });
10811 assert!(!(*is_still_following.borrow()));
10812}
10813
10814#[gpui::test]
10815async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10816 init_test(cx, |_| {});
10817
10818 let fs = FakeFs::new(cx.executor());
10819 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10820 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10821 let pane = workspace
10822 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10823 .unwrap();
10824
10825 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10826
10827 let leader = pane.update_in(cx, |_, window, cx| {
10828 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10829 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10830 });
10831
10832 // Start following the editor when it has no excerpts.
10833 let mut state_message =
10834 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10835 let workspace_entity = workspace.root(cx).unwrap();
10836 let follower_1 = cx
10837 .update_window(*workspace.deref(), |_, window, cx| {
10838 Editor::from_state_proto(
10839 workspace_entity,
10840 ViewId {
10841 creator: Default::default(),
10842 id: 0,
10843 },
10844 &mut state_message,
10845 window,
10846 cx,
10847 )
10848 })
10849 .unwrap()
10850 .unwrap()
10851 .await
10852 .unwrap();
10853
10854 let update_message = Rc::new(RefCell::new(None));
10855 follower_1.update_in(cx, {
10856 let update = update_message.clone();
10857 |_, window, cx| {
10858 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10859 leader.read(cx).add_event_to_update_proto(
10860 event,
10861 &mut update.borrow_mut(),
10862 window,
10863 cx,
10864 );
10865 })
10866 .detach();
10867 }
10868 });
10869
10870 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10871 (
10872 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10873 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10874 )
10875 });
10876
10877 // Insert some excerpts.
10878 leader.update(cx, |leader, cx| {
10879 leader.buffer.update(cx, |multibuffer, cx| {
10880 let excerpt_ids = multibuffer.push_excerpts(
10881 buffer_1.clone(),
10882 [
10883 ExcerptRange {
10884 context: 1..6,
10885 primary: None,
10886 },
10887 ExcerptRange {
10888 context: 12..15,
10889 primary: None,
10890 },
10891 ExcerptRange {
10892 context: 0..3,
10893 primary: None,
10894 },
10895 ],
10896 cx,
10897 );
10898 multibuffer.insert_excerpts_after(
10899 excerpt_ids[0],
10900 buffer_2.clone(),
10901 [
10902 ExcerptRange {
10903 context: 8..12,
10904 primary: None,
10905 },
10906 ExcerptRange {
10907 context: 0..6,
10908 primary: None,
10909 },
10910 ],
10911 cx,
10912 );
10913 });
10914 });
10915
10916 // Apply the update of adding the excerpts.
10917 follower_1
10918 .update_in(cx, |follower, window, cx| {
10919 follower.apply_update_proto(
10920 &project,
10921 update_message.borrow().clone().unwrap(),
10922 window,
10923 cx,
10924 )
10925 })
10926 .await
10927 .unwrap();
10928 assert_eq!(
10929 follower_1.update(cx, |editor, cx| editor.text(cx)),
10930 leader.update(cx, |editor, cx| editor.text(cx))
10931 );
10932 update_message.borrow_mut().take();
10933
10934 // Start following separately after it already has excerpts.
10935 let mut state_message =
10936 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10937 let workspace_entity = workspace.root(cx).unwrap();
10938 let follower_2 = cx
10939 .update_window(*workspace.deref(), |_, window, cx| {
10940 Editor::from_state_proto(
10941 workspace_entity,
10942 ViewId {
10943 creator: Default::default(),
10944 id: 0,
10945 },
10946 &mut state_message,
10947 window,
10948 cx,
10949 )
10950 })
10951 .unwrap()
10952 .unwrap()
10953 .await
10954 .unwrap();
10955 assert_eq!(
10956 follower_2.update(cx, |editor, cx| editor.text(cx)),
10957 leader.update(cx, |editor, cx| editor.text(cx))
10958 );
10959
10960 // Remove some excerpts.
10961 leader.update(cx, |leader, cx| {
10962 leader.buffer.update(cx, |multibuffer, cx| {
10963 let excerpt_ids = multibuffer.excerpt_ids();
10964 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10965 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10966 });
10967 });
10968
10969 // Apply the update of removing the excerpts.
10970 follower_1
10971 .update_in(cx, |follower, window, cx| {
10972 follower.apply_update_proto(
10973 &project,
10974 update_message.borrow().clone().unwrap(),
10975 window,
10976 cx,
10977 )
10978 })
10979 .await
10980 .unwrap();
10981 follower_2
10982 .update_in(cx, |follower, window, cx| {
10983 follower.apply_update_proto(
10984 &project,
10985 update_message.borrow().clone().unwrap(),
10986 window,
10987 cx,
10988 )
10989 })
10990 .await
10991 .unwrap();
10992 update_message.borrow_mut().take();
10993 assert_eq!(
10994 follower_1.update(cx, |editor, cx| editor.text(cx)),
10995 leader.update(cx, |editor, cx| editor.text(cx))
10996 );
10997}
10998
10999#[gpui::test]
11000async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11001 init_test(cx, |_| {});
11002
11003 let mut cx = EditorTestContext::new(cx).await;
11004 let lsp_store =
11005 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11006
11007 cx.set_state(indoc! {"
11008 ˇfn func(abc def: i32) -> u32 {
11009 }
11010 "});
11011
11012 cx.update(|_, cx| {
11013 lsp_store.update(cx, |lsp_store, cx| {
11014 lsp_store
11015 .update_diagnostics(
11016 LanguageServerId(0),
11017 lsp::PublishDiagnosticsParams {
11018 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11019 version: None,
11020 diagnostics: vec![
11021 lsp::Diagnostic {
11022 range: lsp::Range::new(
11023 lsp::Position::new(0, 11),
11024 lsp::Position::new(0, 12),
11025 ),
11026 severity: Some(lsp::DiagnosticSeverity::ERROR),
11027 ..Default::default()
11028 },
11029 lsp::Diagnostic {
11030 range: lsp::Range::new(
11031 lsp::Position::new(0, 12),
11032 lsp::Position::new(0, 15),
11033 ),
11034 severity: Some(lsp::DiagnosticSeverity::ERROR),
11035 ..Default::default()
11036 },
11037 lsp::Diagnostic {
11038 range: lsp::Range::new(
11039 lsp::Position::new(0, 25),
11040 lsp::Position::new(0, 28),
11041 ),
11042 severity: Some(lsp::DiagnosticSeverity::ERROR),
11043 ..Default::default()
11044 },
11045 ],
11046 },
11047 &[],
11048 cx,
11049 )
11050 .unwrap()
11051 });
11052 });
11053
11054 executor.run_until_parked();
11055
11056 cx.update_editor(|editor, window, cx| {
11057 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11058 });
11059
11060 cx.assert_editor_state(indoc! {"
11061 fn func(abc def: i32) -> ˇu32 {
11062 }
11063 "});
11064
11065 cx.update_editor(|editor, window, cx| {
11066 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11067 });
11068
11069 cx.assert_editor_state(indoc! {"
11070 fn func(abc ˇdef: i32) -> u32 {
11071 }
11072 "});
11073
11074 cx.update_editor(|editor, window, cx| {
11075 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11076 });
11077
11078 cx.assert_editor_state(indoc! {"
11079 fn func(abcˇ def: i32) -> u32 {
11080 }
11081 "});
11082
11083 cx.update_editor(|editor, window, cx| {
11084 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11085 });
11086
11087 cx.assert_editor_state(indoc! {"
11088 fn func(abc def: i32) -> ˇu32 {
11089 }
11090 "});
11091}
11092
11093#[gpui::test]
11094async fn cycle_through_same_place_diagnostics(
11095 executor: BackgroundExecutor,
11096 cx: &mut TestAppContext,
11097) {
11098 init_test(cx, |_| {});
11099
11100 let mut cx = EditorTestContext::new(cx).await;
11101 let lsp_store =
11102 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11103
11104 cx.set_state(indoc! {"
11105 ˇfn func(abc def: i32) -> u32 {
11106 }
11107 "});
11108
11109 cx.update(|_, cx| {
11110 lsp_store.update(cx, |lsp_store, cx| {
11111 lsp_store
11112 .update_diagnostics(
11113 LanguageServerId(0),
11114 lsp::PublishDiagnosticsParams {
11115 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11116 version: None,
11117 diagnostics: vec![
11118 lsp::Diagnostic {
11119 range: lsp::Range::new(
11120 lsp::Position::new(0, 11),
11121 lsp::Position::new(0, 12),
11122 ),
11123 severity: Some(lsp::DiagnosticSeverity::ERROR),
11124 ..Default::default()
11125 },
11126 lsp::Diagnostic {
11127 range: lsp::Range::new(
11128 lsp::Position::new(0, 12),
11129 lsp::Position::new(0, 15),
11130 ),
11131 severity: Some(lsp::DiagnosticSeverity::ERROR),
11132 ..Default::default()
11133 },
11134 lsp::Diagnostic {
11135 range: lsp::Range::new(
11136 lsp::Position::new(0, 12),
11137 lsp::Position::new(0, 15),
11138 ),
11139 severity: Some(lsp::DiagnosticSeverity::ERROR),
11140 ..Default::default()
11141 },
11142 lsp::Diagnostic {
11143 range: lsp::Range::new(
11144 lsp::Position::new(0, 25),
11145 lsp::Position::new(0, 28),
11146 ),
11147 severity: Some(lsp::DiagnosticSeverity::ERROR),
11148 ..Default::default()
11149 },
11150 ],
11151 },
11152 &[],
11153 cx,
11154 )
11155 .unwrap()
11156 });
11157 });
11158 executor.run_until_parked();
11159
11160 //// Backward
11161
11162 // Fourth diagnostic
11163 cx.update_editor(|editor, window, cx| {
11164 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11165 });
11166 cx.assert_editor_state(indoc! {"
11167 fn func(abc def: i32) -> ˇu32 {
11168 }
11169 "});
11170
11171 // Third diagnostic
11172 cx.update_editor(|editor, window, cx| {
11173 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11174 });
11175 cx.assert_editor_state(indoc! {"
11176 fn func(abc ˇdef: i32) -> u32 {
11177 }
11178 "});
11179
11180 // Second diagnostic, same place
11181 cx.update_editor(|editor, window, cx| {
11182 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11183 });
11184 cx.assert_editor_state(indoc! {"
11185 fn func(abc ˇdef: i32) -> u32 {
11186 }
11187 "});
11188
11189 // First diagnostic
11190 cx.update_editor(|editor, window, cx| {
11191 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11192 });
11193 cx.assert_editor_state(indoc! {"
11194 fn func(abcˇ def: i32) -> u32 {
11195 }
11196 "});
11197
11198 // Wrapped over, fourth diagnostic
11199 cx.update_editor(|editor, window, cx| {
11200 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11201 });
11202 cx.assert_editor_state(indoc! {"
11203 fn func(abc def: i32) -> ˇu32 {
11204 }
11205 "});
11206
11207 cx.update_editor(|editor, window, cx| {
11208 editor.move_to_beginning(&MoveToBeginning, window, cx);
11209 });
11210 cx.assert_editor_state(indoc! {"
11211 ˇfn func(abc def: i32) -> u32 {
11212 }
11213 "});
11214
11215 //// Forward
11216
11217 // First diagnostic
11218 cx.update_editor(|editor, window, cx| {
11219 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11220 });
11221 cx.assert_editor_state(indoc! {"
11222 fn func(abcˇ def: i32) -> u32 {
11223 }
11224 "});
11225
11226 // Second diagnostic
11227 cx.update_editor(|editor, window, cx| {
11228 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11229 });
11230 cx.assert_editor_state(indoc! {"
11231 fn func(abc ˇdef: i32) -> u32 {
11232 }
11233 "});
11234
11235 // Third diagnostic, same place
11236 cx.update_editor(|editor, window, cx| {
11237 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11238 });
11239 cx.assert_editor_state(indoc! {"
11240 fn func(abc ˇdef: i32) -> u32 {
11241 }
11242 "});
11243
11244 // Fourth diagnostic
11245 cx.update_editor(|editor, window, cx| {
11246 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11247 });
11248 cx.assert_editor_state(indoc! {"
11249 fn func(abc def: i32) -> ˇu32 {
11250 }
11251 "});
11252
11253 // Wrapped around, first diagnostic
11254 cx.update_editor(|editor, window, cx| {
11255 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11256 });
11257 cx.assert_editor_state(indoc! {"
11258 fn func(abcˇ def: i32) -> u32 {
11259 }
11260 "});
11261}
11262
11263#[gpui::test]
11264async fn active_diagnostics_dismiss_after_invalidation(
11265 executor: BackgroundExecutor,
11266 cx: &mut TestAppContext,
11267) {
11268 init_test(cx, |_| {});
11269
11270 let mut cx = EditorTestContext::new(cx).await;
11271 let lsp_store =
11272 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11273
11274 cx.set_state(indoc! {"
11275 ˇfn func(abc def: i32) -> u32 {
11276 }
11277 "});
11278
11279 let message = "Something's wrong!";
11280 cx.update(|_, cx| {
11281 lsp_store.update(cx, |lsp_store, cx| {
11282 lsp_store
11283 .update_diagnostics(
11284 LanguageServerId(0),
11285 lsp::PublishDiagnosticsParams {
11286 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11287 version: None,
11288 diagnostics: vec![lsp::Diagnostic {
11289 range: lsp::Range::new(
11290 lsp::Position::new(0, 11),
11291 lsp::Position::new(0, 12),
11292 ),
11293 severity: Some(lsp::DiagnosticSeverity::ERROR),
11294 message: message.to_string(),
11295 ..Default::default()
11296 }],
11297 },
11298 &[],
11299 cx,
11300 )
11301 .unwrap()
11302 });
11303 });
11304 executor.run_until_parked();
11305
11306 cx.update_editor(|editor, window, cx| {
11307 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11308 assert_eq!(
11309 editor
11310 .active_diagnostics
11311 .as_ref()
11312 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11313 Some(message),
11314 "Should have a diagnostics group activated"
11315 );
11316 });
11317 cx.assert_editor_state(indoc! {"
11318 fn func(abcˇ def: i32) -> u32 {
11319 }
11320 "});
11321
11322 cx.update(|_, cx| {
11323 lsp_store.update(cx, |lsp_store, cx| {
11324 lsp_store
11325 .update_diagnostics(
11326 LanguageServerId(0),
11327 lsp::PublishDiagnosticsParams {
11328 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11329 version: None,
11330 diagnostics: Vec::new(),
11331 },
11332 &[],
11333 cx,
11334 )
11335 .unwrap()
11336 });
11337 });
11338 executor.run_until_parked();
11339 cx.update_editor(|editor, _, _| {
11340 assert_eq!(
11341 editor.active_diagnostics, None,
11342 "After no diagnostics set to the editor, no diagnostics should be active"
11343 );
11344 });
11345 cx.assert_editor_state(indoc! {"
11346 fn func(abcˇ def: i32) -> u32 {
11347 }
11348 "});
11349
11350 cx.update_editor(|editor, window, cx| {
11351 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11352 assert_eq!(
11353 editor.active_diagnostics, None,
11354 "Should be no diagnostics to go to and activate"
11355 );
11356 });
11357 cx.assert_editor_state(indoc! {"
11358 fn func(abcˇ def: i32) -> u32 {
11359 }
11360 "});
11361}
11362
11363#[gpui::test]
11364async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11365 init_test(cx, |_| {});
11366
11367 let mut cx = EditorTestContext::new(cx).await;
11368
11369 cx.set_state(indoc! {"
11370 fn func(abˇc def: i32) -> u32 {
11371 }
11372 "});
11373 let lsp_store =
11374 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11375
11376 cx.update(|_, cx| {
11377 lsp_store.update(cx, |lsp_store, cx| {
11378 lsp_store.update_diagnostics(
11379 LanguageServerId(0),
11380 lsp::PublishDiagnosticsParams {
11381 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11382 version: None,
11383 diagnostics: vec![lsp::Diagnostic {
11384 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11385 severity: Some(lsp::DiagnosticSeverity::ERROR),
11386 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11387 ..Default::default()
11388 }],
11389 },
11390 &[],
11391 cx,
11392 )
11393 })
11394 }).unwrap();
11395 cx.run_until_parked();
11396 cx.update_editor(|editor, window, cx| {
11397 hover_popover::hover(editor, &Default::default(), window, cx)
11398 });
11399 cx.run_until_parked();
11400 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11401}
11402
11403#[gpui::test]
11404async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11405 init_test(cx, |_| {});
11406
11407 let mut cx = EditorTestContext::new(cx).await;
11408
11409 let diff_base = r#"
11410 use some::mod;
11411
11412 const A: u32 = 42;
11413
11414 fn main() {
11415 println!("hello");
11416
11417 println!("world");
11418 }
11419 "#
11420 .unindent();
11421
11422 // Edits are modified, removed, modified, added
11423 cx.set_state(
11424 &r#"
11425 use some::modified;
11426
11427 ˇ
11428 fn main() {
11429 println!("hello there");
11430
11431 println!("around the");
11432 println!("world");
11433 }
11434 "#
11435 .unindent(),
11436 );
11437
11438 cx.set_head_text(&diff_base);
11439 executor.run_until_parked();
11440
11441 cx.update_editor(|editor, window, cx| {
11442 //Wrap around the bottom of the buffer
11443 for _ in 0..3 {
11444 editor.go_to_next_hunk(&GoToHunk, window, cx);
11445 }
11446 });
11447
11448 cx.assert_editor_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.update_editor(|editor, window, cx| {
11464 //Wrap around the top of the buffer
11465 for _ in 0..2 {
11466 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11467 }
11468 });
11469
11470 cx.assert_editor_state(
11471 &r#"
11472 use some::modified;
11473
11474
11475 fn main() {
11476 ˇ println!("hello there");
11477
11478 println!("around the");
11479 println!("world");
11480 }
11481 "#
11482 .unindent(),
11483 );
11484
11485 cx.update_editor(|editor, window, cx| {
11486 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11487 });
11488
11489 cx.assert_editor_state(
11490 &r#"
11491 use some::modified;
11492
11493 ˇ
11494 fn main() {
11495 println!("hello there");
11496
11497 println!("around the");
11498 println!("world");
11499 }
11500 "#
11501 .unindent(),
11502 );
11503
11504 cx.update_editor(|editor, window, cx| {
11505 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11506 });
11507
11508 cx.assert_editor_state(
11509 &r#"
11510 ˇuse some::modified;
11511
11512
11513 fn main() {
11514 println!("hello there");
11515
11516 println!("around the");
11517 println!("world");
11518 }
11519 "#
11520 .unindent(),
11521 );
11522
11523 cx.update_editor(|editor, window, cx| {
11524 for _ in 0..2 {
11525 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11526 }
11527 });
11528
11529 cx.assert_editor_state(
11530 &r#"
11531 use some::modified;
11532
11533
11534 fn main() {
11535 ˇ println!("hello there");
11536
11537 println!("around the");
11538 println!("world");
11539 }
11540 "#
11541 .unindent(),
11542 );
11543
11544 cx.update_editor(|editor, window, cx| {
11545 editor.fold(&Fold, window, cx);
11546 });
11547
11548 cx.update_editor(|editor, window, cx| {
11549 editor.go_to_next_hunk(&GoToHunk, window, cx);
11550 });
11551
11552 cx.assert_editor_state(
11553 &r#"
11554 ˇuse some::modified;
11555
11556
11557 fn main() {
11558 println!("hello there");
11559
11560 println!("around the");
11561 println!("world");
11562 }
11563 "#
11564 .unindent(),
11565 );
11566}
11567
11568#[test]
11569fn test_split_words() {
11570 fn split(text: &str) -> Vec<&str> {
11571 split_words(text).collect()
11572 }
11573
11574 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11575 assert_eq!(split("hello_world"), &["hello_", "world"]);
11576 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11577 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11578 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11579 assert_eq!(split("helloworld"), &["helloworld"]);
11580
11581 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11582}
11583
11584#[gpui::test]
11585async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11586 init_test(cx, |_| {});
11587
11588 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11589 let mut assert = |before, after| {
11590 let _state_context = cx.set_state(before);
11591 cx.run_until_parked();
11592 cx.update_editor(|editor, window, cx| {
11593 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11594 });
11595 cx.assert_editor_state(after);
11596 };
11597
11598 // Outside bracket jumps to outside of matching bracket
11599 assert("console.logˇ(var);", "console.log(var)ˇ;");
11600 assert("console.log(var)ˇ;", "console.logˇ(var);");
11601
11602 // Inside bracket jumps to inside of matching bracket
11603 assert("console.log(ˇvar);", "console.log(varˇ);");
11604 assert("console.log(varˇ);", "console.log(ˇvar);");
11605
11606 // When outside a bracket and inside, favor jumping to the inside bracket
11607 assert(
11608 "console.log('foo', [1, 2, 3]ˇ);",
11609 "console.log(ˇ'foo', [1, 2, 3]);",
11610 );
11611 assert(
11612 "console.log(ˇ'foo', [1, 2, 3]);",
11613 "console.log('foo', [1, 2, 3]ˇ);",
11614 );
11615
11616 // Bias forward if two options are equally likely
11617 assert(
11618 "let result = curried_fun()ˇ();",
11619 "let result = curried_fun()()ˇ;",
11620 );
11621
11622 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11623 assert(
11624 indoc! {"
11625 function test() {
11626 console.log('test')ˇ
11627 }"},
11628 indoc! {"
11629 function test() {
11630 console.logˇ('test')
11631 }"},
11632 );
11633}
11634
11635#[gpui::test]
11636async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11637 init_test(cx, |_| {});
11638
11639 let fs = FakeFs::new(cx.executor());
11640 fs.insert_tree(
11641 path!("/a"),
11642 json!({
11643 "main.rs": "fn main() { let a = 5; }",
11644 "other.rs": "// Test file",
11645 }),
11646 )
11647 .await;
11648 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11649
11650 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11651 language_registry.add(Arc::new(Language::new(
11652 LanguageConfig {
11653 name: "Rust".into(),
11654 matcher: LanguageMatcher {
11655 path_suffixes: vec!["rs".to_string()],
11656 ..Default::default()
11657 },
11658 brackets: BracketPairConfig {
11659 pairs: vec![BracketPair {
11660 start: "{".to_string(),
11661 end: "}".to_string(),
11662 close: true,
11663 surround: true,
11664 newline: true,
11665 }],
11666 disabled_scopes_by_bracket_ix: Vec::new(),
11667 },
11668 ..Default::default()
11669 },
11670 Some(tree_sitter_rust::LANGUAGE.into()),
11671 )));
11672 let mut fake_servers = language_registry.register_fake_lsp(
11673 "Rust",
11674 FakeLspAdapter {
11675 capabilities: lsp::ServerCapabilities {
11676 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11677 first_trigger_character: "{".to_string(),
11678 more_trigger_character: None,
11679 }),
11680 ..Default::default()
11681 },
11682 ..Default::default()
11683 },
11684 );
11685
11686 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11687
11688 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11689
11690 let worktree_id = workspace
11691 .update(cx, |workspace, _, cx| {
11692 workspace.project().update(cx, |project, cx| {
11693 project.worktrees(cx).next().unwrap().read(cx).id()
11694 })
11695 })
11696 .unwrap();
11697
11698 let buffer = project
11699 .update(cx, |project, cx| {
11700 project.open_local_buffer(path!("/a/main.rs"), cx)
11701 })
11702 .await
11703 .unwrap();
11704 let editor_handle = workspace
11705 .update(cx, |workspace, window, cx| {
11706 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11707 })
11708 .unwrap()
11709 .await
11710 .unwrap()
11711 .downcast::<Editor>()
11712 .unwrap();
11713
11714 cx.executor().start_waiting();
11715 let fake_server = fake_servers.next().await.unwrap();
11716
11717 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11718 assert_eq!(
11719 params.text_document_position.text_document.uri,
11720 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11721 );
11722 assert_eq!(
11723 params.text_document_position.position,
11724 lsp::Position::new(0, 21),
11725 );
11726
11727 Ok(Some(vec![lsp::TextEdit {
11728 new_text: "]".to_string(),
11729 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11730 }]))
11731 });
11732
11733 editor_handle.update_in(cx, |editor, window, cx| {
11734 window.focus(&editor.focus_handle(cx));
11735 editor.change_selections(None, window, cx, |s| {
11736 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11737 });
11738 editor.handle_input("{", window, cx);
11739 });
11740
11741 cx.executor().run_until_parked();
11742
11743 buffer.update(cx, |buffer, _| {
11744 assert_eq!(
11745 buffer.text(),
11746 "fn main() { let a = {5}; }",
11747 "No extra braces from on type formatting should appear in the buffer"
11748 )
11749 });
11750}
11751
11752#[gpui::test]
11753async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11754 init_test(cx, |_| {});
11755
11756 let fs = FakeFs::new(cx.executor());
11757 fs.insert_tree(
11758 path!("/a"),
11759 json!({
11760 "main.rs": "fn main() { let a = 5; }",
11761 "other.rs": "// Test file",
11762 }),
11763 )
11764 .await;
11765
11766 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11767
11768 let server_restarts = Arc::new(AtomicUsize::new(0));
11769 let closure_restarts = Arc::clone(&server_restarts);
11770 let language_server_name = "test language server";
11771 let language_name: LanguageName = "Rust".into();
11772
11773 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11774 language_registry.add(Arc::new(Language::new(
11775 LanguageConfig {
11776 name: language_name.clone(),
11777 matcher: LanguageMatcher {
11778 path_suffixes: vec!["rs".to_string()],
11779 ..Default::default()
11780 },
11781 ..Default::default()
11782 },
11783 Some(tree_sitter_rust::LANGUAGE.into()),
11784 )));
11785 let mut fake_servers = language_registry.register_fake_lsp(
11786 "Rust",
11787 FakeLspAdapter {
11788 name: language_server_name,
11789 initialization_options: Some(json!({
11790 "testOptionValue": true
11791 })),
11792 initializer: Some(Box::new(move |fake_server| {
11793 let task_restarts = Arc::clone(&closure_restarts);
11794 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11795 task_restarts.fetch_add(1, atomic::Ordering::Release);
11796 futures::future::ready(Ok(()))
11797 });
11798 })),
11799 ..Default::default()
11800 },
11801 );
11802
11803 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11804 let _buffer = project
11805 .update(cx, |project, cx| {
11806 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11807 })
11808 .await
11809 .unwrap();
11810 let _fake_server = fake_servers.next().await.unwrap();
11811 update_test_language_settings(cx, |language_settings| {
11812 language_settings.languages.insert(
11813 language_name.clone(),
11814 LanguageSettingsContent {
11815 tab_size: NonZeroU32::new(8),
11816 ..Default::default()
11817 },
11818 );
11819 });
11820 cx.executor().run_until_parked();
11821 assert_eq!(
11822 server_restarts.load(atomic::Ordering::Acquire),
11823 0,
11824 "Should not restart LSP server on an unrelated change"
11825 );
11826
11827 update_test_project_settings(cx, |project_settings| {
11828 project_settings.lsp.insert(
11829 "Some other server name".into(),
11830 LspSettings {
11831 binary: None,
11832 settings: None,
11833 initialization_options: Some(json!({
11834 "some other init value": false
11835 })),
11836 },
11837 );
11838 });
11839 cx.executor().run_until_parked();
11840 assert_eq!(
11841 server_restarts.load(atomic::Ordering::Acquire),
11842 0,
11843 "Should not restart LSP server on an unrelated LSP settings change"
11844 );
11845
11846 update_test_project_settings(cx, |project_settings| {
11847 project_settings.lsp.insert(
11848 language_server_name.into(),
11849 LspSettings {
11850 binary: None,
11851 settings: None,
11852 initialization_options: Some(json!({
11853 "anotherInitValue": false
11854 })),
11855 },
11856 );
11857 });
11858 cx.executor().run_until_parked();
11859 assert_eq!(
11860 server_restarts.load(atomic::Ordering::Acquire),
11861 1,
11862 "Should restart LSP server on a related LSP settings change"
11863 );
11864
11865 update_test_project_settings(cx, |project_settings| {
11866 project_settings.lsp.insert(
11867 language_server_name.into(),
11868 LspSettings {
11869 binary: None,
11870 settings: None,
11871 initialization_options: Some(json!({
11872 "anotherInitValue": false
11873 })),
11874 },
11875 );
11876 });
11877 cx.executor().run_until_parked();
11878 assert_eq!(
11879 server_restarts.load(atomic::Ordering::Acquire),
11880 1,
11881 "Should not restart LSP server on a related LSP settings change that is the same"
11882 );
11883
11884 update_test_project_settings(cx, |project_settings| {
11885 project_settings.lsp.insert(
11886 language_server_name.into(),
11887 LspSettings {
11888 binary: None,
11889 settings: None,
11890 initialization_options: None,
11891 },
11892 );
11893 });
11894 cx.executor().run_until_parked();
11895 assert_eq!(
11896 server_restarts.load(atomic::Ordering::Acquire),
11897 2,
11898 "Should restart LSP server on another related LSP settings change"
11899 );
11900}
11901
11902#[gpui::test]
11903async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
11904 init_test(cx, |_| {});
11905
11906 let mut cx = EditorLspTestContext::new_rust(
11907 lsp::ServerCapabilities {
11908 completion_provider: Some(lsp::CompletionOptions {
11909 trigger_characters: Some(vec![".".to_string()]),
11910 resolve_provider: Some(true),
11911 ..Default::default()
11912 }),
11913 ..Default::default()
11914 },
11915 cx,
11916 )
11917 .await;
11918
11919 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11920 cx.simulate_keystroke(".");
11921 let completion_item = lsp::CompletionItem {
11922 label: "some".into(),
11923 kind: Some(lsp::CompletionItemKind::SNIPPET),
11924 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
11925 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
11926 kind: lsp::MarkupKind::Markdown,
11927 value: "```rust\nSome(2)\n```".to_string(),
11928 })),
11929 deprecated: Some(false),
11930 sort_text: Some("fffffff2".to_string()),
11931 filter_text: Some("some".to_string()),
11932 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
11933 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11934 range: lsp::Range {
11935 start: lsp::Position {
11936 line: 0,
11937 character: 22,
11938 },
11939 end: lsp::Position {
11940 line: 0,
11941 character: 22,
11942 },
11943 },
11944 new_text: "Some(2)".to_string(),
11945 })),
11946 additional_text_edits: Some(vec![lsp::TextEdit {
11947 range: lsp::Range {
11948 start: lsp::Position {
11949 line: 0,
11950 character: 20,
11951 },
11952 end: lsp::Position {
11953 line: 0,
11954 character: 22,
11955 },
11956 },
11957 new_text: "".to_string(),
11958 }]),
11959 ..Default::default()
11960 };
11961
11962 let closure_completion_item = completion_item.clone();
11963 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11964 let task_completion_item = closure_completion_item.clone();
11965 async move {
11966 Ok(Some(lsp::CompletionResponse::Array(vec![
11967 task_completion_item,
11968 ])))
11969 }
11970 });
11971
11972 request.next().await;
11973
11974 cx.condition(|editor, _| editor.context_menu_visible())
11975 .await;
11976 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11977 editor
11978 .confirm_completion(&ConfirmCompletion::default(), window, cx)
11979 .unwrap()
11980 });
11981 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11982
11983 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11984 let task_completion_item = completion_item.clone();
11985 async move { Ok(task_completion_item) }
11986 })
11987 .next()
11988 .await
11989 .unwrap();
11990 apply_additional_edits.await.unwrap();
11991 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11992}
11993
11994#[gpui::test]
11995async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
11996 init_test(cx, |_| {});
11997
11998 let mut cx = EditorLspTestContext::new_rust(
11999 lsp::ServerCapabilities {
12000 completion_provider: Some(lsp::CompletionOptions {
12001 trigger_characters: Some(vec![".".to_string()]),
12002 resolve_provider: Some(true),
12003 ..Default::default()
12004 }),
12005 ..Default::default()
12006 },
12007 cx,
12008 )
12009 .await;
12010
12011 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12012 cx.simulate_keystroke(".");
12013
12014 let item1 = lsp::CompletionItem {
12015 label: "method id()".to_string(),
12016 filter_text: Some("id".to_string()),
12017 detail: None,
12018 documentation: None,
12019 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12020 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12021 new_text: ".id".to_string(),
12022 })),
12023 ..lsp::CompletionItem::default()
12024 };
12025
12026 let item2 = lsp::CompletionItem {
12027 label: "other".to_string(),
12028 filter_text: Some("other".to_string()),
12029 detail: None,
12030 documentation: None,
12031 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12032 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12033 new_text: ".other".to_string(),
12034 })),
12035 ..lsp::CompletionItem::default()
12036 };
12037
12038 let item1 = item1.clone();
12039 cx.handle_request::<lsp::request::Completion, _, _>({
12040 let item1 = item1.clone();
12041 move |_, _, _| {
12042 let item1 = item1.clone();
12043 let item2 = item2.clone();
12044 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12045 }
12046 })
12047 .next()
12048 .await;
12049
12050 cx.condition(|editor, _| editor.context_menu_visible())
12051 .await;
12052 cx.update_editor(|editor, _, _| {
12053 let context_menu = editor.context_menu.borrow_mut();
12054 let context_menu = context_menu
12055 .as_ref()
12056 .expect("Should have the context menu deployed");
12057 match context_menu {
12058 CodeContextMenu::Completions(completions_menu) => {
12059 let completions = completions_menu.completions.borrow_mut();
12060 assert_eq!(
12061 completions
12062 .iter()
12063 .map(|completion| &completion.label.text)
12064 .collect::<Vec<_>>(),
12065 vec!["method id()", "other"]
12066 )
12067 }
12068 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12069 }
12070 });
12071
12072 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12073 let item1 = item1.clone();
12074 move |_, item_to_resolve, _| {
12075 let item1 = item1.clone();
12076 async move {
12077 if item1 == item_to_resolve {
12078 Ok(lsp::CompletionItem {
12079 label: "method id()".to_string(),
12080 filter_text: Some("id".to_string()),
12081 detail: Some("Now resolved!".to_string()),
12082 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12083 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12084 range: lsp::Range::new(
12085 lsp::Position::new(0, 22),
12086 lsp::Position::new(0, 22),
12087 ),
12088 new_text: ".id".to_string(),
12089 })),
12090 ..lsp::CompletionItem::default()
12091 })
12092 } else {
12093 Ok(item_to_resolve)
12094 }
12095 }
12096 }
12097 })
12098 .next()
12099 .await
12100 .unwrap();
12101 cx.run_until_parked();
12102
12103 cx.update_editor(|editor, window, cx| {
12104 editor.context_menu_next(&Default::default(), window, cx);
12105 });
12106
12107 cx.update_editor(|editor, _, _| {
12108 let context_menu = editor.context_menu.borrow_mut();
12109 let context_menu = context_menu
12110 .as_ref()
12111 .expect("Should have the context menu deployed");
12112 match context_menu {
12113 CodeContextMenu::Completions(completions_menu) => {
12114 let completions = completions_menu.completions.borrow_mut();
12115 assert_eq!(
12116 completions
12117 .iter()
12118 .map(|completion| &completion.label.text)
12119 .collect::<Vec<_>>(),
12120 vec!["method id() Now resolved!", "other"],
12121 "Should update first completion label, but not second as the filter text did not match."
12122 );
12123 }
12124 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12125 }
12126 });
12127}
12128
12129#[gpui::test]
12130async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12131 init_test(cx, |_| {});
12132
12133 let mut cx = EditorLspTestContext::new_rust(
12134 lsp::ServerCapabilities {
12135 completion_provider: Some(lsp::CompletionOptions {
12136 trigger_characters: Some(vec![".".to_string()]),
12137 resolve_provider: Some(true),
12138 ..Default::default()
12139 }),
12140 ..Default::default()
12141 },
12142 cx,
12143 )
12144 .await;
12145
12146 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12147 cx.simulate_keystroke(".");
12148
12149 let unresolved_item_1 = lsp::CompletionItem {
12150 label: "id".to_string(),
12151 filter_text: Some("id".to_string()),
12152 detail: None,
12153 documentation: None,
12154 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12155 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12156 new_text: ".id".to_string(),
12157 })),
12158 ..lsp::CompletionItem::default()
12159 };
12160 let resolved_item_1 = lsp::CompletionItem {
12161 additional_text_edits: Some(vec![lsp::TextEdit {
12162 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12163 new_text: "!!".to_string(),
12164 }]),
12165 ..unresolved_item_1.clone()
12166 };
12167 let unresolved_item_2 = lsp::CompletionItem {
12168 label: "other".to_string(),
12169 filter_text: Some("other".to_string()),
12170 detail: None,
12171 documentation: None,
12172 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12173 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12174 new_text: ".other".to_string(),
12175 })),
12176 ..lsp::CompletionItem::default()
12177 };
12178 let resolved_item_2 = lsp::CompletionItem {
12179 additional_text_edits: Some(vec![lsp::TextEdit {
12180 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12181 new_text: "??".to_string(),
12182 }]),
12183 ..unresolved_item_2.clone()
12184 };
12185
12186 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12187 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12188 cx.lsp
12189 .server
12190 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12191 let unresolved_item_1 = unresolved_item_1.clone();
12192 let resolved_item_1 = resolved_item_1.clone();
12193 let unresolved_item_2 = unresolved_item_2.clone();
12194 let resolved_item_2 = resolved_item_2.clone();
12195 let resolve_requests_1 = resolve_requests_1.clone();
12196 let resolve_requests_2 = resolve_requests_2.clone();
12197 move |unresolved_request, _| {
12198 let unresolved_item_1 = unresolved_item_1.clone();
12199 let resolved_item_1 = resolved_item_1.clone();
12200 let unresolved_item_2 = unresolved_item_2.clone();
12201 let resolved_item_2 = resolved_item_2.clone();
12202 let resolve_requests_1 = resolve_requests_1.clone();
12203 let resolve_requests_2 = resolve_requests_2.clone();
12204 async move {
12205 if unresolved_request == unresolved_item_1 {
12206 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12207 Ok(resolved_item_1.clone())
12208 } else if unresolved_request == unresolved_item_2 {
12209 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12210 Ok(resolved_item_2.clone())
12211 } else {
12212 panic!("Unexpected completion item {unresolved_request:?}")
12213 }
12214 }
12215 }
12216 })
12217 .detach();
12218
12219 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12220 let unresolved_item_1 = unresolved_item_1.clone();
12221 let unresolved_item_2 = unresolved_item_2.clone();
12222 async move {
12223 Ok(Some(lsp::CompletionResponse::Array(vec![
12224 unresolved_item_1,
12225 unresolved_item_2,
12226 ])))
12227 }
12228 })
12229 .next()
12230 .await;
12231
12232 cx.condition(|editor, _| editor.context_menu_visible())
12233 .await;
12234 cx.update_editor(|editor, _, _| {
12235 let context_menu = editor.context_menu.borrow_mut();
12236 let context_menu = context_menu
12237 .as_ref()
12238 .expect("Should have the context menu deployed");
12239 match context_menu {
12240 CodeContextMenu::Completions(completions_menu) => {
12241 let completions = completions_menu.completions.borrow_mut();
12242 assert_eq!(
12243 completions
12244 .iter()
12245 .map(|completion| &completion.label.text)
12246 .collect::<Vec<_>>(),
12247 vec!["id", "other"]
12248 )
12249 }
12250 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12251 }
12252 });
12253 cx.run_until_parked();
12254
12255 cx.update_editor(|editor, window, cx| {
12256 editor.context_menu_next(&ContextMenuNext, window, cx);
12257 });
12258 cx.run_until_parked();
12259 cx.update_editor(|editor, window, cx| {
12260 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12261 });
12262 cx.run_until_parked();
12263 cx.update_editor(|editor, window, cx| {
12264 editor.context_menu_next(&ContextMenuNext, window, cx);
12265 });
12266 cx.run_until_parked();
12267 cx.update_editor(|editor, window, cx| {
12268 editor
12269 .compose_completion(&ComposeCompletion::default(), window, cx)
12270 .expect("No task returned")
12271 })
12272 .await
12273 .expect("Completion failed");
12274 cx.run_until_parked();
12275
12276 cx.update_editor(|editor, _, cx| {
12277 assert_eq!(
12278 resolve_requests_1.load(atomic::Ordering::Acquire),
12279 1,
12280 "Should always resolve once despite multiple selections"
12281 );
12282 assert_eq!(
12283 resolve_requests_2.load(atomic::Ordering::Acquire),
12284 1,
12285 "Should always resolve once after multiple selections and applying the completion"
12286 );
12287 assert_eq!(
12288 editor.text(cx),
12289 "fn main() { let a = ??.other; }",
12290 "Should use resolved data when applying the completion"
12291 );
12292 });
12293}
12294
12295#[gpui::test]
12296async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12297 init_test(cx, |_| {});
12298
12299 let item_0 = lsp::CompletionItem {
12300 label: "abs".into(),
12301 insert_text: Some("abs".into()),
12302 data: Some(json!({ "very": "special"})),
12303 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12304 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12305 lsp::InsertReplaceEdit {
12306 new_text: "abs".to_string(),
12307 insert: lsp::Range::default(),
12308 replace: lsp::Range::default(),
12309 },
12310 )),
12311 ..lsp::CompletionItem::default()
12312 };
12313 let items = iter::once(item_0.clone())
12314 .chain((11..51).map(|i| lsp::CompletionItem {
12315 label: format!("item_{}", i),
12316 insert_text: Some(format!("item_{}", i)),
12317 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12318 ..lsp::CompletionItem::default()
12319 }))
12320 .collect::<Vec<_>>();
12321
12322 let default_commit_characters = vec!["?".to_string()];
12323 let default_data = json!({ "default": "data"});
12324 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12325 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12326 let default_edit_range = lsp::Range {
12327 start: lsp::Position {
12328 line: 0,
12329 character: 5,
12330 },
12331 end: lsp::Position {
12332 line: 0,
12333 character: 5,
12334 },
12335 };
12336
12337 let mut cx = EditorLspTestContext::new_rust(
12338 lsp::ServerCapabilities {
12339 completion_provider: Some(lsp::CompletionOptions {
12340 trigger_characters: Some(vec![".".to_string()]),
12341 resolve_provider: Some(true),
12342 ..Default::default()
12343 }),
12344 ..Default::default()
12345 },
12346 cx,
12347 )
12348 .await;
12349
12350 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12351 cx.simulate_keystroke(".");
12352
12353 let completion_data = default_data.clone();
12354 let completion_characters = default_commit_characters.clone();
12355 let completion_items = items.clone();
12356 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12357 let default_data = completion_data.clone();
12358 let default_commit_characters = completion_characters.clone();
12359 let items = completion_items.clone();
12360 async move {
12361 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12362 items,
12363 item_defaults: Some(lsp::CompletionListItemDefaults {
12364 data: Some(default_data.clone()),
12365 commit_characters: Some(default_commit_characters.clone()),
12366 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12367 default_edit_range,
12368 )),
12369 insert_text_format: Some(default_insert_text_format),
12370 insert_text_mode: Some(default_insert_text_mode),
12371 }),
12372 ..lsp::CompletionList::default()
12373 })))
12374 }
12375 })
12376 .next()
12377 .await;
12378
12379 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12380 cx.lsp
12381 .server
12382 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12383 let closure_resolved_items = resolved_items.clone();
12384 move |item_to_resolve, _| {
12385 let closure_resolved_items = closure_resolved_items.clone();
12386 async move {
12387 closure_resolved_items.lock().push(item_to_resolve.clone());
12388 Ok(item_to_resolve)
12389 }
12390 }
12391 })
12392 .detach();
12393
12394 cx.condition(|editor, _| editor.context_menu_visible())
12395 .await;
12396 cx.run_until_parked();
12397 cx.update_editor(|editor, _, _| {
12398 let menu = editor.context_menu.borrow_mut();
12399 match menu.as_ref().expect("should have the completions menu") {
12400 CodeContextMenu::Completions(completions_menu) => {
12401 assert_eq!(
12402 completions_menu
12403 .entries
12404 .borrow()
12405 .iter()
12406 .map(|mat| mat.string.clone())
12407 .collect::<Vec<String>>(),
12408 items
12409 .iter()
12410 .map(|completion| completion.label.clone())
12411 .collect::<Vec<String>>()
12412 );
12413 }
12414 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12415 }
12416 });
12417 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12418 // with 4 from the end.
12419 assert_eq!(
12420 *resolved_items.lock(),
12421 [&items[0..16], &items[items.len() - 4..items.len()]]
12422 .concat()
12423 .iter()
12424 .cloned()
12425 .map(|mut item| {
12426 if item.data.is_none() {
12427 item.data = Some(default_data.clone());
12428 }
12429 item
12430 })
12431 .collect::<Vec<lsp::CompletionItem>>(),
12432 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12433 );
12434 resolved_items.lock().clear();
12435
12436 cx.update_editor(|editor, window, cx| {
12437 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12438 });
12439 cx.run_until_parked();
12440 // Completions that have already been resolved are skipped.
12441 assert_eq!(
12442 *resolved_items.lock(),
12443 items[items.len() - 16..items.len() - 4]
12444 .iter()
12445 .cloned()
12446 .map(|mut item| {
12447 if item.data.is_none() {
12448 item.data = Some(default_data.clone());
12449 }
12450 item
12451 })
12452 .collect::<Vec<lsp::CompletionItem>>()
12453 );
12454 resolved_items.lock().clear();
12455}
12456
12457#[gpui::test]
12458async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12459 init_test(cx, |_| {});
12460
12461 let mut cx = EditorLspTestContext::new(
12462 Language::new(
12463 LanguageConfig {
12464 matcher: LanguageMatcher {
12465 path_suffixes: vec!["jsx".into()],
12466 ..Default::default()
12467 },
12468 overrides: [(
12469 "element".into(),
12470 LanguageConfigOverride {
12471 word_characters: Override::Set(['-'].into_iter().collect()),
12472 ..Default::default()
12473 },
12474 )]
12475 .into_iter()
12476 .collect(),
12477 ..Default::default()
12478 },
12479 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12480 )
12481 .with_override_query("(jsx_self_closing_element) @element")
12482 .unwrap(),
12483 lsp::ServerCapabilities {
12484 completion_provider: Some(lsp::CompletionOptions {
12485 trigger_characters: Some(vec![":".to_string()]),
12486 ..Default::default()
12487 }),
12488 ..Default::default()
12489 },
12490 cx,
12491 )
12492 .await;
12493
12494 cx.lsp
12495 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12496 Ok(Some(lsp::CompletionResponse::Array(vec![
12497 lsp::CompletionItem {
12498 label: "bg-blue".into(),
12499 ..Default::default()
12500 },
12501 lsp::CompletionItem {
12502 label: "bg-red".into(),
12503 ..Default::default()
12504 },
12505 lsp::CompletionItem {
12506 label: "bg-yellow".into(),
12507 ..Default::default()
12508 },
12509 ])))
12510 });
12511
12512 cx.set_state(r#"<p class="bgˇ" />"#);
12513
12514 // Trigger completion when typing a dash, because the dash is an extra
12515 // word character in the 'element' scope, which contains the cursor.
12516 cx.simulate_keystroke("-");
12517 cx.executor().run_until_parked();
12518 cx.update_editor(|editor, _, _| {
12519 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12520 {
12521 assert_eq!(
12522 completion_menu_entries(&menu),
12523 &["bg-red", "bg-blue", "bg-yellow"]
12524 );
12525 } else {
12526 panic!("expected completion menu to be open");
12527 }
12528 });
12529
12530 cx.simulate_keystroke("l");
12531 cx.executor().run_until_parked();
12532 cx.update_editor(|editor, _, _| {
12533 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12534 {
12535 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12536 } else {
12537 panic!("expected completion menu to be open");
12538 }
12539 });
12540
12541 // When filtering completions, consider the character after the '-' to
12542 // be the start of a subword.
12543 cx.set_state(r#"<p class="yelˇ" />"#);
12544 cx.simulate_keystroke("l");
12545 cx.executor().run_until_parked();
12546 cx.update_editor(|editor, _, _| {
12547 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12548 {
12549 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12550 } else {
12551 panic!("expected completion menu to be open");
12552 }
12553 });
12554}
12555
12556fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12557 let entries = menu.entries.borrow();
12558 entries.iter().map(|mat| mat.string.clone()).collect()
12559}
12560
12561#[gpui::test]
12562async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12563 init_test(cx, |settings| {
12564 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12565 FormatterList(vec![Formatter::Prettier].into()),
12566 ))
12567 });
12568
12569 let fs = FakeFs::new(cx.executor());
12570 fs.insert_file(path!("/file.ts"), Default::default()).await;
12571
12572 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12573 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12574
12575 language_registry.add(Arc::new(Language::new(
12576 LanguageConfig {
12577 name: "TypeScript".into(),
12578 matcher: LanguageMatcher {
12579 path_suffixes: vec!["ts".to_string()],
12580 ..Default::default()
12581 },
12582 ..Default::default()
12583 },
12584 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12585 )));
12586 update_test_language_settings(cx, |settings| {
12587 settings.defaults.prettier = Some(PrettierSettings {
12588 allowed: true,
12589 ..PrettierSettings::default()
12590 });
12591 });
12592
12593 let test_plugin = "test_plugin";
12594 let _ = language_registry.register_fake_lsp(
12595 "TypeScript",
12596 FakeLspAdapter {
12597 prettier_plugins: vec![test_plugin],
12598 ..Default::default()
12599 },
12600 );
12601
12602 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12603 let buffer = project
12604 .update(cx, |project, cx| {
12605 project.open_local_buffer(path!("/file.ts"), cx)
12606 })
12607 .await
12608 .unwrap();
12609
12610 let buffer_text = "one\ntwo\nthree\n";
12611 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12612 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12613 editor.update_in(cx, |editor, window, cx| {
12614 editor.set_text(buffer_text, window, cx)
12615 });
12616
12617 editor
12618 .update_in(cx, |editor, window, cx| {
12619 editor.perform_format(
12620 project.clone(),
12621 FormatTrigger::Manual,
12622 FormatTarget::Buffers,
12623 window,
12624 cx,
12625 )
12626 })
12627 .unwrap()
12628 .await;
12629 assert_eq!(
12630 editor.update(cx, |editor, cx| editor.text(cx)),
12631 buffer_text.to_string() + prettier_format_suffix,
12632 "Test prettier formatting was not applied to the original buffer text",
12633 );
12634
12635 update_test_language_settings(cx, |settings| {
12636 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12637 });
12638 let format = editor.update_in(cx, |editor, window, cx| {
12639 editor.perform_format(
12640 project.clone(),
12641 FormatTrigger::Manual,
12642 FormatTarget::Buffers,
12643 window,
12644 cx,
12645 )
12646 });
12647 format.await.unwrap();
12648 assert_eq!(
12649 editor.update(cx, |editor, cx| editor.text(cx)),
12650 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12651 "Autoformatting (via test prettier) was not applied to the original buffer text",
12652 );
12653}
12654
12655#[gpui::test]
12656async fn test_addition_reverts(cx: &mut TestAppContext) {
12657 init_test(cx, |_| {});
12658 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12659 let base_text = indoc! {r#"
12660 struct Row;
12661 struct Row1;
12662 struct Row2;
12663
12664 struct Row4;
12665 struct Row5;
12666 struct Row6;
12667
12668 struct Row8;
12669 struct Row9;
12670 struct Row10;"#};
12671
12672 // When addition hunks are not adjacent to carets, no hunk revert is performed
12673 assert_hunk_revert(
12674 indoc! {r#"struct Row;
12675 struct Row1;
12676 struct Row1.1;
12677 struct Row1.2;
12678 struct Row2;ˇ
12679
12680 struct Row4;
12681 struct Row5;
12682 struct Row6;
12683
12684 struct Row8;
12685 ˇstruct Row9;
12686 struct Row9.1;
12687 struct Row9.2;
12688 struct Row9.3;
12689 struct Row10;"#},
12690 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12691 indoc! {r#"struct Row;
12692 struct Row1;
12693 struct Row1.1;
12694 struct Row1.2;
12695 struct Row2;ˇ
12696
12697 struct Row4;
12698 struct Row5;
12699 struct Row6;
12700
12701 struct Row8;
12702 ˇstruct Row9;
12703 struct Row9.1;
12704 struct Row9.2;
12705 struct Row9.3;
12706 struct Row10;"#},
12707 base_text,
12708 &mut cx,
12709 );
12710 // Same for selections
12711 assert_hunk_revert(
12712 indoc! {r#"struct Row;
12713 struct Row1;
12714 struct Row2;
12715 struct Row2.1;
12716 struct Row2.2;
12717 «ˇ
12718 struct Row4;
12719 struct» Row5;
12720 «struct Row6;
12721 ˇ»
12722 struct Row9.1;
12723 struct Row9.2;
12724 struct Row9.3;
12725 struct Row8;
12726 struct Row9;
12727 struct Row10;"#},
12728 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12729 indoc! {r#"struct Row;
12730 struct Row1;
12731 struct Row2;
12732 struct Row2.1;
12733 struct Row2.2;
12734 «ˇ
12735 struct Row4;
12736 struct» Row5;
12737 «struct Row6;
12738 ˇ»
12739 struct Row9.1;
12740 struct Row9.2;
12741 struct Row9.3;
12742 struct Row8;
12743 struct Row9;
12744 struct Row10;"#},
12745 base_text,
12746 &mut cx,
12747 );
12748
12749 // When carets and selections intersect the addition hunks, those are reverted.
12750 // Adjacent carets got merged.
12751 assert_hunk_revert(
12752 indoc! {r#"struct Row;
12753 ˇ// something on the top
12754 struct Row1;
12755 struct Row2;
12756 struct Roˇw3.1;
12757 struct Row2.2;
12758 struct Row2.3;ˇ
12759
12760 struct Row4;
12761 struct ˇRow5.1;
12762 struct Row5.2;
12763 struct «Rowˇ»5.3;
12764 struct Row5;
12765 struct Row6;
12766 ˇ
12767 struct Row9.1;
12768 struct «Rowˇ»9.2;
12769 struct «ˇRow»9.3;
12770 struct Row8;
12771 struct Row9;
12772 «ˇ// something on bottom»
12773 struct Row10;"#},
12774 vec![
12775 DiffHunkStatusKind::Added,
12776 DiffHunkStatusKind::Added,
12777 DiffHunkStatusKind::Added,
12778 DiffHunkStatusKind::Added,
12779 DiffHunkStatusKind::Added,
12780 ],
12781 indoc! {r#"struct Row;
12782 ˇstruct Row1;
12783 struct Row2;
12784 ˇ
12785 struct Row4;
12786 ˇstruct Row5;
12787 struct Row6;
12788 ˇ
12789 ˇstruct Row8;
12790 struct Row9;
12791 ˇstruct Row10;"#},
12792 base_text,
12793 &mut cx,
12794 );
12795}
12796
12797#[gpui::test]
12798async fn test_modification_reverts(cx: &mut TestAppContext) {
12799 init_test(cx, |_| {});
12800 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12801 let base_text = indoc! {r#"
12802 struct Row;
12803 struct Row1;
12804 struct Row2;
12805
12806 struct Row4;
12807 struct Row5;
12808 struct Row6;
12809
12810 struct Row8;
12811 struct Row9;
12812 struct Row10;"#};
12813
12814 // Modification hunks behave the same as the addition ones.
12815 assert_hunk_revert(
12816 indoc! {r#"struct Row;
12817 struct Row1;
12818 struct Row33;
12819 ˇ
12820 struct Row4;
12821 struct Row5;
12822 struct Row6;
12823 ˇ
12824 struct Row99;
12825 struct Row9;
12826 struct Row10;"#},
12827 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12828 indoc! {r#"struct Row;
12829 struct Row1;
12830 struct Row33;
12831 ˇ
12832 struct Row4;
12833 struct Row5;
12834 struct Row6;
12835 ˇ
12836 struct Row99;
12837 struct Row9;
12838 struct Row10;"#},
12839 base_text,
12840 &mut cx,
12841 );
12842 assert_hunk_revert(
12843 indoc! {r#"struct Row;
12844 struct Row1;
12845 struct Row33;
12846 «ˇ
12847 struct Row4;
12848 struct» Row5;
12849 «struct Row6;
12850 ˇ»
12851 struct Row99;
12852 struct Row9;
12853 struct Row10;"#},
12854 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12855 indoc! {r#"struct Row;
12856 struct Row1;
12857 struct Row33;
12858 «ˇ
12859 struct Row4;
12860 struct» Row5;
12861 «struct Row6;
12862 ˇ»
12863 struct Row99;
12864 struct Row9;
12865 struct Row10;"#},
12866 base_text,
12867 &mut cx,
12868 );
12869
12870 assert_hunk_revert(
12871 indoc! {r#"ˇstruct Row1.1;
12872 struct Row1;
12873 «ˇstr»uct Row22;
12874
12875 struct ˇRow44;
12876 struct Row5;
12877 struct «Rˇ»ow66;ˇ
12878
12879 «struˇ»ct Row88;
12880 struct Row9;
12881 struct Row1011;ˇ"#},
12882 vec![
12883 DiffHunkStatusKind::Modified,
12884 DiffHunkStatusKind::Modified,
12885 DiffHunkStatusKind::Modified,
12886 DiffHunkStatusKind::Modified,
12887 DiffHunkStatusKind::Modified,
12888 DiffHunkStatusKind::Modified,
12889 ],
12890 indoc! {r#"struct Row;
12891 ˇstruct Row1;
12892 struct Row2;
12893 ˇ
12894 struct Row4;
12895 ˇstruct Row5;
12896 struct Row6;
12897 ˇ
12898 struct Row8;
12899 ˇstruct Row9;
12900 struct Row10;ˇ"#},
12901 base_text,
12902 &mut cx,
12903 );
12904}
12905
12906#[gpui::test]
12907async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12908 init_test(cx, |_| {});
12909 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12910 let base_text = indoc! {r#"
12911 one
12912
12913 two
12914 three
12915 "#};
12916
12917 cx.set_head_text(base_text);
12918 cx.set_state("\nˇ\n");
12919 cx.executor().run_until_parked();
12920 cx.update_editor(|editor, _window, cx| {
12921 editor.expand_selected_diff_hunks(cx);
12922 });
12923 cx.executor().run_until_parked();
12924 cx.update_editor(|editor, window, cx| {
12925 editor.backspace(&Default::default(), window, cx);
12926 });
12927 cx.run_until_parked();
12928 cx.assert_state_with_diff(
12929 indoc! {r#"
12930
12931 - two
12932 - threeˇ
12933 +
12934 "#}
12935 .to_string(),
12936 );
12937}
12938
12939#[gpui::test]
12940async fn test_deletion_reverts(cx: &mut TestAppContext) {
12941 init_test(cx, |_| {});
12942 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12943 let base_text = indoc! {r#"struct Row;
12944struct Row1;
12945struct Row2;
12946
12947struct Row4;
12948struct Row5;
12949struct Row6;
12950
12951struct Row8;
12952struct Row9;
12953struct Row10;"#};
12954
12955 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12956 assert_hunk_revert(
12957 indoc! {r#"struct Row;
12958 struct Row2;
12959
12960 ˇstruct Row4;
12961 struct Row5;
12962 struct Row6;
12963 ˇ
12964 struct Row8;
12965 struct Row10;"#},
12966 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12967 indoc! {r#"struct Row;
12968 struct Row2;
12969
12970 ˇstruct Row4;
12971 struct Row5;
12972 struct Row6;
12973 ˇ
12974 struct Row8;
12975 struct Row10;"#},
12976 base_text,
12977 &mut cx,
12978 );
12979 assert_hunk_revert(
12980 indoc! {r#"struct Row;
12981 struct Row2;
12982
12983 «ˇstruct Row4;
12984 struct» Row5;
12985 «struct Row6;
12986 ˇ»
12987 struct Row8;
12988 struct Row10;"#},
12989 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12990 indoc! {r#"struct Row;
12991 struct Row2;
12992
12993 «ˇstruct Row4;
12994 struct» Row5;
12995 «struct Row6;
12996 ˇ»
12997 struct Row8;
12998 struct Row10;"#},
12999 base_text,
13000 &mut cx,
13001 );
13002
13003 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
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 Row1;
13017 ˇstruct Row2;
13018
13019 struct Row4;
13020 struct Row5;
13021 struct Row6;
13022
13023 struct Row8;ˇ
13024 struct Row9;
13025 struct Row10;"#},
13026 base_text,
13027 &mut cx,
13028 );
13029 assert_hunk_revert(
13030 indoc! {r#"struct Row;
13031 struct Row2«ˇ;
13032 struct Row4;
13033 struct» Row5;
13034 «struct Row6;
13035
13036 struct Row8;ˇ»
13037 struct Row10;"#},
13038 vec![
13039 DiffHunkStatusKind::Deleted,
13040 DiffHunkStatusKind::Deleted,
13041 DiffHunkStatusKind::Deleted,
13042 ],
13043 indoc! {r#"struct Row;
13044 struct Row1;
13045 struct Row2«ˇ;
13046
13047 struct Row4;
13048 struct» Row5;
13049 «struct Row6;
13050
13051 struct Row8;ˇ»
13052 struct Row9;
13053 struct Row10;"#},
13054 base_text,
13055 &mut cx,
13056 );
13057}
13058
13059#[gpui::test]
13060async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13061 init_test(cx, |_| {});
13062
13063 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13064 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13065 let base_text_3 =
13066 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13067
13068 let text_1 = edit_first_char_of_every_line(base_text_1);
13069 let text_2 = edit_first_char_of_every_line(base_text_2);
13070 let text_3 = edit_first_char_of_every_line(base_text_3);
13071
13072 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13073 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13074 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13075
13076 let multibuffer = cx.new(|cx| {
13077 let mut multibuffer = MultiBuffer::new(ReadWrite);
13078 multibuffer.push_excerpts(
13079 buffer_1.clone(),
13080 [
13081 ExcerptRange {
13082 context: Point::new(0, 0)..Point::new(3, 0),
13083 primary: None,
13084 },
13085 ExcerptRange {
13086 context: Point::new(5, 0)..Point::new(7, 0),
13087 primary: None,
13088 },
13089 ExcerptRange {
13090 context: Point::new(9, 0)..Point::new(10, 4),
13091 primary: None,
13092 },
13093 ],
13094 cx,
13095 );
13096 multibuffer.push_excerpts(
13097 buffer_2.clone(),
13098 [
13099 ExcerptRange {
13100 context: Point::new(0, 0)..Point::new(3, 0),
13101 primary: None,
13102 },
13103 ExcerptRange {
13104 context: Point::new(5, 0)..Point::new(7, 0),
13105 primary: None,
13106 },
13107 ExcerptRange {
13108 context: Point::new(9, 0)..Point::new(10, 4),
13109 primary: None,
13110 },
13111 ],
13112 cx,
13113 );
13114 multibuffer.push_excerpts(
13115 buffer_3.clone(),
13116 [
13117 ExcerptRange {
13118 context: Point::new(0, 0)..Point::new(3, 0),
13119 primary: None,
13120 },
13121 ExcerptRange {
13122 context: Point::new(5, 0)..Point::new(7, 0),
13123 primary: None,
13124 },
13125 ExcerptRange {
13126 context: Point::new(9, 0)..Point::new(10, 4),
13127 primary: None,
13128 },
13129 ],
13130 cx,
13131 );
13132 multibuffer
13133 });
13134
13135 let fs = FakeFs::new(cx.executor());
13136 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13137 let (editor, cx) = cx
13138 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13139 editor.update_in(cx, |editor, _window, cx| {
13140 for (buffer, diff_base) in [
13141 (buffer_1.clone(), base_text_1),
13142 (buffer_2.clone(), base_text_2),
13143 (buffer_3.clone(), base_text_3),
13144 ] {
13145 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13146 editor
13147 .buffer
13148 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13149 }
13150 });
13151 cx.executor().run_until_parked();
13152
13153 editor.update_in(cx, |editor, window, cx| {
13154 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}");
13155 editor.select_all(&SelectAll, window, cx);
13156 editor.git_restore(&Default::default(), window, cx);
13157 });
13158 cx.executor().run_until_parked();
13159
13160 // When all ranges are selected, all buffer hunks are reverted.
13161 editor.update(cx, |editor, cx| {
13162 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");
13163 });
13164 buffer_1.update(cx, |buffer, _| {
13165 assert_eq!(buffer.text(), base_text_1);
13166 });
13167 buffer_2.update(cx, |buffer, _| {
13168 assert_eq!(buffer.text(), base_text_2);
13169 });
13170 buffer_3.update(cx, |buffer, _| {
13171 assert_eq!(buffer.text(), base_text_3);
13172 });
13173
13174 editor.update_in(cx, |editor, window, cx| {
13175 editor.undo(&Default::default(), window, cx);
13176 });
13177
13178 editor.update_in(cx, |editor, window, cx| {
13179 editor.change_selections(None, window, cx, |s| {
13180 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13181 });
13182 editor.git_restore(&Default::default(), window, cx);
13183 });
13184
13185 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13186 // but not affect buffer_2 and its related excerpts.
13187 editor.update(cx, |editor, cx| {
13188 assert_eq!(
13189 editor.text(cx),
13190 "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}"
13191 );
13192 });
13193 buffer_1.update(cx, |buffer, _| {
13194 assert_eq!(buffer.text(), base_text_1);
13195 });
13196 buffer_2.update(cx, |buffer, _| {
13197 assert_eq!(
13198 buffer.text(),
13199 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13200 );
13201 });
13202 buffer_3.update(cx, |buffer, _| {
13203 assert_eq!(
13204 buffer.text(),
13205 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13206 );
13207 });
13208
13209 fn edit_first_char_of_every_line(text: &str) -> String {
13210 text.split('\n')
13211 .map(|line| format!("X{}", &line[1..]))
13212 .collect::<Vec<_>>()
13213 .join("\n")
13214 }
13215}
13216
13217#[gpui::test]
13218async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13219 init_test(cx, |_| {});
13220
13221 let cols = 4;
13222 let rows = 10;
13223 let sample_text_1 = sample_text(rows, cols, 'a');
13224 assert_eq!(
13225 sample_text_1,
13226 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13227 );
13228 let sample_text_2 = sample_text(rows, cols, 'l');
13229 assert_eq!(
13230 sample_text_2,
13231 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13232 );
13233 let sample_text_3 = sample_text(rows, cols, 'v');
13234 assert_eq!(
13235 sample_text_3,
13236 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13237 );
13238
13239 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13240 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13241 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13242
13243 let multi_buffer = cx.new(|cx| {
13244 let mut multibuffer = MultiBuffer::new(ReadWrite);
13245 multibuffer.push_excerpts(
13246 buffer_1.clone(),
13247 [
13248 ExcerptRange {
13249 context: Point::new(0, 0)..Point::new(3, 0),
13250 primary: None,
13251 },
13252 ExcerptRange {
13253 context: Point::new(5, 0)..Point::new(7, 0),
13254 primary: None,
13255 },
13256 ExcerptRange {
13257 context: Point::new(9, 0)..Point::new(10, 4),
13258 primary: None,
13259 },
13260 ],
13261 cx,
13262 );
13263 multibuffer.push_excerpts(
13264 buffer_2.clone(),
13265 [
13266 ExcerptRange {
13267 context: Point::new(0, 0)..Point::new(3, 0),
13268 primary: None,
13269 },
13270 ExcerptRange {
13271 context: Point::new(5, 0)..Point::new(7, 0),
13272 primary: None,
13273 },
13274 ExcerptRange {
13275 context: Point::new(9, 0)..Point::new(10, 4),
13276 primary: None,
13277 },
13278 ],
13279 cx,
13280 );
13281 multibuffer.push_excerpts(
13282 buffer_3.clone(),
13283 [
13284 ExcerptRange {
13285 context: Point::new(0, 0)..Point::new(3, 0),
13286 primary: None,
13287 },
13288 ExcerptRange {
13289 context: Point::new(5, 0)..Point::new(7, 0),
13290 primary: None,
13291 },
13292 ExcerptRange {
13293 context: Point::new(9, 0)..Point::new(10, 4),
13294 primary: None,
13295 },
13296 ],
13297 cx,
13298 );
13299 multibuffer
13300 });
13301
13302 let fs = FakeFs::new(cx.executor());
13303 fs.insert_tree(
13304 "/a",
13305 json!({
13306 "main.rs": sample_text_1,
13307 "other.rs": sample_text_2,
13308 "lib.rs": sample_text_3,
13309 }),
13310 )
13311 .await;
13312 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13313 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13314 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13315 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13316 Editor::new(
13317 EditorMode::Full,
13318 multi_buffer,
13319 Some(project.clone()),
13320 true,
13321 window,
13322 cx,
13323 )
13324 });
13325 let multibuffer_item_id = workspace
13326 .update(cx, |workspace, window, cx| {
13327 assert!(
13328 workspace.active_item(cx).is_none(),
13329 "active item should be None before the first item is added"
13330 );
13331 workspace.add_item_to_active_pane(
13332 Box::new(multi_buffer_editor.clone()),
13333 None,
13334 true,
13335 window,
13336 cx,
13337 );
13338 let active_item = workspace
13339 .active_item(cx)
13340 .expect("should have an active item after adding the multi buffer");
13341 assert!(
13342 !active_item.is_singleton(cx),
13343 "A multi buffer was expected to active after adding"
13344 );
13345 active_item.item_id()
13346 })
13347 .unwrap();
13348 cx.executor().run_until_parked();
13349
13350 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13351 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13352 s.select_ranges(Some(1..2))
13353 });
13354 editor.open_excerpts(&OpenExcerpts, window, cx);
13355 });
13356 cx.executor().run_until_parked();
13357 let first_item_id = workspace
13358 .update(cx, |workspace, window, cx| {
13359 let active_item = workspace
13360 .active_item(cx)
13361 .expect("should have an active item after navigating into the 1st buffer");
13362 let first_item_id = active_item.item_id();
13363 assert_ne!(
13364 first_item_id, multibuffer_item_id,
13365 "Should navigate into the 1st buffer and activate it"
13366 );
13367 assert!(
13368 active_item.is_singleton(cx),
13369 "New active item should be a singleton buffer"
13370 );
13371 assert_eq!(
13372 active_item
13373 .act_as::<Editor>(cx)
13374 .expect("should have navigated into an editor for the 1st buffer")
13375 .read(cx)
13376 .text(cx),
13377 sample_text_1
13378 );
13379
13380 workspace
13381 .go_back(workspace.active_pane().downgrade(), window, cx)
13382 .detach_and_log_err(cx);
13383
13384 first_item_id
13385 })
13386 .unwrap();
13387 cx.executor().run_until_parked();
13388 workspace
13389 .update(cx, |workspace, _, cx| {
13390 let active_item = workspace
13391 .active_item(cx)
13392 .expect("should have an active item after navigating back");
13393 assert_eq!(
13394 active_item.item_id(),
13395 multibuffer_item_id,
13396 "Should navigate back to the multi buffer"
13397 );
13398 assert!(!active_item.is_singleton(cx));
13399 })
13400 .unwrap();
13401
13402 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13403 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13404 s.select_ranges(Some(39..40))
13405 });
13406 editor.open_excerpts(&OpenExcerpts, window, cx);
13407 });
13408 cx.executor().run_until_parked();
13409 let second_item_id = workspace
13410 .update(cx, |workspace, window, cx| {
13411 let active_item = workspace
13412 .active_item(cx)
13413 .expect("should have an active item after navigating into the 2nd buffer");
13414 let second_item_id = active_item.item_id();
13415 assert_ne!(
13416 second_item_id, multibuffer_item_id,
13417 "Should navigate away from the multibuffer"
13418 );
13419 assert_ne!(
13420 second_item_id, first_item_id,
13421 "Should navigate into the 2nd buffer and activate it"
13422 );
13423 assert!(
13424 active_item.is_singleton(cx),
13425 "New active item should be a singleton buffer"
13426 );
13427 assert_eq!(
13428 active_item
13429 .act_as::<Editor>(cx)
13430 .expect("should have navigated into an editor")
13431 .read(cx)
13432 .text(cx),
13433 sample_text_2
13434 );
13435
13436 workspace
13437 .go_back(workspace.active_pane().downgrade(), window, cx)
13438 .detach_and_log_err(cx);
13439
13440 second_item_id
13441 })
13442 .unwrap();
13443 cx.executor().run_until_parked();
13444 workspace
13445 .update(cx, |workspace, _, cx| {
13446 let active_item = workspace
13447 .active_item(cx)
13448 .expect("should have an active item after navigating back from the 2nd buffer");
13449 assert_eq!(
13450 active_item.item_id(),
13451 multibuffer_item_id,
13452 "Should navigate back from the 2nd buffer to the multi buffer"
13453 );
13454 assert!(!active_item.is_singleton(cx));
13455 })
13456 .unwrap();
13457
13458 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13459 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13460 s.select_ranges(Some(70..70))
13461 });
13462 editor.open_excerpts(&OpenExcerpts, window, cx);
13463 });
13464 cx.executor().run_until_parked();
13465 workspace
13466 .update(cx, |workspace, window, cx| {
13467 let active_item = workspace
13468 .active_item(cx)
13469 .expect("should have an active item after navigating into the 3rd buffer");
13470 let third_item_id = active_item.item_id();
13471 assert_ne!(
13472 third_item_id, multibuffer_item_id,
13473 "Should navigate into the 3rd buffer and activate it"
13474 );
13475 assert_ne!(third_item_id, first_item_id);
13476 assert_ne!(third_item_id, second_item_id);
13477 assert!(
13478 active_item.is_singleton(cx),
13479 "New active item should be a singleton buffer"
13480 );
13481 assert_eq!(
13482 active_item
13483 .act_as::<Editor>(cx)
13484 .expect("should have navigated into an editor")
13485 .read(cx)
13486 .text(cx),
13487 sample_text_3
13488 );
13489
13490 workspace
13491 .go_back(workspace.active_pane().downgrade(), window, cx)
13492 .detach_and_log_err(cx);
13493 })
13494 .unwrap();
13495 cx.executor().run_until_parked();
13496 workspace
13497 .update(cx, |workspace, _, cx| {
13498 let active_item = workspace
13499 .active_item(cx)
13500 .expect("should have an active item after navigating back from the 3rd buffer");
13501 assert_eq!(
13502 active_item.item_id(),
13503 multibuffer_item_id,
13504 "Should navigate back from the 3rd buffer to the multi buffer"
13505 );
13506 assert!(!active_item.is_singleton(cx));
13507 })
13508 .unwrap();
13509}
13510
13511#[gpui::test]
13512async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13513 init_test(cx, |_| {});
13514
13515 let mut cx = EditorTestContext::new(cx).await;
13516
13517 let diff_base = r#"
13518 use some::mod;
13519
13520 const A: u32 = 42;
13521
13522 fn main() {
13523 println!("hello");
13524
13525 println!("world");
13526 }
13527 "#
13528 .unindent();
13529
13530 cx.set_state(
13531 &r#"
13532 use some::modified;
13533
13534 ˇ
13535 fn main() {
13536 println!("hello there");
13537
13538 println!("around the");
13539 println!("world");
13540 }
13541 "#
13542 .unindent(),
13543 );
13544
13545 cx.set_head_text(&diff_base);
13546 executor.run_until_parked();
13547
13548 cx.update_editor(|editor, window, cx| {
13549 editor.go_to_next_hunk(&GoToHunk, window, cx);
13550 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13551 });
13552 executor.run_until_parked();
13553 cx.assert_state_with_diff(
13554 r#"
13555 use some::modified;
13556
13557
13558 fn main() {
13559 - println!("hello");
13560 + ˇ println!("hello there");
13561
13562 println!("around the");
13563 println!("world");
13564 }
13565 "#
13566 .unindent(),
13567 );
13568
13569 cx.update_editor(|editor, window, cx| {
13570 for _ in 0..2 {
13571 editor.go_to_next_hunk(&GoToHunk, window, cx);
13572 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13573 }
13574 });
13575 executor.run_until_parked();
13576 cx.assert_state_with_diff(
13577 r#"
13578 - use some::mod;
13579 + ˇuse some::modified;
13580
13581
13582 fn main() {
13583 - println!("hello");
13584 + println!("hello there");
13585
13586 + println!("around the");
13587 println!("world");
13588 }
13589 "#
13590 .unindent(),
13591 );
13592
13593 cx.update_editor(|editor, window, cx| {
13594 editor.go_to_next_hunk(&GoToHunk, window, cx);
13595 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13596 });
13597 executor.run_until_parked();
13598 cx.assert_state_with_diff(
13599 r#"
13600 - use some::mod;
13601 + use some::modified;
13602
13603 - const A: u32 = 42;
13604 ˇ
13605 fn main() {
13606 - println!("hello");
13607 + println!("hello there");
13608
13609 + println!("around the");
13610 println!("world");
13611 }
13612 "#
13613 .unindent(),
13614 );
13615
13616 cx.update_editor(|editor, window, cx| {
13617 editor.cancel(&Cancel, window, cx);
13618 });
13619
13620 cx.assert_state_with_diff(
13621 r#"
13622 use some::modified;
13623
13624 ˇ
13625 fn main() {
13626 println!("hello there");
13627
13628 println!("around the");
13629 println!("world");
13630 }
13631 "#
13632 .unindent(),
13633 );
13634}
13635
13636#[gpui::test]
13637async fn test_diff_base_change_with_expanded_diff_hunks(
13638 executor: BackgroundExecutor,
13639 cx: &mut TestAppContext,
13640) {
13641 init_test(cx, |_| {});
13642
13643 let mut cx = EditorTestContext::new(cx).await;
13644
13645 let diff_base = r#"
13646 use some::mod1;
13647 use some::mod2;
13648
13649 const A: u32 = 42;
13650 const B: u32 = 42;
13651 const C: u32 = 42;
13652
13653 fn main() {
13654 println!("hello");
13655
13656 println!("world");
13657 }
13658 "#
13659 .unindent();
13660
13661 cx.set_state(
13662 &r#"
13663 use some::mod2;
13664
13665 const A: u32 = 42;
13666 const C: u32 = 42;
13667
13668 fn main(ˇ) {
13669 //println!("hello");
13670
13671 println!("world");
13672 //
13673 //
13674 }
13675 "#
13676 .unindent(),
13677 );
13678
13679 cx.set_head_text(&diff_base);
13680 executor.run_until_parked();
13681
13682 cx.update_editor(|editor, window, cx| {
13683 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13684 });
13685 executor.run_until_parked();
13686 cx.assert_state_with_diff(
13687 r#"
13688 - use some::mod1;
13689 use some::mod2;
13690
13691 const A: u32 = 42;
13692 - const B: u32 = 42;
13693 const C: u32 = 42;
13694
13695 fn main(ˇ) {
13696 - println!("hello");
13697 + //println!("hello");
13698
13699 println!("world");
13700 + //
13701 + //
13702 }
13703 "#
13704 .unindent(),
13705 );
13706
13707 cx.set_head_text("new diff base!");
13708 executor.run_until_parked();
13709 cx.assert_state_with_diff(
13710 r#"
13711 - new diff base!
13712 + use some::mod2;
13713 +
13714 + const A: u32 = 42;
13715 + const C: u32 = 42;
13716 +
13717 + fn main(ˇ) {
13718 + //println!("hello");
13719 +
13720 + println!("world");
13721 + //
13722 + //
13723 + }
13724 "#
13725 .unindent(),
13726 );
13727}
13728
13729#[gpui::test]
13730async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13731 init_test(cx, |_| {});
13732
13733 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13734 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13735 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13736 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13737 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13738 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13739
13740 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13741 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13742 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13743
13744 let multi_buffer = cx.new(|cx| {
13745 let mut multibuffer = MultiBuffer::new(ReadWrite);
13746 multibuffer.push_excerpts(
13747 buffer_1.clone(),
13748 [
13749 ExcerptRange {
13750 context: Point::new(0, 0)..Point::new(3, 0),
13751 primary: None,
13752 },
13753 ExcerptRange {
13754 context: Point::new(5, 0)..Point::new(7, 0),
13755 primary: None,
13756 },
13757 ExcerptRange {
13758 context: Point::new(9, 0)..Point::new(10, 3),
13759 primary: None,
13760 },
13761 ],
13762 cx,
13763 );
13764 multibuffer.push_excerpts(
13765 buffer_2.clone(),
13766 [
13767 ExcerptRange {
13768 context: Point::new(0, 0)..Point::new(3, 0),
13769 primary: None,
13770 },
13771 ExcerptRange {
13772 context: Point::new(5, 0)..Point::new(7, 0),
13773 primary: None,
13774 },
13775 ExcerptRange {
13776 context: Point::new(9, 0)..Point::new(10, 3),
13777 primary: None,
13778 },
13779 ],
13780 cx,
13781 );
13782 multibuffer.push_excerpts(
13783 buffer_3.clone(),
13784 [
13785 ExcerptRange {
13786 context: Point::new(0, 0)..Point::new(3, 0),
13787 primary: None,
13788 },
13789 ExcerptRange {
13790 context: Point::new(5, 0)..Point::new(7, 0),
13791 primary: None,
13792 },
13793 ExcerptRange {
13794 context: Point::new(9, 0)..Point::new(10, 3),
13795 primary: None,
13796 },
13797 ],
13798 cx,
13799 );
13800 multibuffer
13801 });
13802
13803 let editor = cx.add_window(|window, cx| {
13804 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13805 });
13806 editor
13807 .update(cx, |editor, _window, cx| {
13808 for (buffer, diff_base) in [
13809 (buffer_1.clone(), file_1_old),
13810 (buffer_2.clone(), file_2_old),
13811 (buffer_3.clone(), file_3_old),
13812 ] {
13813 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13814 editor
13815 .buffer
13816 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13817 }
13818 })
13819 .unwrap();
13820
13821 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13822 cx.run_until_parked();
13823
13824 cx.assert_editor_state(
13825 &"
13826 ˇaaa
13827 ccc
13828 ddd
13829
13830 ggg
13831 hhh
13832
13833
13834 lll
13835 mmm
13836 NNN
13837
13838 qqq
13839 rrr
13840
13841 uuu
13842 111
13843 222
13844 333
13845
13846 666
13847 777
13848
13849 000
13850 !!!"
13851 .unindent(),
13852 );
13853
13854 cx.update_editor(|editor, window, cx| {
13855 editor.select_all(&SelectAll, window, cx);
13856 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13857 });
13858 cx.executor().run_until_parked();
13859
13860 cx.assert_state_with_diff(
13861 "
13862 «aaa
13863 - bbb
13864 ccc
13865 ddd
13866
13867 ggg
13868 hhh
13869
13870
13871 lll
13872 mmm
13873 - nnn
13874 + NNN
13875
13876 qqq
13877 rrr
13878
13879 uuu
13880 111
13881 222
13882 333
13883
13884 + 666
13885 777
13886
13887 000
13888 !!!ˇ»"
13889 .unindent(),
13890 );
13891}
13892
13893#[gpui::test]
13894async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13895 init_test(cx, |_| {});
13896
13897 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13898 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13899
13900 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13901 let multi_buffer = cx.new(|cx| {
13902 let mut multibuffer = MultiBuffer::new(ReadWrite);
13903 multibuffer.push_excerpts(
13904 buffer.clone(),
13905 [
13906 ExcerptRange {
13907 context: Point::new(0, 0)..Point::new(2, 0),
13908 primary: None,
13909 },
13910 ExcerptRange {
13911 context: Point::new(4, 0)..Point::new(7, 0),
13912 primary: None,
13913 },
13914 ExcerptRange {
13915 context: Point::new(9, 0)..Point::new(10, 0),
13916 primary: None,
13917 },
13918 ],
13919 cx,
13920 );
13921 multibuffer
13922 });
13923
13924 let editor = cx.add_window(|window, cx| {
13925 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13926 });
13927 editor
13928 .update(cx, |editor, _window, cx| {
13929 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13930 editor
13931 .buffer
13932 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13933 })
13934 .unwrap();
13935
13936 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13937 cx.run_until_parked();
13938
13939 cx.update_editor(|editor, window, cx| {
13940 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13941 });
13942 cx.executor().run_until_parked();
13943
13944 // When the start of a hunk coincides with the start of its excerpt,
13945 // the hunk is expanded. When the start of a a hunk is earlier than
13946 // the start of its excerpt, the hunk is not expanded.
13947 cx.assert_state_with_diff(
13948 "
13949 ˇaaa
13950 - bbb
13951 + BBB
13952
13953 - ddd
13954 - eee
13955 + DDD
13956 + EEE
13957 fff
13958
13959 iii
13960 "
13961 .unindent(),
13962 );
13963}
13964
13965#[gpui::test]
13966async fn test_edits_around_expanded_insertion_hunks(
13967 executor: BackgroundExecutor,
13968 cx: &mut TestAppContext,
13969) {
13970 init_test(cx, |_| {});
13971
13972 let mut cx = EditorTestContext::new(cx).await;
13973
13974 let diff_base = r#"
13975 use some::mod1;
13976 use some::mod2;
13977
13978 const A: u32 = 42;
13979
13980 fn main() {
13981 println!("hello");
13982
13983 println!("world");
13984 }
13985 "#
13986 .unindent();
13987 executor.run_until_parked();
13988 cx.set_state(
13989 &r#"
13990 use some::mod1;
13991 use some::mod2;
13992
13993 const A: u32 = 42;
13994 const B: u32 = 42;
13995 const C: u32 = 42;
13996 ˇ
13997
13998 fn main() {
13999 println!("hello");
14000
14001 println!("world");
14002 }
14003 "#
14004 .unindent(),
14005 );
14006
14007 cx.set_head_text(&diff_base);
14008 executor.run_until_parked();
14009
14010 cx.update_editor(|editor, window, cx| {
14011 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14012 });
14013 executor.run_until_parked();
14014
14015 cx.assert_state_with_diff(
14016 r#"
14017 use some::mod1;
14018 use some::mod2;
14019
14020 const A: u32 = 42;
14021 + const B: u32 = 42;
14022 + const C: u32 = 42;
14023 + ˇ
14024
14025 fn main() {
14026 println!("hello");
14027
14028 println!("world");
14029 }
14030 "#
14031 .unindent(),
14032 );
14033
14034 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14035 executor.run_until_parked();
14036
14037 cx.assert_state_with_diff(
14038 r#"
14039 use some::mod1;
14040 use some::mod2;
14041
14042 const A: u32 = 42;
14043 + const B: u32 = 42;
14044 + const C: u32 = 42;
14045 + const D: u32 = 42;
14046 + ˇ
14047
14048 fn main() {
14049 println!("hello");
14050
14051 println!("world");
14052 }
14053 "#
14054 .unindent(),
14055 );
14056
14057 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14058 executor.run_until_parked();
14059
14060 cx.assert_state_with_diff(
14061 r#"
14062 use some::mod1;
14063 use some::mod2;
14064
14065 const A: u32 = 42;
14066 + const B: u32 = 42;
14067 + const C: u32 = 42;
14068 + const D: u32 = 42;
14069 + const E: u32 = 42;
14070 + ˇ
14071
14072 fn main() {
14073 println!("hello");
14074
14075 println!("world");
14076 }
14077 "#
14078 .unindent(),
14079 );
14080
14081 cx.update_editor(|editor, window, cx| {
14082 editor.delete_line(&DeleteLine, window, cx);
14083 });
14084 executor.run_until_parked();
14085
14086 cx.assert_state_with_diff(
14087 r#"
14088 use some::mod1;
14089 use some::mod2;
14090
14091 const A: u32 = 42;
14092 + const B: u32 = 42;
14093 + const C: u32 = 42;
14094 + const D: u32 = 42;
14095 + const E: u32 = 42;
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.move_up(&MoveUp, window, cx);
14108 editor.delete_line(&DeleteLine, window, cx);
14109 editor.move_up(&MoveUp, window, cx);
14110 editor.delete_line(&DeleteLine, window, cx);
14111 editor.move_up(&MoveUp, window, cx);
14112 editor.delete_line(&DeleteLine, window, cx);
14113 });
14114 executor.run_until_parked();
14115 cx.assert_state_with_diff(
14116 r#"
14117 use some::mod1;
14118 use some::mod2;
14119
14120 const A: u32 = 42;
14121 + const B: u32 = 42;
14122 ˇ
14123 fn main() {
14124 println!("hello");
14125
14126 println!("world");
14127 }
14128 "#
14129 .unindent(),
14130 );
14131
14132 cx.update_editor(|editor, window, cx| {
14133 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14134 editor.delete_line(&DeleteLine, window, cx);
14135 });
14136 executor.run_until_parked();
14137 cx.assert_state_with_diff(
14138 r#"
14139 ˇ
14140 fn main() {
14141 println!("hello");
14142
14143 println!("world");
14144 }
14145 "#
14146 .unindent(),
14147 );
14148}
14149
14150#[gpui::test]
14151async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14152 init_test(cx, |_| {});
14153
14154 let mut cx = EditorTestContext::new(cx).await;
14155 cx.set_head_text(indoc! { "
14156 one
14157 two
14158 three
14159 four
14160 five
14161 "
14162 });
14163 cx.set_state(indoc! { "
14164 one
14165 ˇthree
14166 five
14167 "});
14168 cx.run_until_parked();
14169 cx.update_editor(|editor, window, cx| {
14170 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14171 });
14172 cx.assert_state_with_diff(
14173 indoc! { "
14174 one
14175 - two
14176 ˇthree
14177 - four
14178 five
14179 "}
14180 .to_string(),
14181 );
14182 cx.update_editor(|editor, window, cx| {
14183 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14184 });
14185
14186 cx.assert_state_with_diff(
14187 indoc! { "
14188 one
14189 ˇthree
14190 five
14191 "}
14192 .to_string(),
14193 );
14194
14195 cx.set_state(indoc! { "
14196 one
14197 ˇTWO
14198 three
14199 four
14200 five
14201 "});
14202 cx.run_until_parked();
14203 cx.update_editor(|editor, window, cx| {
14204 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14205 });
14206
14207 cx.assert_state_with_diff(
14208 indoc! { "
14209 one
14210 - two
14211 + ˇTWO
14212 three
14213 four
14214 five
14215 "}
14216 .to_string(),
14217 );
14218 cx.update_editor(|editor, window, cx| {
14219 editor.move_up(&Default::default(), window, cx);
14220 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14221 });
14222 cx.assert_state_with_diff(
14223 indoc! { "
14224 one
14225 ˇTWO
14226 three
14227 four
14228 five
14229 "}
14230 .to_string(),
14231 );
14232}
14233
14234#[gpui::test]
14235async fn test_edits_around_expanded_deletion_hunks(
14236 executor: BackgroundExecutor,
14237 cx: &mut TestAppContext,
14238) {
14239 init_test(cx, |_| {});
14240
14241 let mut cx = EditorTestContext::new(cx).await;
14242
14243 let diff_base = r#"
14244 use some::mod1;
14245 use some::mod2;
14246
14247 const A: u32 = 42;
14248 const B: u32 = 42;
14249 const C: u32 = 42;
14250
14251
14252 fn main() {
14253 println!("hello");
14254
14255 println!("world");
14256 }
14257 "#
14258 .unindent();
14259 executor.run_until_parked();
14260 cx.set_state(
14261 &r#"
14262 use some::mod1;
14263 use some::mod2;
14264
14265 ˇconst B: u32 = 42;
14266 const C: u32 = 42;
14267
14268
14269 fn main() {
14270 println!("hello");
14271
14272 println!("world");
14273 }
14274 "#
14275 .unindent(),
14276 );
14277
14278 cx.set_head_text(&diff_base);
14279 executor.run_until_parked();
14280
14281 cx.update_editor(|editor, window, cx| {
14282 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14283 });
14284 executor.run_until_parked();
14285
14286 cx.assert_state_with_diff(
14287 r#"
14288 use some::mod1;
14289 use some::mod2;
14290
14291 - const A: u32 = 42;
14292 ˇconst B: u32 = 42;
14293 const C: u32 = 42;
14294
14295
14296 fn main() {
14297 println!("hello");
14298
14299 println!("world");
14300 }
14301 "#
14302 .unindent(),
14303 );
14304
14305 cx.update_editor(|editor, window, cx| {
14306 editor.delete_line(&DeleteLine, window, cx);
14307 });
14308 executor.run_until_parked();
14309 cx.assert_state_with_diff(
14310 r#"
14311 use some::mod1;
14312 use some::mod2;
14313
14314 - const A: u32 = 42;
14315 - const B: u32 = 42;
14316 ˇconst C: u32 = 42;
14317
14318
14319 fn main() {
14320 println!("hello");
14321
14322 println!("world");
14323 }
14324 "#
14325 .unindent(),
14326 );
14327
14328 cx.update_editor(|editor, window, cx| {
14329 editor.delete_line(&DeleteLine, window, cx);
14330 });
14331 executor.run_until_parked();
14332 cx.assert_state_with_diff(
14333 r#"
14334 use some::mod1;
14335 use some::mod2;
14336
14337 - const A: u32 = 42;
14338 - const B: u32 = 42;
14339 - const C: u32 = 42;
14340 ˇ
14341
14342 fn main() {
14343 println!("hello");
14344
14345 println!("world");
14346 }
14347 "#
14348 .unindent(),
14349 );
14350
14351 cx.update_editor(|editor, window, cx| {
14352 editor.handle_input("replacement", window, cx);
14353 });
14354 executor.run_until_parked();
14355 cx.assert_state_with_diff(
14356 r#"
14357 use some::mod1;
14358 use some::mod2;
14359
14360 - const A: u32 = 42;
14361 - const B: u32 = 42;
14362 - const C: u32 = 42;
14363 -
14364 + replacementˇ
14365
14366 fn main() {
14367 println!("hello");
14368
14369 println!("world");
14370 }
14371 "#
14372 .unindent(),
14373 );
14374}
14375
14376#[gpui::test]
14377async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14378 init_test(cx, |_| {});
14379
14380 let mut cx = EditorTestContext::new(cx).await;
14381
14382 let base_text = r#"
14383 one
14384 two
14385 three
14386 four
14387 five
14388 "#
14389 .unindent();
14390 executor.run_until_parked();
14391 cx.set_state(
14392 &r#"
14393 one
14394 two
14395 fˇour
14396 five
14397 "#
14398 .unindent(),
14399 );
14400
14401 cx.set_head_text(&base_text);
14402 executor.run_until_parked();
14403
14404 cx.update_editor(|editor, window, cx| {
14405 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14406 });
14407 executor.run_until_parked();
14408
14409 cx.assert_state_with_diff(
14410 r#"
14411 one
14412 two
14413 - three
14414 fˇour
14415 five
14416 "#
14417 .unindent(),
14418 );
14419
14420 cx.update_editor(|editor, window, cx| {
14421 editor.backspace(&Backspace, window, cx);
14422 editor.backspace(&Backspace, window, cx);
14423 });
14424 executor.run_until_parked();
14425 cx.assert_state_with_diff(
14426 r#"
14427 one
14428 two
14429 - threeˇ
14430 - four
14431 + our
14432 five
14433 "#
14434 .unindent(),
14435 );
14436}
14437
14438#[gpui::test]
14439async fn test_edit_after_expanded_modification_hunk(
14440 executor: BackgroundExecutor,
14441 cx: &mut TestAppContext,
14442) {
14443 init_test(cx, |_| {});
14444
14445 let mut cx = EditorTestContext::new(cx).await;
14446
14447 let diff_base = r#"
14448 use some::mod1;
14449 use some::mod2;
14450
14451 const A: u32 = 42;
14452 const B: u32 = 42;
14453 const C: u32 = 42;
14454 const D: u32 = 42;
14455
14456
14457 fn main() {
14458 println!("hello");
14459
14460 println!("world");
14461 }"#
14462 .unindent();
14463
14464 cx.set_state(
14465 &r#"
14466 use some::mod1;
14467 use some::mod2;
14468
14469 const A: u32 = 42;
14470 const B: u32 = 42;
14471 const C: u32 = 43ˇ
14472 const D: u32 = 42;
14473
14474
14475 fn main() {
14476 println!("hello");
14477
14478 println!("world");
14479 }"#
14480 .unindent(),
14481 );
14482
14483 cx.set_head_text(&diff_base);
14484 executor.run_until_parked();
14485 cx.update_editor(|editor, window, cx| {
14486 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14487 });
14488 executor.run_until_parked();
14489
14490 cx.assert_state_with_diff(
14491 r#"
14492 use some::mod1;
14493 use some::mod2;
14494
14495 const A: u32 = 42;
14496 const B: u32 = 42;
14497 - const C: u32 = 42;
14498 + const C: u32 = 43ˇ
14499 const D: u32 = 42;
14500
14501
14502 fn main() {
14503 println!("hello");
14504
14505 println!("world");
14506 }"#
14507 .unindent(),
14508 );
14509
14510 cx.update_editor(|editor, window, cx| {
14511 editor.handle_input("\nnew_line\n", 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 + new_line
14525 + ˇ
14526 const D: u32 = 42;
14527
14528
14529 fn main() {
14530 println!("hello");
14531
14532 println!("world");
14533 }"#
14534 .unindent(),
14535 );
14536}
14537
14538#[gpui::test]
14539async fn test_stage_and_unstage_added_file_hunk(
14540 executor: BackgroundExecutor,
14541 cx: &mut TestAppContext,
14542) {
14543 init_test(cx, |_| {});
14544
14545 let mut cx = EditorTestContext::new(cx).await;
14546 cx.update_editor(|editor, _, cx| {
14547 editor.set_expand_all_diff_hunks(cx);
14548 });
14549
14550 let working_copy = r#"
14551 ˇfn main() {
14552 println!("hello, world!");
14553 }
14554 "#
14555 .unindent();
14556
14557 cx.set_state(&working_copy);
14558 executor.run_until_parked();
14559
14560 cx.assert_state_with_diff(
14561 r#"
14562 + ˇfn main() {
14563 + println!("hello, world!");
14564 + }
14565 "#
14566 .unindent(),
14567 );
14568 cx.assert_index_text(None);
14569
14570 cx.update_editor(|editor, window, cx| {
14571 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14572 });
14573 executor.run_until_parked();
14574 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14575 cx.assert_state_with_diff(
14576 r#"
14577 + ˇfn main() {
14578 + println!("hello, world!");
14579 + }
14580 "#
14581 .unindent(),
14582 );
14583
14584 cx.update_editor(|editor, window, cx| {
14585 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14586 });
14587 executor.run_until_parked();
14588 cx.assert_index_text(None);
14589}
14590
14591async fn setup_indent_guides_editor(
14592 text: &str,
14593 cx: &mut TestAppContext,
14594) -> (BufferId, EditorTestContext) {
14595 init_test(cx, |_| {});
14596
14597 let mut cx = EditorTestContext::new(cx).await;
14598
14599 let buffer_id = cx.update_editor(|editor, window, cx| {
14600 editor.set_text(text, window, cx);
14601 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14602
14603 buffer_ids[0]
14604 });
14605
14606 (buffer_id, cx)
14607}
14608
14609fn assert_indent_guides(
14610 range: Range<u32>,
14611 expected: Vec<IndentGuide>,
14612 active_indices: Option<Vec<usize>>,
14613 cx: &mut EditorTestContext,
14614) {
14615 let indent_guides = cx.update_editor(|editor, window, cx| {
14616 let snapshot = editor.snapshot(window, cx).display_snapshot;
14617 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14618 editor,
14619 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14620 true,
14621 &snapshot,
14622 cx,
14623 );
14624
14625 indent_guides.sort_by(|a, b| {
14626 a.depth.cmp(&b.depth).then(
14627 a.start_row
14628 .cmp(&b.start_row)
14629 .then(a.end_row.cmp(&b.end_row)),
14630 )
14631 });
14632 indent_guides
14633 });
14634
14635 if let Some(expected) = active_indices {
14636 let active_indices = cx.update_editor(|editor, window, cx| {
14637 let snapshot = editor.snapshot(window, cx).display_snapshot;
14638 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14639 });
14640
14641 assert_eq!(
14642 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14643 expected,
14644 "Active indent guide indices do not match"
14645 );
14646 }
14647
14648 assert_eq!(indent_guides, expected, "Indent guides do not match");
14649}
14650
14651fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14652 IndentGuide {
14653 buffer_id,
14654 start_row: MultiBufferRow(start_row),
14655 end_row: MultiBufferRow(end_row),
14656 depth,
14657 tab_size: 4,
14658 settings: IndentGuideSettings {
14659 enabled: true,
14660 line_width: 1,
14661 active_line_width: 1,
14662 ..Default::default()
14663 },
14664 }
14665}
14666
14667#[gpui::test]
14668async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14669 let (buffer_id, mut cx) = setup_indent_guides_editor(
14670 &"
14671 fn main() {
14672 let a = 1;
14673 }"
14674 .unindent(),
14675 cx,
14676 )
14677 .await;
14678
14679 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14680}
14681
14682#[gpui::test]
14683async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14684 let (buffer_id, mut cx) = setup_indent_guides_editor(
14685 &"
14686 fn main() {
14687 let a = 1;
14688 let b = 2;
14689 }"
14690 .unindent(),
14691 cx,
14692 )
14693 .await;
14694
14695 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14696}
14697
14698#[gpui::test]
14699async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14700 let (buffer_id, mut cx) = setup_indent_guides_editor(
14701 &"
14702 fn main() {
14703 let a = 1;
14704 if a == 3 {
14705 let b = 2;
14706 } else {
14707 let c = 3;
14708 }
14709 }"
14710 .unindent(),
14711 cx,
14712 )
14713 .await;
14714
14715 assert_indent_guides(
14716 0..8,
14717 vec![
14718 indent_guide(buffer_id, 1, 6, 0),
14719 indent_guide(buffer_id, 3, 3, 1),
14720 indent_guide(buffer_id, 5, 5, 1),
14721 ],
14722 None,
14723 &mut cx,
14724 );
14725}
14726
14727#[gpui::test]
14728async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14729 let (buffer_id, mut cx) = setup_indent_guides_editor(
14730 &"
14731 fn main() {
14732 let a = 1;
14733 let b = 2;
14734 let c = 3;
14735 }"
14736 .unindent(),
14737 cx,
14738 )
14739 .await;
14740
14741 assert_indent_guides(
14742 0..5,
14743 vec![
14744 indent_guide(buffer_id, 1, 3, 0),
14745 indent_guide(buffer_id, 2, 2, 1),
14746 ],
14747 None,
14748 &mut cx,
14749 );
14750}
14751
14752#[gpui::test]
14753async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14754 let (buffer_id, mut cx) = setup_indent_guides_editor(
14755 &"
14756 fn main() {
14757 let a = 1;
14758
14759 let c = 3;
14760 }"
14761 .unindent(),
14762 cx,
14763 )
14764 .await;
14765
14766 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14767}
14768
14769#[gpui::test]
14770async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14771 let (buffer_id, mut cx) = setup_indent_guides_editor(
14772 &"
14773 fn main() {
14774 let a = 1;
14775
14776 let c = 3;
14777
14778 if a == 3 {
14779 let b = 2;
14780 } else {
14781 let c = 3;
14782 }
14783 }"
14784 .unindent(),
14785 cx,
14786 )
14787 .await;
14788
14789 assert_indent_guides(
14790 0..11,
14791 vec![
14792 indent_guide(buffer_id, 1, 9, 0),
14793 indent_guide(buffer_id, 6, 6, 1),
14794 indent_guide(buffer_id, 8, 8, 1),
14795 ],
14796 None,
14797 &mut cx,
14798 );
14799}
14800
14801#[gpui::test]
14802async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14803 let (buffer_id, mut cx) = setup_indent_guides_editor(
14804 &"
14805 fn main() {
14806 let a = 1;
14807
14808 let c = 3;
14809
14810 if a == 3 {
14811 let b = 2;
14812 } else {
14813 let c = 3;
14814 }
14815 }"
14816 .unindent(),
14817 cx,
14818 )
14819 .await;
14820
14821 assert_indent_guides(
14822 1..11,
14823 vec![
14824 indent_guide(buffer_id, 1, 9, 0),
14825 indent_guide(buffer_id, 6, 6, 1),
14826 indent_guide(buffer_id, 8, 8, 1),
14827 ],
14828 None,
14829 &mut cx,
14830 );
14831}
14832
14833#[gpui::test]
14834async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14835 let (buffer_id, mut cx) = setup_indent_guides_editor(
14836 &"
14837 fn main() {
14838 let a = 1;
14839
14840 let c = 3;
14841
14842 if a == 3 {
14843 let b = 2;
14844 } else {
14845 let c = 3;
14846 }
14847 }"
14848 .unindent(),
14849 cx,
14850 )
14851 .await;
14852
14853 assert_indent_guides(
14854 1..10,
14855 vec![
14856 indent_guide(buffer_id, 1, 9, 0),
14857 indent_guide(buffer_id, 6, 6, 1),
14858 indent_guide(buffer_id, 8, 8, 1),
14859 ],
14860 None,
14861 &mut cx,
14862 );
14863}
14864
14865#[gpui::test]
14866async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14867 let (buffer_id, mut cx) = setup_indent_guides_editor(
14868 &"
14869 block1
14870 block2
14871 block3
14872 block4
14873 block2
14874 block1
14875 block1"
14876 .unindent(),
14877 cx,
14878 )
14879 .await;
14880
14881 assert_indent_guides(
14882 1..10,
14883 vec![
14884 indent_guide(buffer_id, 1, 4, 0),
14885 indent_guide(buffer_id, 2, 3, 1),
14886 indent_guide(buffer_id, 3, 3, 2),
14887 ],
14888 None,
14889 &mut cx,
14890 );
14891}
14892
14893#[gpui::test]
14894async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14895 let (buffer_id, mut cx) = setup_indent_guides_editor(
14896 &"
14897 block1
14898 block2
14899 block3
14900
14901 block1
14902 block1"
14903 .unindent(),
14904 cx,
14905 )
14906 .await;
14907
14908 assert_indent_guides(
14909 0..6,
14910 vec![
14911 indent_guide(buffer_id, 1, 2, 0),
14912 indent_guide(buffer_id, 2, 2, 1),
14913 ],
14914 None,
14915 &mut cx,
14916 );
14917}
14918
14919#[gpui::test]
14920async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14921 let (buffer_id, mut cx) = setup_indent_guides_editor(
14922 &"
14923 block1
14924
14925
14926
14927 block2
14928 "
14929 .unindent(),
14930 cx,
14931 )
14932 .await;
14933
14934 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14935}
14936
14937#[gpui::test]
14938async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14939 let (buffer_id, mut cx) = setup_indent_guides_editor(
14940 &"
14941 def a:
14942 \tb = 3
14943 \tif True:
14944 \t\tc = 4
14945 \t\td = 5
14946 \tprint(b)
14947 "
14948 .unindent(),
14949 cx,
14950 )
14951 .await;
14952
14953 assert_indent_guides(
14954 0..6,
14955 vec![
14956 indent_guide(buffer_id, 1, 6, 0),
14957 indent_guide(buffer_id, 3, 4, 1),
14958 ],
14959 None,
14960 &mut cx,
14961 );
14962}
14963
14964#[gpui::test]
14965async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14966 let (buffer_id, mut cx) = setup_indent_guides_editor(
14967 &"
14968 fn main() {
14969 let a = 1;
14970 }"
14971 .unindent(),
14972 cx,
14973 )
14974 .await;
14975
14976 cx.update_editor(|editor, window, cx| {
14977 editor.change_selections(None, window, cx, |s| {
14978 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14979 });
14980 });
14981
14982 assert_indent_guides(
14983 0..3,
14984 vec![indent_guide(buffer_id, 1, 1, 0)],
14985 Some(vec![0]),
14986 &mut cx,
14987 );
14988}
14989
14990#[gpui::test]
14991async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14992 let (buffer_id, mut cx) = setup_indent_guides_editor(
14993 &"
14994 fn main() {
14995 if 1 == 2 {
14996 let a = 1;
14997 }
14998 }"
14999 .unindent(),
15000 cx,
15001 )
15002 .await;
15003
15004 cx.update_editor(|editor, window, cx| {
15005 editor.change_selections(None, window, cx, |s| {
15006 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15007 });
15008 });
15009
15010 assert_indent_guides(
15011 0..4,
15012 vec![
15013 indent_guide(buffer_id, 1, 3, 0),
15014 indent_guide(buffer_id, 2, 2, 1),
15015 ],
15016 Some(vec![1]),
15017 &mut cx,
15018 );
15019
15020 cx.update_editor(|editor, window, cx| {
15021 editor.change_selections(None, window, cx, |s| {
15022 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15023 });
15024 });
15025
15026 assert_indent_guides(
15027 0..4,
15028 vec![
15029 indent_guide(buffer_id, 1, 3, 0),
15030 indent_guide(buffer_id, 2, 2, 1),
15031 ],
15032 Some(vec![1]),
15033 &mut cx,
15034 );
15035
15036 cx.update_editor(|editor, window, cx| {
15037 editor.change_selections(None, window, cx, |s| {
15038 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15039 });
15040 });
15041
15042 assert_indent_guides(
15043 0..4,
15044 vec![
15045 indent_guide(buffer_id, 1, 3, 0),
15046 indent_guide(buffer_id, 2, 2, 1),
15047 ],
15048 Some(vec![0]),
15049 &mut cx,
15050 );
15051}
15052
15053#[gpui::test]
15054async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15055 let (buffer_id, mut cx) = setup_indent_guides_editor(
15056 &"
15057 fn main() {
15058 let a = 1;
15059
15060 let b = 2;
15061 }"
15062 .unindent(),
15063 cx,
15064 )
15065 .await;
15066
15067 cx.update_editor(|editor, window, cx| {
15068 editor.change_selections(None, window, cx, |s| {
15069 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15070 });
15071 });
15072
15073 assert_indent_guides(
15074 0..5,
15075 vec![indent_guide(buffer_id, 1, 3, 0)],
15076 Some(vec![0]),
15077 &mut cx,
15078 );
15079}
15080
15081#[gpui::test]
15082async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15083 let (buffer_id, mut cx) = setup_indent_guides_editor(
15084 &"
15085 def m:
15086 a = 1
15087 pass"
15088 .unindent(),
15089 cx,
15090 )
15091 .await;
15092
15093 cx.update_editor(|editor, window, cx| {
15094 editor.change_selections(None, window, cx, |s| {
15095 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15096 });
15097 });
15098
15099 assert_indent_guides(
15100 0..3,
15101 vec![indent_guide(buffer_id, 1, 2, 0)],
15102 Some(vec![0]),
15103 &mut cx,
15104 );
15105}
15106
15107#[gpui::test]
15108async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15109 init_test(cx, |_| {});
15110 let mut cx = EditorTestContext::new(cx).await;
15111 let text = indoc! {
15112 "
15113 impl A {
15114 fn b() {
15115 0;
15116 3;
15117 5;
15118 6;
15119 7;
15120 }
15121 }
15122 "
15123 };
15124 let base_text = indoc! {
15125 "
15126 impl A {
15127 fn b() {
15128 0;
15129 1;
15130 2;
15131 3;
15132 4;
15133 }
15134 fn c() {
15135 5;
15136 6;
15137 7;
15138 }
15139 }
15140 "
15141 };
15142
15143 cx.update_editor(|editor, window, cx| {
15144 editor.set_text(text, window, cx);
15145
15146 editor.buffer().update(cx, |multibuffer, cx| {
15147 let buffer = multibuffer.as_singleton().unwrap();
15148 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15149
15150 multibuffer.set_all_diff_hunks_expanded(cx);
15151 multibuffer.add_diff(diff, cx);
15152
15153 buffer.read(cx).remote_id()
15154 })
15155 });
15156 cx.run_until_parked();
15157
15158 cx.assert_state_with_diff(
15159 indoc! { "
15160 impl A {
15161 fn b() {
15162 0;
15163 - 1;
15164 - 2;
15165 3;
15166 - 4;
15167 - }
15168 - fn c() {
15169 5;
15170 6;
15171 7;
15172 }
15173 }
15174 ˇ"
15175 }
15176 .to_string(),
15177 );
15178
15179 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15180 editor
15181 .snapshot(window, cx)
15182 .buffer_snapshot
15183 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15184 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15185 .collect::<Vec<_>>()
15186 });
15187 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15188 assert_eq!(
15189 actual_guides,
15190 vec![
15191 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15192 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15193 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15194 ]
15195 );
15196}
15197
15198#[gpui::test]
15199async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15200 init_test(cx, |_| {});
15201 let mut cx = EditorTestContext::new(cx).await;
15202
15203 let diff_base = r#"
15204 a
15205 b
15206 c
15207 "#
15208 .unindent();
15209
15210 cx.set_state(
15211 &r#"
15212 ˇA
15213 b
15214 C
15215 "#
15216 .unindent(),
15217 );
15218 cx.set_head_text(&diff_base);
15219 cx.update_editor(|editor, window, cx| {
15220 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15221 });
15222 executor.run_until_parked();
15223
15224 let both_hunks_expanded = r#"
15225 - a
15226 + ˇA
15227 b
15228 - c
15229 + C
15230 "#
15231 .unindent();
15232
15233 cx.assert_state_with_diff(both_hunks_expanded.clone());
15234
15235 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15236 let snapshot = editor.snapshot(window, cx);
15237 let hunks = editor
15238 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15239 .collect::<Vec<_>>();
15240 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15241 let buffer_id = hunks[0].buffer_id;
15242 hunks
15243 .into_iter()
15244 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15245 .collect::<Vec<_>>()
15246 });
15247 assert_eq!(hunk_ranges.len(), 2);
15248
15249 cx.update_editor(|editor, _, cx| {
15250 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15251 });
15252 executor.run_until_parked();
15253
15254 let second_hunk_expanded = r#"
15255 ˇA
15256 b
15257 - c
15258 + C
15259 "#
15260 .unindent();
15261
15262 cx.assert_state_with_diff(second_hunk_expanded);
15263
15264 cx.update_editor(|editor, _, cx| {
15265 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15266 });
15267 executor.run_until_parked();
15268
15269 cx.assert_state_with_diff(both_hunks_expanded.clone());
15270
15271 cx.update_editor(|editor, _, cx| {
15272 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15273 });
15274 executor.run_until_parked();
15275
15276 let first_hunk_expanded = r#"
15277 - a
15278 + ˇA
15279 b
15280 C
15281 "#
15282 .unindent();
15283
15284 cx.assert_state_with_diff(first_hunk_expanded);
15285
15286 cx.update_editor(|editor, _, cx| {
15287 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15288 });
15289 executor.run_until_parked();
15290
15291 cx.assert_state_with_diff(both_hunks_expanded);
15292
15293 cx.set_state(
15294 &r#"
15295 ˇA
15296 b
15297 "#
15298 .unindent(),
15299 );
15300 cx.run_until_parked();
15301
15302 // TODO this cursor position seems bad
15303 cx.assert_state_with_diff(
15304 r#"
15305 - ˇa
15306 + A
15307 b
15308 "#
15309 .unindent(),
15310 );
15311
15312 cx.update_editor(|editor, window, cx| {
15313 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15314 });
15315
15316 cx.assert_state_with_diff(
15317 r#"
15318 - ˇa
15319 + A
15320 b
15321 - c
15322 "#
15323 .unindent(),
15324 );
15325
15326 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15327 let snapshot = editor.snapshot(window, cx);
15328 let hunks = editor
15329 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15330 .collect::<Vec<_>>();
15331 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15332 let buffer_id = hunks[0].buffer_id;
15333 hunks
15334 .into_iter()
15335 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15336 .collect::<Vec<_>>()
15337 });
15338 assert_eq!(hunk_ranges.len(), 2);
15339
15340 cx.update_editor(|editor, _, cx| {
15341 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15342 });
15343 executor.run_until_parked();
15344
15345 cx.assert_state_with_diff(
15346 r#"
15347 - ˇa
15348 + A
15349 b
15350 "#
15351 .unindent(),
15352 );
15353}
15354
15355#[gpui::test]
15356async fn test_toggle_deletion_hunk_at_start_of_file(
15357 executor: BackgroundExecutor,
15358 cx: &mut TestAppContext,
15359) {
15360 init_test(cx, |_| {});
15361 let mut cx = EditorTestContext::new(cx).await;
15362
15363 let diff_base = r#"
15364 a
15365 b
15366 c
15367 "#
15368 .unindent();
15369
15370 cx.set_state(
15371 &r#"
15372 ˇb
15373 c
15374 "#
15375 .unindent(),
15376 );
15377 cx.set_head_text(&diff_base);
15378 cx.update_editor(|editor, window, cx| {
15379 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15380 });
15381 executor.run_until_parked();
15382
15383 let hunk_expanded = r#"
15384 - a
15385 ˇb
15386 c
15387 "#
15388 .unindent();
15389
15390 cx.assert_state_with_diff(hunk_expanded.clone());
15391
15392 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15393 let snapshot = editor.snapshot(window, cx);
15394 let hunks = editor
15395 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15396 .collect::<Vec<_>>();
15397 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15398 let buffer_id = hunks[0].buffer_id;
15399 hunks
15400 .into_iter()
15401 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15402 .collect::<Vec<_>>()
15403 });
15404 assert_eq!(hunk_ranges.len(), 1);
15405
15406 cx.update_editor(|editor, _, cx| {
15407 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15408 });
15409 executor.run_until_parked();
15410
15411 let hunk_collapsed = r#"
15412 ˇb
15413 c
15414 "#
15415 .unindent();
15416
15417 cx.assert_state_with_diff(hunk_collapsed);
15418
15419 cx.update_editor(|editor, _, cx| {
15420 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15421 });
15422 executor.run_until_parked();
15423
15424 cx.assert_state_with_diff(hunk_expanded.clone());
15425}
15426
15427#[gpui::test]
15428async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15429 init_test(cx, |_| {});
15430
15431 let fs = FakeFs::new(cx.executor());
15432 fs.insert_tree(
15433 path!("/test"),
15434 json!({
15435 ".git": {},
15436 "file-1": "ONE\n",
15437 "file-2": "TWO\n",
15438 "file-3": "THREE\n",
15439 }),
15440 )
15441 .await;
15442
15443 fs.set_head_for_repo(
15444 path!("/test/.git").as_ref(),
15445 &[
15446 ("file-1".into(), "one\n".into()),
15447 ("file-2".into(), "two\n".into()),
15448 ("file-3".into(), "three\n".into()),
15449 ],
15450 );
15451
15452 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15453 let mut buffers = vec![];
15454 for i in 1..=3 {
15455 let buffer = project
15456 .update(cx, |project, cx| {
15457 let path = format!(path!("/test/file-{}"), i);
15458 project.open_local_buffer(path, cx)
15459 })
15460 .await
15461 .unwrap();
15462 buffers.push(buffer);
15463 }
15464
15465 let multibuffer = cx.new(|cx| {
15466 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15467 multibuffer.set_all_diff_hunks_expanded(cx);
15468 for buffer in &buffers {
15469 let snapshot = buffer.read(cx).snapshot();
15470 multibuffer.set_excerpts_for_path(
15471 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15472 buffer.clone(),
15473 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15474 DEFAULT_MULTIBUFFER_CONTEXT,
15475 cx,
15476 );
15477 }
15478 multibuffer
15479 });
15480
15481 let editor = cx.add_window(|window, cx| {
15482 Editor::new(
15483 EditorMode::Full,
15484 multibuffer,
15485 Some(project),
15486 true,
15487 window,
15488 cx,
15489 )
15490 });
15491 cx.run_until_parked();
15492
15493 let snapshot = editor
15494 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15495 .unwrap();
15496 let hunks = snapshot
15497 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15498 .map(|hunk| match hunk {
15499 DisplayDiffHunk::Unfolded {
15500 display_row_range, ..
15501 } => display_row_range,
15502 DisplayDiffHunk::Folded { .. } => unreachable!(),
15503 })
15504 .collect::<Vec<_>>();
15505 assert_eq!(
15506 hunks,
15507 [
15508 DisplayRow(3)..DisplayRow(5),
15509 DisplayRow(10)..DisplayRow(12),
15510 DisplayRow(17)..DisplayRow(19),
15511 ]
15512 );
15513}
15514
15515#[gpui::test]
15516async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15517 init_test(cx, |_| {});
15518
15519 let mut cx = EditorTestContext::new(cx).await;
15520 cx.set_head_text(indoc! { "
15521 one
15522 two
15523 three
15524 four
15525 five
15526 "
15527 });
15528 cx.set_index_text(indoc! { "
15529 one
15530 two
15531 three
15532 four
15533 five
15534 "
15535 });
15536 cx.set_state(indoc! {"
15537 one
15538 TWO
15539 ˇTHREE
15540 FOUR
15541 five
15542 "});
15543 cx.run_until_parked();
15544 cx.update_editor(|editor, window, cx| {
15545 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15546 });
15547 cx.run_until_parked();
15548 cx.assert_index_text(Some(indoc! {"
15549 one
15550 TWO
15551 THREE
15552 FOUR
15553 five
15554 "}));
15555 cx.set_state(indoc! { "
15556 one
15557 TWO
15558 ˇTHREE-HUNDRED
15559 FOUR
15560 five
15561 "});
15562 cx.run_until_parked();
15563 cx.update_editor(|editor, window, cx| {
15564 let snapshot = editor.snapshot(window, cx);
15565 let hunks = editor
15566 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15567 .collect::<Vec<_>>();
15568 assert_eq!(hunks.len(), 1);
15569 assert_eq!(
15570 hunks[0].status(),
15571 DiffHunkStatus {
15572 kind: DiffHunkStatusKind::Modified,
15573 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15574 }
15575 );
15576
15577 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15578 });
15579 cx.run_until_parked();
15580 cx.assert_index_text(Some(indoc! {"
15581 one
15582 TWO
15583 THREE-HUNDRED
15584 FOUR
15585 five
15586 "}));
15587}
15588
15589#[gpui::test]
15590fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15591 init_test(cx, |_| {});
15592
15593 let editor = cx.add_window(|window, cx| {
15594 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15595 build_editor(buffer, window, cx)
15596 });
15597
15598 let render_args = Arc::new(Mutex::new(None));
15599 let snapshot = editor
15600 .update(cx, |editor, window, cx| {
15601 let snapshot = editor.buffer().read(cx).snapshot(cx);
15602 let range =
15603 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15604
15605 struct RenderArgs {
15606 row: MultiBufferRow,
15607 folded: bool,
15608 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15609 }
15610
15611 let crease = Crease::inline(
15612 range,
15613 FoldPlaceholder::test(),
15614 {
15615 let toggle_callback = render_args.clone();
15616 move |row, folded, callback, _window, _cx| {
15617 *toggle_callback.lock() = Some(RenderArgs {
15618 row,
15619 folded,
15620 callback,
15621 });
15622 div()
15623 }
15624 },
15625 |_row, _folded, _window, _cx| div(),
15626 );
15627
15628 editor.insert_creases(Some(crease), cx);
15629 let snapshot = editor.snapshot(window, cx);
15630 let _div = snapshot.render_crease_toggle(
15631 MultiBufferRow(1),
15632 false,
15633 cx.entity().clone(),
15634 window,
15635 cx,
15636 );
15637 snapshot
15638 })
15639 .unwrap();
15640
15641 let render_args = render_args.lock().take().unwrap();
15642 assert_eq!(render_args.row, MultiBufferRow(1));
15643 assert!(!render_args.folded);
15644 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15645
15646 cx.update_window(*editor, |_, window, cx| {
15647 (render_args.callback)(true, window, cx)
15648 })
15649 .unwrap();
15650 let snapshot = editor
15651 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15652 .unwrap();
15653 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15654
15655 cx.update_window(*editor, |_, window, cx| {
15656 (render_args.callback)(false, window, cx)
15657 })
15658 .unwrap();
15659 let snapshot = editor
15660 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15661 .unwrap();
15662 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15663}
15664
15665#[gpui::test]
15666async fn test_input_text(cx: &mut TestAppContext) {
15667 init_test(cx, |_| {});
15668 let mut cx = EditorTestContext::new(cx).await;
15669
15670 cx.set_state(
15671 &r#"ˇone
15672 two
15673
15674 three
15675 fourˇ
15676 five
15677
15678 siˇx"#
15679 .unindent(),
15680 );
15681
15682 cx.dispatch_action(HandleInput(String::new()));
15683 cx.assert_editor_state(
15684 &r#"ˇone
15685 two
15686
15687 three
15688 fourˇ
15689 five
15690
15691 siˇx"#
15692 .unindent(),
15693 );
15694
15695 cx.dispatch_action(HandleInput("AAAA".to_string()));
15696 cx.assert_editor_state(
15697 &r#"AAAAˇone
15698 two
15699
15700 three
15701 fourAAAAˇ
15702 five
15703
15704 siAAAAˇx"#
15705 .unindent(),
15706 );
15707}
15708
15709#[gpui::test]
15710async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15711 init_test(cx, |_| {});
15712
15713 let mut cx = EditorTestContext::new(cx).await;
15714 cx.set_state(
15715 r#"let foo = 1;
15716let foo = 2;
15717let foo = 3;
15718let fooˇ = 4;
15719let foo = 5;
15720let foo = 6;
15721let foo = 7;
15722let foo = 8;
15723let foo = 9;
15724let foo = 10;
15725let foo = 11;
15726let foo = 12;
15727let foo = 13;
15728let foo = 14;
15729let foo = 15;"#,
15730 );
15731
15732 cx.update_editor(|e, window, cx| {
15733 assert_eq!(
15734 e.next_scroll_position,
15735 NextScrollCursorCenterTopBottom::Center,
15736 "Default next scroll direction is center",
15737 );
15738
15739 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15740 assert_eq!(
15741 e.next_scroll_position,
15742 NextScrollCursorCenterTopBottom::Top,
15743 "After center, next scroll direction should be top",
15744 );
15745
15746 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15747 assert_eq!(
15748 e.next_scroll_position,
15749 NextScrollCursorCenterTopBottom::Bottom,
15750 "After top, next scroll direction should be bottom",
15751 );
15752
15753 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15754 assert_eq!(
15755 e.next_scroll_position,
15756 NextScrollCursorCenterTopBottom::Center,
15757 "After bottom, scrolling should start over",
15758 );
15759
15760 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15761 assert_eq!(
15762 e.next_scroll_position,
15763 NextScrollCursorCenterTopBottom::Top,
15764 "Scrolling continues if retriggered fast enough"
15765 );
15766 });
15767
15768 cx.executor()
15769 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15770 cx.executor().run_until_parked();
15771 cx.update_editor(|e, _, _| {
15772 assert_eq!(
15773 e.next_scroll_position,
15774 NextScrollCursorCenterTopBottom::Center,
15775 "If scrolling is not triggered fast enough, it should reset"
15776 );
15777 });
15778}
15779
15780#[gpui::test]
15781async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15782 init_test(cx, |_| {});
15783 let mut cx = EditorLspTestContext::new_rust(
15784 lsp::ServerCapabilities {
15785 definition_provider: Some(lsp::OneOf::Left(true)),
15786 references_provider: Some(lsp::OneOf::Left(true)),
15787 ..lsp::ServerCapabilities::default()
15788 },
15789 cx,
15790 )
15791 .await;
15792
15793 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15794 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15795 move |params, _| async move {
15796 if empty_go_to_definition {
15797 Ok(None)
15798 } else {
15799 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15800 uri: params.text_document_position_params.text_document.uri,
15801 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15802 })))
15803 }
15804 },
15805 );
15806 let references =
15807 cx.lsp
15808 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15809 Ok(Some(vec![lsp::Location {
15810 uri: params.text_document_position.text_document.uri,
15811 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15812 }]))
15813 });
15814 (go_to_definition, references)
15815 };
15816
15817 cx.set_state(
15818 &r#"fn one() {
15819 let mut a = ˇtwo();
15820 }
15821
15822 fn two() {}"#
15823 .unindent(),
15824 );
15825 set_up_lsp_handlers(false, &mut cx);
15826 let navigated = cx
15827 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15828 .await
15829 .expect("Failed to navigate to definition");
15830 assert_eq!(
15831 navigated,
15832 Navigated::Yes,
15833 "Should have navigated to definition from the GetDefinition response"
15834 );
15835 cx.assert_editor_state(
15836 &r#"fn one() {
15837 let mut a = two();
15838 }
15839
15840 fn «twoˇ»() {}"#
15841 .unindent(),
15842 );
15843
15844 let editors = cx.update_workspace(|workspace, _, cx| {
15845 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15846 });
15847 cx.update_editor(|_, _, test_editor_cx| {
15848 assert_eq!(
15849 editors.len(),
15850 1,
15851 "Initially, only one, test, editor should be open in the workspace"
15852 );
15853 assert_eq!(
15854 test_editor_cx.entity(),
15855 editors.last().expect("Asserted len is 1").clone()
15856 );
15857 });
15858
15859 set_up_lsp_handlers(true, &mut cx);
15860 let navigated = cx
15861 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15862 .await
15863 .expect("Failed to navigate to lookup references");
15864 assert_eq!(
15865 navigated,
15866 Navigated::Yes,
15867 "Should have navigated to references as a fallback after empty GoToDefinition response"
15868 );
15869 // We should not change the selections in the existing file,
15870 // if opening another milti buffer with the references
15871 cx.assert_editor_state(
15872 &r#"fn one() {
15873 let mut a = two();
15874 }
15875
15876 fn «twoˇ»() {}"#
15877 .unindent(),
15878 );
15879 let editors = cx.update_workspace(|workspace, _, cx| {
15880 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15881 });
15882 cx.update_editor(|_, _, test_editor_cx| {
15883 assert_eq!(
15884 editors.len(),
15885 2,
15886 "After falling back to references search, we open a new editor with the results"
15887 );
15888 let references_fallback_text = editors
15889 .into_iter()
15890 .find(|new_editor| *new_editor != test_editor_cx.entity())
15891 .expect("Should have one non-test editor now")
15892 .read(test_editor_cx)
15893 .text(test_editor_cx);
15894 assert_eq!(
15895 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15896 "Should use the range from the references response and not the GoToDefinition one"
15897 );
15898 });
15899}
15900
15901#[gpui::test]
15902async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15903 init_test(cx, |_| {});
15904
15905 let language = Arc::new(Language::new(
15906 LanguageConfig::default(),
15907 Some(tree_sitter_rust::LANGUAGE.into()),
15908 ));
15909
15910 let text = r#"
15911 #[cfg(test)]
15912 mod tests() {
15913 #[test]
15914 fn runnable_1() {
15915 let a = 1;
15916 }
15917
15918 #[test]
15919 fn runnable_2() {
15920 let a = 1;
15921 let b = 2;
15922 }
15923 }
15924 "#
15925 .unindent();
15926
15927 let fs = FakeFs::new(cx.executor());
15928 fs.insert_file("/file.rs", Default::default()).await;
15929
15930 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15931 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15932 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15933 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15934 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15935
15936 let editor = cx.new_window_entity(|window, cx| {
15937 Editor::new(
15938 EditorMode::Full,
15939 multi_buffer,
15940 Some(project.clone()),
15941 true,
15942 window,
15943 cx,
15944 )
15945 });
15946
15947 editor.update_in(cx, |editor, window, cx| {
15948 let snapshot = editor.buffer().read(cx).snapshot(cx);
15949 editor.tasks.insert(
15950 (buffer.read(cx).remote_id(), 3),
15951 RunnableTasks {
15952 templates: vec![],
15953 offset: snapshot.anchor_before(43),
15954 column: 0,
15955 extra_variables: HashMap::default(),
15956 context_range: BufferOffset(43)..BufferOffset(85),
15957 },
15958 );
15959 editor.tasks.insert(
15960 (buffer.read(cx).remote_id(), 8),
15961 RunnableTasks {
15962 templates: vec![],
15963 offset: snapshot.anchor_before(86),
15964 column: 0,
15965 extra_variables: HashMap::default(),
15966 context_range: BufferOffset(86)..BufferOffset(191),
15967 },
15968 );
15969
15970 // Test finding task when cursor is inside function body
15971 editor.change_selections(None, window, cx, |s| {
15972 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15973 });
15974 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15975 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15976
15977 // Test finding task when cursor is on function name
15978 editor.change_selections(None, window, cx, |s| {
15979 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15980 });
15981 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15982 assert_eq!(row, 8, "Should find task when cursor is on function name");
15983 });
15984}
15985
15986#[gpui::test]
15987async fn test_folding_buffers(cx: &mut TestAppContext) {
15988 init_test(cx, |_| {});
15989
15990 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15991 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15992 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
15993
15994 let fs = FakeFs::new(cx.executor());
15995 fs.insert_tree(
15996 path!("/a"),
15997 json!({
15998 "first.rs": sample_text_1,
15999 "second.rs": sample_text_2,
16000 "third.rs": sample_text_3,
16001 }),
16002 )
16003 .await;
16004 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16005 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16006 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16007 let worktree = project.update(cx, |project, cx| {
16008 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16009 assert_eq!(worktrees.len(), 1);
16010 worktrees.pop().unwrap()
16011 });
16012 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16013
16014 let buffer_1 = project
16015 .update(cx, |project, cx| {
16016 project.open_buffer((worktree_id, "first.rs"), cx)
16017 })
16018 .await
16019 .unwrap();
16020 let buffer_2 = project
16021 .update(cx, |project, cx| {
16022 project.open_buffer((worktree_id, "second.rs"), cx)
16023 })
16024 .await
16025 .unwrap();
16026 let buffer_3 = project
16027 .update(cx, |project, cx| {
16028 project.open_buffer((worktree_id, "third.rs"), cx)
16029 })
16030 .await
16031 .unwrap();
16032
16033 let multi_buffer = cx.new(|cx| {
16034 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16035 multi_buffer.push_excerpts(
16036 buffer_1.clone(),
16037 [
16038 ExcerptRange {
16039 context: Point::new(0, 0)..Point::new(3, 0),
16040 primary: None,
16041 },
16042 ExcerptRange {
16043 context: Point::new(5, 0)..Point::new(7, 0),
16044 primary: None,
16045 },
16046 ExcerptRange {
16047 context: Point::new(9, 0)..Point::new(10, 4),
16048 primary: None,
16049 },
16050 ],
16051 cx,
16052 );
16053 multi_buffer.push_excerpts(
16054 buffer_2.clone(),
16055 [
16056 ExcerptRange {
16057 context: Point::new(0, 0)..Point::new(3, 0),
16058 primary: None,
16059 },
16060 ExcerptRange {
16061 context: Point::new(5, 0)..Point::new(7, 0),
16062 primary: None,
16063 },
16064 ExcerptRange {
16065 context: Point::new(9, 0)..Point::new(10, 4),
16066 primary: None,
16067 },
16068 ],
16069 cx,
16070 );
16071 multi_buffer.push_excerpts(
16072 buffer_3.clone(),
16073 [
16074 ExcerptRange {
16075 context: Point::new(0, 0)..Point::new(3, 0),
16076 primary: None,
16077 },
16078 ExcerptRange {
16079 context: Point::new(5, 0)..Point::new(7, 0),
16080 primary: None,
16081 },
16082 ExcerptRange {
16083 context: Point::new(9, 0)..Point::new(10, 4),
16084 primary: None,
16085 },
16086 ],
16087 cx,
16088 );
16089 multi_buffer
16090 });
16091 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16092 Editor::new(
16093 EditorMode::Full,
16094 multi_buffer.clone(),
16095 Some(project.clone()),
16096 true,
16097 window,
16098 cx,
16099 )
16100 });
16101
16102 assert_eq!(
16103 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16104 "\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",
16105 );
16106
16107 multi_buffer_editor.update(cx, |editor, cx| {
16108 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16109 });
16110 assert_eq!(
16111 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16112 "\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",
16113 "After folding the first buffer, its text should not be displayed"
16114 );
16115
16116 multi_buffer_editor.update(cx, |editor, cx| {
16117 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16118 });
16119 assert_eq!(
16120 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16121 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16122 "After folding the second buffer, its text should not be displayed"
16123 );
16124
16125 multi_buffer_editor.update(cx, |editor, cx| {
16126 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16127 });
16128 assert_eq!(
16129 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16130 "\n\n\n\n\n",
16131 "After folding the third buffer, its text should not be displayed"
16132 );
16133
16134 // Emulate selection inside the fold logic, that should work
16135 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16136 editor
16137 .snapshot(window, cx)
16138 .next_line_boundary(Point::new(0, 4));
16139 });
16140
16141 multi_buffer_editor.update(cx, |editor, cx| {
16142 editor.unfold_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\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16147 "After unfolding the second buffer, its text should be displayed"
16148 );
16149
16150 // Typing inside of buffer 1 causes that buffer to be unfolded.
16151 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16152 assert_eq!(
16153 multi_buffer
16154 .read(cx)
16155 .snapshot(cx)
16156 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16157 .collect::<String>(),
16158 "bbbb"
16159 );
16160 editor.change_selections(None, window, cx, |selections| {
16161 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16162 });
16163 editor.handle_input("B", window, cx);
16164 });
16165
16166 assert_eq!(
16167 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16168 "\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",
16169 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16170 );
16171
16172 multi_buffer_editor.update(cx, |editor, cx| {
16173 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16174 });
16175 assert_eq!(
16176 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16177 "\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",
16178 "After unfolding the all buffers, all original text should be displayed"
16179 );
16180}
16181
16182#[gpui::test]
16183async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16184 init_test(cx, |_| {});
16185
16186 let sample_text_1 = "1111\n2222\n3333".to_string();
16187 let sample_text_2 = "4444\n5555\n6666".to_string();
16188 let sample_text_3 = "7777\n8888\n9999".to_string();
16189
16190 let fs = FakeFs::new(cx.executor());
16191 fs.insert_tree(
16192 path!("/a"),
16193 json!({
16194 "first.rs": sample_text_1,
16195 "second.rs": sample_text_2,
16196 "third.rs": sample_text_3,
16197 }),
16198 )
16199 .await;
16200 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16201 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16202 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16203 let worktree = project.update(cx, |project, cx| {
16204 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16205 assert_eq!(worktrees.len(), 1);
16206 worktrees.pop().unwrap()
16207 });
16208 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16209
16210 let buffer_1 = project
16211 .update(cx, |project, cx| {
16212 project.open_buffer((worktree_id, "first.rs"), cx)
16213 })
16214 .await
16215 .unwrap();
16216 let buffer_2 = project
16217 .update(cx, |project, cx| {
16218 project.open_buffer((worktree_id, "second.rs"), cx)
16219 })
16220 .await
16221 .unwrap();
16222 let buffer_3 = project
16223 .update(cx, |project, cx| {
16224 project.open_buffer((worktree_id, "third.rs"), cx)
16225 })
16226 .await
16227 .unwrap();
16228
16229 let multi_buffer = cx.new(|cx| {
16230 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16231 multi_buffer.push_excerpts(
16232 buffer_1.clone(),
16233 [ExcerptRange {
16234 context: Point::new(0, 0)..Point::new(3, 0),
16235 primary: None,
16236 }],
16237 cx,
16238 );
16239 multi_buffer.push_excerpts(
16240 buffer_2.clone(),
16241 [ExcerptRange {
16242 context: Point::new(0, 0)..Point::new(3, 0),
16243 primary: None,
16244 }],
16245 cx,
16246 );
16247 multi_buffer.push_excerpts(
16248 buffer_3.clone(),
16249 [ExcerptRange {
16250 context: Point::new(0, 0)..Point::new(3, 0),
16251 primary: None,
16252 }],
16253 cx,
16254 );
16255 multi_buffer
16256 });
16257
16258 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16259 Editor::new(
16260 EditorMode::Full,
16261 multi_buffer,
16262 Some(project.clone()),
16263 true,
16264 window,
16265 cx,
16266 )
16267 });
16268
16269 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16270 assert_eq!(
16271 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16272 full_text,
16273 );
16274
16275 multi_buffer_editor.update(cx, |editor, cx| {
16276 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16277 });
16278 assert_eq!(
16279 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16280 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16281 "After folding the first buffer, its text should not be displayed"
16282 );
16283
16284 multi_buffer_editor.update(cx, |editor, cx| {
16285 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16286 });
16287
16288 assert_eq!(
16289 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16290 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16291 "After folding the second buffer, its text should not be displayed"
16292 );
16293
16294 multi_buffer_editor.update(cx, |editor, cx| {
16295 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16296 });
16297 assert_eq!(
16298 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16299 "\n\n\n\n\n",
16300 "After folding the third buffer, its text should not be displayed"
16301 );
16302
16303 multi_buffer_editor.update(cx, |editor, cx| {
16304 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16305 });
16306 assert_eq!(
16307 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16308 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16309 "After unfolding the second buffer, its text should be displayed"
16310 );
16311
16312 multi_buffer_editor.update(cx, |editor, cx| {
16313 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16314 });
16315 assert_eq!(
16316 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16317 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16318 "After unfolding the first buffer, its text should be displayed"
16319 );
16320
16321 multi_buffer_editor.update(cx, |editor, cx| {
16322 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16323 });
16324 assert_eq!(
16325 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16326 full_text,
16327 "After unfolding all buffers, all original text should be displayed"
16328 );
16329}
16330
16331#[gpui::test]
16332async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16333 init_test(cx, |_| {});
16334
16335 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16336
16337 let fs = FakeFs::new(cx.executor());
16338 fs.insert_tree(
16339 path!("/a"),
16340 json!({
16341 "main.rs": sample_text,
16342 }),
16343 )
16344 .await;
16345 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16346 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16347 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16348 let worktree = project.update(cx, |project, cx| {
16349 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16350 assert_eq!(worktrees.len(), 1);
16351 worktrees.pop().unwrap()
16352 });
16353 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16354
16355 let buffer_1 = project
16356 .update(cx, |project, cx| {
16357 project.open_buffer((worktree_id, "main.rs"), cx)
16358 })
16359 .await
16360 .unwrap();
16361
16362 let multi_buffer = cx.new(|cx| {
16363 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16364 multi_buffer.push_excerpts(
16365 buffer_1.clone(),
16366 [ExcerptRange {
16367 context: Point::new(0, 0)
16368 ..Point::new(
16369 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16370 0,
16371 ),
16372 primary: None,
16373 }],
16374 cx,
16375 );
16376 multi_buffer
16377 });
16378 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16379 Editor::new(
16380 EditorMode::Full,
16381 multi_buffer,
16382 Some(project.clone()),
16383 true,
16384 window,
16385 cx,
16386 )
16387 });
16388
16389 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16390 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16391 enum TestHighlight {}
16392 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16393 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16394 editor.highlight_text::<TestHighlight>(
16395 vec![highlight_range.clone()],
16396 HighlightStyle::color(Hsla::green()),
16397 cx,
16398 );
16399 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16400 });
16401
16402 let full_text = format!("\n\n\n{sample_text}\n");
16403 assert_eq!(
16404 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16405 full_text,
16406 );
16407}
16408
16409#[gpui::test]
16410async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16411 init_test(cx, |_| {});
16412 cx.update(|cx| {
16413 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16414 "keymaps/default-linux.json",
16415 cx,
16416 )
16417 .unwrap();
16418 cx.bind_keys(default_key_bindings);
16419 });
16420
16421 let (editor, cx) = cx.add_window_view(|window, cx| {
16422 let multi_buffer = MultiBuffer::build_multi(
16423 [
16424 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16425 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16426 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16427 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16428 ],
16429 cx,
16430 );
16431 let mut editor = Editor::new(
16432 EditorMode::Full,
16433 multi_buffer.clone(),
16434 None,
16435 true,
16436 window,
16437 cx,
16438 );
16439
16440 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16441 // fold all but the second buffer, so that we test navigating between two
16442 // adjacent folded buffers, as well as folded buffers at the start and
16443 // end the multibuffer
16444 editor.fold_buffer(buffer_ids[0], cx);
16445 editor.fold_buffer(buffer_ids[2], cx);
16446 editor.fold_buffer(buffer_ids[3], cx);
16447
16448 editor
16449 });
16450 cx.simulate_resize(size(px(1000.), px(1000.)));
16451
16452 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16453 cx.assert_excerpts_with_selections(indoc! {"
16454 [EXCERPT]
16455 ˇ[FOLDED]
16456 [EXCERPT]
16457 a1
16458 b1
16459 [EXCERPT]
16460 [FOLDED]
16461 [EXCERPT]
16462 [FOLDED]
16463 "
16464 });
16465 cx.simulate_keystroke("down");
16466 cx.assert_excerpts_with_selections(indoc! {"
16467 [EXCERPT]
16468 [FOLDED]
16469 [EXCERPT]
16470 ˇa1
16471 b1
16472 [EXCERPT]
16473 [FOLDED]
16474 [EXCERPT]
16475 [FOLDED]
16476 "
16477 });
16478 cx.simulate_keystroke("down");
16479 cx.assert_excerpts_with_selections(indoc! {"
16480 [EXCERPT]
16481 [FOLDED]
16482 [EXCERPT]
16483 a1
16484 ˇb1
16485 [EXCERPT]
16486 [FOLDED]
16487 [EXCERPT]
16488 [FOLDED]
16489 "
16490 });
16491 cx.simulate_keystroke("down");
16492 cx.assert_excerpts_with_selections(indoc! {"
16493 [EXCERPT]
16494 [FOLDED]
16495 [EXCERPT]
16496 a1
16497 b1
16498 ˇ[EXCERPT]
16499 [FOLDED]
16500 [EXCERPT]
16501 [FOLDED]
16502 "
16503 });
16504 cx.simulate_keystroke("down");
16505 cx.assert_excerpts_with_selections(indoc! {"
16506 [EXCERPT]
16507 [FOLDED]
16508 [EXCERPT]
16509 a1
16510 b1
16511 [EXCERPT]
16512 ˇ[FOLDED]
16513 [EXCERPT]
16514 [FOLDED]
16515 "
16516 });
16517 for _ in 0..5 {
16518 cx.simulate_keystroke("down");
16519 cx.assert_excerpts_with_selections(indoc! {"
16520 [EXCERPT]
16521 [FOLDED]
16522 [EXCERPT]
16523 a1
16524 b1
16525 [EXCERPT]
16526 [FOLDED]
16527 [EXCERPT]
16528 ˇ[FOLDED]
16529 "
16530 });
16531 }
16532
16533 cx.simulate_keystroke("up");
16534 cx.assert_excerpts_with_selections(indoc! {"
16535 [EXCERPT]
16536 [FOLDED]
16537 [EXCERPT]
16538 a1
16539 b1
16540 [EXCERPT]
16541 ˇ[FOLDED]
16542 [EXCERPT]
16543 [FOLDED]
16544 "
16545 });
16546 cx.simulate_keystroke("up");
16547 cx.assert_excerpts_with_selections(indoc! {"
16548 [EXCERPT]
16549 [FOLDED]
16550 [EXCERPT]
16551 a1
16552 b1
16553 ˇ[EXCERPT]
16554 [FOLDED]
16555 [EXCERPT]
16556 [FOLDED]
16557 "
16558 });
16559 cx.simulate_keystroke("up");
16560 cx.assert_excerpts_with_selections(indoc! {"
16561 [EXCERPT]
16562 [FOLDED]
16563 [EXCERPT]
16564 a1
16565 ˇb1
16566 [EXCERPT]
16567 [FOLDED]
16568 [EXCERPT]
16569 [FOLDED]
16570 "
16571 });
16572 cx.simulate_keystroke("up");
16573 cx.assert_excerpts_with_selections(indoc! {"
16574 [EXCERPT]
16575 [FOLDED]
16576 [EXCERPT]
16577 ˇa1
16578 b1
16579 [EXCERPT]
16580 [FOLDED]
16581 [EXCERPT]
16582 [FOLDED]
16583 "
16584 });
16585 for _ in 0..5 {
16586 cx.simulate_keystroke("up");
16587 cx.assert_excerpts_with_selections(indoc! {"
16588 [EXCERPT]
16589 ˇ[FOLDED]
16590 [EXCERPT]
16591 a1
16592 b1
16593 [EXCERPT]
16594 [FOLDED]
16595 [EXCERPT]
16596 [FOLDED]
16597 "
16598 });
16599 }
16600}
16601
16602#[gpui::test]
16603async fn test_inline_completion_text(cx: &mut TestAppContext) {
16604 init_test(cx, |_| {});
16605
16606 // Simple insertion
16607 assert_highlighted_edits(
16608 "Hello, world!",
16609 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16610 true,
16611 cx,
16612 |highlighted_edits, cx| {
16613 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16614 assert_eq!(highlighted_edits.highlights.len(), 1);
16615 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16616 assert_eq!(
16617 highlighted_edits.highlights[0].1.background_color,
16618 Some(cx.theme().status().created_background)
16619 );
16620 },
16621 )
16622 .await;
16623
16624 // Replacement
16625 assert_highlighted_edits(
16626 "This is a test.",
16627 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16628 false,
16629 cx,
16630 |highlighted_edits, cx| {
16631 assert_eq!(highlighted_edits.text, "That is a test.");
16632 assert_eq!(highlighted_edits.highlights.len(), 1);
16633 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16634 assert_eq!(
16635 highlighted_edits.highlights[0].1.background_color,
16636 Some(cx.theme().status().created_background)
16637 );
16638 },
16639 )
16640 .await;
16641
16642 // Multiple edits
16643 assert_highlighted_edits(
16644 "Hello, world!",
16645 vec![
16646 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16647 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16648 ],
16649 false,
16650 cx,
16651 |highlighted_edits, cx| {
16652 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16653 assert_eq!(highlighted_edits.highlights.len(), 2);
16654 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16655 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16656 assert_eq!(
16657 highlighted_edits.highlights[0].1.background_color,
16658 Some(cx.theme().status().created_background)
16659 );
16660 assert_eq!(
16661 highlighted_edits.highlights[1].1.background_color,
16662 Some(cx.theme().status().created_background)
16663 );
16664 },
16665 )
16666 .await;
16667
16668 // Multiple lines with edits
16669 assert_highlighted_edits(
16670 "First line\nSecond line\nThird line\nFourth line",
16671 vec![
16672 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16673 (
16674 Point::new(2, 0)..Point::new(2, 10),
16675 "New third line".to_string(),
16676 ),
16677 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16678 ],
16679 false,
16680 cx,
16681 |highlighted_edits, cx| {
16682 assert_eq!(
16683 highlighted_edits.text,
16684 "Second modified\nNew third line\nFourth updated line"
16685 );
16686 assert_eq!(highlighted_edits.highlights.len(), 3);
16687 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16688 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16689 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16690 for highlight in &highlighted_edits.highlights {
16691 assert_eq!(
16692 highlight.1.background_color,
16693 Some(cx.theme().status().created_background)
16694 );
16695 }
16696 },
16697 )
16698 .await;
16699}
16700
16701#[gpui::test]
16702async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16703 init_test(cx, |_| {});
16704
16705 // Deletion
16706 assert_highlighted_edits(
16707 "Hello, world!",
16708 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16709 true,
16710 cx,
16711 |highlighted_edits, cx| {
16712 assert_eq!(highlighted_edits.text, "Hello, world!");
16713 assert_eq!(highlighted_edits.highlights.len(), 1);
16714 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16715 assert_eq!(
16716 highlighted_edits.highlights[0].1.background_color,
16717 Some(cx.theme().status().deleted_background)
16718 );
16719 },
16720 )
16721 .await;
16722
16723 // Insertion
16724 assert_highlighted_edits(
16725 "Hello, world!",
16726 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16727 true,
16728 cx,
16729 |highlighted_edits, cx| {
16730 assert_eq!(highlighted_edits.highlights.len(), 1);
16731 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16732 assert_eq!(
16733 highlighted_edits.highlights[0].1.background_color,
16734 Some(cx.theme().status().created_background)
16735 );
16736 },
16737 )
16738 .await;
16739}
16740
16741async fn assert_highlighted_edits(
16742 text: &str,
16743 edits: Vec<(Range<Point>, String)>,
16744 include_deletions: bool,
16745 cx: &mut TestAppContext,
16746 assertion_fn: impl Fn(HighlightedText, &App),
16747) {
16748 let window = cx.add_window(|window, cx| {
16749 let buffer = MultiBuffer::build_simple(text, cx);
16750 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16751 });
16752 let cx = &mut VisualTestContext::from_window(*window, cx);
16753
16754 let (buffer, snapshot) = window
16755 .update(cx, |editor, _window, cx| {
16756 (
16757 editor.buffer().clone(),
16758 editor.buffer().read(cx).snapshot(cx),
16759 )
16760 })
16761 .unwrap();
16762
16763 let edits = edits
16764 .into_iter()
16765 .map(|(range, edit)| {
16766 (
16767 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16768 edit,
16769 )
16770 })
16771 .collect::<Vec<_>>();
16772
16773 let text_anchor_edits = edits
16774 .clone()
16775 .into_iter()
16776 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16777 .collect::<Vec<_>>();
16778
16779 let edit_preview = window
16780 .update(cx, |_, _window, cx| {
16781 buffer
16782 .read(cx)
16783 .as_singleton()
16784 .unwrap()
16785 .read(cx)
16786 .preview_edits(text_anchor_edits.into(), cx)
16787 })
16788 .unwrap()
16789 .await;
16790
16791 cx.update(|_window, cx| {
16792 let highlighted_edits = inline_completion_edit_text(
16793 &snapshot.as_singleton().unwrap().2,
16794 &edits,
16795 &edit_preview,
16796 include_deletions,
16797 cx,
16798 );
16799 assertion_fn(highlighted_edits, cx)
16800 });
16801}
16802
16803#[gpui::test]
16804async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16805 init_test(cx, |_| {});
16806 let capabilities = lsp::ServerCapabilities {
16807 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16808 prepare_provider: Some(true),
16809 work_done_progress_options: Default::default(),
16810 })),
16811 ..Default::default()
16812 };
16813 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16814
16815 cx.set_state(indoc! {"
16816 struct Fˇoo {}
16817 "});
16818
16819 cx.update_editor(|editor, _, cx| {
16820 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16821 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16822 editor.highlight_background::<DocumentHighlightRead>(
16823 &[highlight_range],
16824 |c| c.editor_document_highlight_read_background,
16825 cx,
16826 );
16827 });
16828
16829 let mut prepare_rename_handler =
16830 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16831 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16832 start: lsp::Position {
16833 line: 0,
16834 character: 7,
16835 },
16836 end: lsp::Position {
16837 line: 0,
16838 character: 10,
16839 },
16840 })))
16841 });
16842 let prepare_rename_task = cx
16843 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16844 .expect("Prepare rename was not started");
16845 prepare_rename_handler.next().await.unwrap();
16846 prepare_rename_task.await.expect("Prepare rename failed");
16847
16848 let mut rename_handler =
16849 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16850 let edit = lsp::TextEdit {
16851 range: lsp::Range {
16852 start: lsp::Position {
16853 line: 0,
16854 character: 7,
16855 },
16856 end: lsp::Position {
16857 line: 0,
16858 character: 10,
16859 },
16860 },
16861 new_text: "FooRenamed".to_string(),
16862 };
16863 Ok(Some(lsp::WorkspaceEdit::new(
16864 // Specify the same edit twice
16865 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16866 )))
16867 });
16868 let rename_task = cx
16869 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16870 .expect("Confirm rename was not started");
16871 rename_handler.next().await.unwrap();
16872 rename_task.await.expect("Confirm rename failed");
16873 cx.run_until_parked();
16874
16875 // Despite two edits, only one is actually applied as those are identical
16876 cx.assert_editor_state(indoc! {"
16877 struct FooRenamedˇ {}
16878 "});
16879}
16880
16881#[gpui::test]
16882async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16883 init_test(cx, |_| {});
16884 // These capabilities indicate that the server does not support prepare rename.
16885 let capabilities = lsp::ServerCapabilities {
16886 rename_provider: Some(lsp::OneOf::Left(true)),
16887 ..Default::default()
16888 };
16889 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16890
16891 cx.set_state(indoc! {"
16892 struct Fˇoo {}
16893 "});
16894
16895 cx.update_editor(|editor, _window, cx| {
16896 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16897 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16898 editor.highlight_background::<DocumentHighlightRead>(
16899 &[highlight_range],
16900 |c| c.editor_document_highlight_read_background,
16901 cx,
16902 );
16903 });
16904
16905 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16906 .expect("Prepare rename was not started")
16907 .await
16908 .expect("Prepare rename failed");
16909
16910 let mut rename_handler =
16911 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16912 let edit = lsp::TextEdit {
16913 range: lsp::Range {
16914 start: lsp::Position {
16915 line: 0,
16916 character: 7,
16917 },
16918 end: lsp::Position {
16919 line: 0,
16920 character: 10,
16921 },
16922 },
16923 new_text: "FooRenamed".to_string(),
16924 };
16925 Ok(Some(lsp::WorkspaceEdit::new(
16926 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16927 )))
16928 });
16929 let rename_task = cx
16930 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16931 .expect("Confirm rename was not started");
16932 rename_handler.next().await.unwrap();
16933 rename_task.await.expect("Confirm rename failed");
16934 cx.run_until_parked();
16935
16936 // Correct range is renamed, as `surrounding_word` is used to find it.
16937 cx.assert_editor_state(indoc! {"
16938 struct FooRenamedˇ {}
16939 "});
16940}
16941
16942#[gpui::test]
16943async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16944 init_test(cx, |_| {});
16945 let mut cx = EditorTestContext::new(cx).await;
16946
16947 let language = Arc::new(
16948 Language::new(
16949 LanguageConfig::default(),
16950 Some(tree_sitter_html::LANGUAGE.into()),
16951 )
16952 .with_brackets_query(
16953 r#"
16954 ("<" @open "/>" @close)
16955 ("</" @open ">" @close)
16956 ("<" @open ">" @close)
16957 ("\"" @open "\"" @close)
16958 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16959 "#,
16960 )
16961 .unwrap(),
16962 );
16963 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16964
16965 cx.set_state(indoc! {"
16966 <span>ˇ</span>
16967 "});
16968 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16969 cx.assert_editor_state(indoc! {"
16970 <span>
16971 ˇ
16972 </span>
16973 "});
16974
16975 cx.set_state(indoc! {"
16976 <span><span></span>ˇ</span>
16977 "});
16978 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16979 cx.assert_editor_state(indoc! {"
16980 <span><span></span>
16981 ˇ</span>
16982 "});
16983
16984 cx.set_state(indoc! {"
16985 <span>ˇ
16986 </span>
16987 "});
16988 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16989 cx.assert_editor_state(indoc! {"
16990 <span>
16991 ˇ
16992 </span>
16993 "});
16994}
16995
16996mod autoclose_tags {
16997 use super::*;
16998 use language::language_settings::JsxTagAutoCloseSettings;
16999 use languages::language;
17000
17001 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17002 init_test(cx, |settings| {
17003 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17004 });
17005
17006 let mut cx = EditorTestContext::new(cx).await;
17007 cx.update_buffer(|buffer, cx| {
17008 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17009
17010 buffer.set_language(Some(language), cx)
17011 });
17012
17013 cx
17014 }
17015
17016 macro_rules! check {
17017 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17018 #[gpui::test]
17019 async fn $name(cx: &mut TestAppContext) {
17020 let mut cx = test_setup(cx).await;
17021 cx.set_state($initial);
17022 cx.run_until_parked();
17023
17024 cx.update_editor(|editor, window, cx| {
17025 editor.handle_input($input, window, cx);
17026 });
17027 cx.run_until_parked();
17028 cx.assert_editor_state($expected);
17029 }
17030 };
17031 }
17032
17033 check!(
17034 test_basic,
17035 "<divˇ" + ">" => "<div>ˇ</div>"
17036 );
17037
17038 check!(
17039 test_basic_nested,
17040 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17041 );
17042
17043 check!(
17044 test_basic_ignore_already_closed,
17045 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17046 );
17047
17048 check!(
17049 test_doesnt_autoclose_closing_tag,
17050 "</divˇ" + ">" => "</div>ˇ"
17051 );
17052
17053 check!(
17054 test_jsx_attr,
17055 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17056 );
17057
17058 check!(
17059 test_ignores_closing_tags_in_expr_block,
17060 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17061 );
17062
17063 check!(
17064 test_doesnt_autoclose_on_gt_in_expr,
17065 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17066 );
17067
17068 check!(
17069 test_ignores_closing_tags_with_different_tag_names,
17070 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17071 );
17072
17073 check!(
17074 test_autocloses_in_jsx_expression,
17075 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17076 );
17077
17078 check!(
17079 test_doesnt_autoclose_already_closed_in_jsx_expression,
17080 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17081 );
17082
17083 check!(
17084 test_autocloses_fragment,
17085 "<ˇ" + ">" => "<>ˇ</>"
17086 );
17087
17088 check!(
17089 test_does_not_include_type_argument_in_autoclose_tag_name,
17090 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17091 );
17092
17093 check!(
17094 test_does_not_autoclose_doctype,
17095 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17096 );
17097
17098 check!(
17099 test_does_not_autoclose_comment,
17100 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17101 );
17102
17103 check!(
17104 test_multi_cursor_autoclose_same_tag,
17105 r#"
17106 <divˇ
17107 <divˇ
17108 "#
17109 + ">" =>
17110 r#"
17111 <div>ˇ</div>
17112 <div>ˇ</div>
17113 "#
17114 );
17115
17116 check!(
17117 test_multi_cursor_autoclose_different_tags,
17118 r#"
17119 <divˇ
17120 <spanˇ
17121 "#
17122 + ">" =>
17123 r#"
17124 <div>ˇ</div>
17125 <span>ˇ</span>
17126 "#
17127 );
17128
17129 check!(
17130 test_multi_cursor_autoclose_some_dont_autoclose_others,
17131 r#"
17132 <divˇ
17133 <div /ˇ
17134 <spanˇ</span>
17135 <!DOCTYPE htmlˇ
17136 </headˇ
17137 <Component<T>ˇ
17138 ˇ
17139 "#
17140 + ">" =>
17141 r#"
17142 <div>ˇ</div>
17143 <div />ˇ
17144 <span>ˇ</span>
17145 <!DOCTYPE html>ˇ
17146 </head>ˇ
17147 <Component<T>>ˇ</Component>
17148 >ˇ
17149 "#
17150 );
17151
17152 check!(
17153 test_doesnt_mess_up_trailing_text,
17154 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17155 );
17156
17157 #[gpui::test]
17158 async fn test_multibuffer(cx: &mut TestAppContext) {
17159 init_test(cx, |settings| {
17160 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17161 });
17162
17163 let buffer_a = cx.new(|cx| {
17164 let mut buf = language::Buffer::local("<div", cx);
17165 buf.set_language(
17166 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17167 cx,
17168 );
17169 buf
17170 });
17171 let buffer_b = cx.new(|cx| {
17172 let mut buf = language::Buffer::local("<pre", cx);
17173 buf.set_language(
17174 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17175 cx,
17176 );
17177 buf
17178 });
17179 let buffer_c = cx.new(|cx| {
17180 let buf = language::Buffer::local("<span", cx);
17181 buf
17182 });
17183 let buffer = cx.new(|cx| {
17184 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17185 buf.push_excerpts(
17186 buffer_a,
17187 [ExcerptRange {
17188 context: text::Anchor::MIN..text::Anchor::MAX,
17189 primary: None,
17190 }],
17191 cx,
17192 );
17193 buf.push_excerpts(
17194 buffer_b,
17195 [ExcerptRange {
17196 context: text::Anchor::MIN..text::Anchor::MAX,
17197 primary: None,
17198 }],
17199 cx,
17200 );
17201 buf.push_excerpts(
17202 buffer_c,
17203 [ExcerptRange {
17204 context: text::Anchor::MIN..text::Anchor::MAX,
17205 primary: None,
17206 }],
17207 cx,
17208 );
17209 buf
17210 });
17211 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17212
17213 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17214
17215 cx.update_editor(|editor, window, cx| {
17216 editor.change_selections(None, window, cx, |selections| {
17217 selections.select(vec![
17218 Selection::from_offset(4),
17219 Selection::from_offset(9),
17220 Selection::from_offset(15),
17221 ])
17222 })
17223 });
17224 cx.run_until_parked();
17225
17226 cx.update_editor(|editor, window, cx| {
17227 editor.handle_input(">", window, cx);
17228 });
17229 cx.run_until_parked();
17230
17231 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17232 }
17233}
17234
17235fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17236 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17237 point..point
17238}
17239
17240fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17241 let (text, ranges) = marked_text_ranges(marked_text, true);
17242 assert_eq!(editor.text(cx), text);
17243 assert_eq!(
17244 editor.selections.ranges(cx),
17245 ranges,
17246 "Assert selections are {}",
17247 marked_text
17248 );
17249}
17250
17251pub fn handle_signature_help_request(
17252 cx: &mut EditorLspTestContext,
17253 mocked_response: lsp::SignatureHelp,
17254) -> impl Future<Output = ()> {
17255 let mut request =
17256 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17257 let mocked_response = mocked_response.clone();
17258 async move { Ok(Some(mocked_response)) }
17259 });
17260
17261 async move {
17262 request.next().await;
17263 }
17264}
17265
17266/// Handle completion request passing a marked string specifying where the completion
17267/// should be triggered from using '|' character, what range should be replaced, and what completions
17268/// should be returned using '<' and '>' to delimit the range
17269pub fn handle_completion_request(
17270 cx: &mut EditorLspTestContext,
17271 marked_string: &str,
17272 completions: Vec<&'static str>,
17273 counter: Arc<AtomicUsize>,
17274) -> impl Future<Output = ()> {
17275 let complete_from_marker: TextRangeMarker = '|'.into();
17276 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17277 let (_, mut marked_ranges) = marked_text_ranges_by(
17278 marked_string,
17279 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17280 );
17281
17282 let complete_from_position =
17283 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17284 let replace_range =
17285 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17286
17287 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17288 let completions = completions.clone();
17289 counter.fetch_add(1, atomic::Ordering::Release);
17290 async move {
17291 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17292 assert_eq!(
17293 params.text_document_position.position,
17294 complete_from_position
17295 );
17296 Ok(Some(lsp::CompletionResponse::Array(
17297 completions
17298 .iter()
17299 .map(|completion_text| lsp::CompletionItem {
17300 label: completion_text.to_string(),
17301 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17302 range: replace_range,
17303 new_text: completion_text.to_string(),
17304 })),
17305 ..Default::default()
17306 })
17307 .collect(),
17308 )))
17309 }
17310 });
17311
17312 async move {
17313 request.next().await;
17314 }
17315}
17316
17317fn handle_resolve_completion_request(
17318 cx: &mut EditorLspTestContext,
17319 edits: Option<Vec<(&'static str, &'static str)>>,
17320) -> impl Future<Output = ()> {
17321 let edits = edits.map(|edits| {
17322 edits
17323 .iter()
17324 .map(|(marked_string, new_text)| {
17325 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17326 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17327 lsp::TextEdit::new(replace_range, new_text.to_string())
17328 })
17329 .collect::<Vec<_>>()
17330 });
17331
17332 let mut request =
17333 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17334 let edits = edits.clone();
17335 async move {
17336 Ok(lsp::CompletionItem {
17337 additional_text_edits: edits,
17338 ..Default::default()
17339 })
17340 }
17341 });
17342
17343 async move {
17344 request.next().await;
17345 }
17346}
17347
17348pub(crate) fn update_test_language_settings(
17349 cx: &mut TestAppContext,
17350 f: impl Fn(&mut AllLanguageSettingsContent),
17351) {
17352 cx.update(|cx| {
17353 SettingsStore::update_global(cx, |store, cx| {
17354 store.update_user_settings::<AllLanguageSettings>(cx, f);
17355 });
17356 });
17357}
17358
17359pub(crate) fn update_test_project_settings(
17360 cx: &mut TestAppContext,
17361 f: impl Fn(&mut ProjectSettings),
17362) {
17363 cx.update(|cx| {
17364 SettingsStore::update_global(cx, |store, cx| {
17365 store.update_user_settings::<ProjectSettings>(cx, f);
17366 });
17367 });
17368}
17369
17370pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17371 cx.update(|cx| {
17372 assets::Assets.load_test_fonts(cx);
17373 let store = SettingsStore::test(cx);
17374 cx.set_global(store);
17375 theme::init(theme::LoadThemes::JustBase, cx);
17376 release_channel::init(SemanticVersion::default(), cx);
17377 client::init_settings(cx);
17378 language::init(cx);
17379 Project::init_settings(cx);
17380 workspace::init_settings(cx);
17381 crate::init(cx);
17382 });
17383
17384 update_test_language_settings(cx, f);
17385}
17386
17387#[track_caller]
17388fn assert_hunk_revert(
17389 not_reverted_text_with_selections: &str,
17390 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17391 expected_reverted_text_with_selections: &str,
17392 base_text: &str,
17393 cx: &mut EditorLspTestContext,
17394) {
17395 cx.set_state(not_reverted_text_with_selections);
17396 cx.set_head_text(base_text);
17397 cx.executor().run_until_parked();
17398
17399 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17400 let snapshot = editor.snapshot(window, cx);
17401 let reverted_hunk_statuses = snapshot
17402 .buffer_snapshot
17403 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17404 .map(|hunk| hunk.status().kind)
17405 .collect::<Vec<_>>();
17406
17407 editor.git_restore(&Default::default(), window, cx);
17408 reverted_hunk_statuses
17409 });
17410 cx.executor().run_until_parked();
17411 cx.assert_editor_state(expected_reverted_text_with_selections);
17412 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17413}