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 item_0_out = lsp::CompletionItem {
12338 commit_characters: Some(default_commit_characters.clone()),
12339 insert_text_format: Some(default_insert_text_format),
12340 ..item_0
12341 };
12342 let items_out = iter::once(item_0_out)
12343 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
12344 commit_characters: Some(default_commit_characters.clone()),
12345 data: Some(default_data.clone()),
12346 insert_text_mode: Some(default_insert_text_mode),
12347 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12348 range: default_edit_range,
12349 new_text: item.label.clone(),
12350 })),
12351 ..item.clone()
12352 }))
12353 .collect::<Vec<lsp::CompletionItem>>();
12354
12355 let mut cx = EditorLspTestContext::new_rust(
12356 lsp::ServerCapabilities {
12357 completion_provider: Some(lsp::CompletionOptions {
12358 trigger_characters: Some(vec![".".to_string()]),
12359 resolve_provider: Some(true),
12360 ..Default::default()
12361 }),
12362 ..Default::default()
12363 },
12364 cx,
12365 )
12366 .await;
12367
12368 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12369 cx.simulate_keystroke(".");
12370
12371 let completion_data = default_data.clone();
12372 let completion_characters = default_commit_characters.clone();
12373 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12374 let default_data = completion_data.clone();
12375 let default_commit_characters = completion_characters.clone();
12376 let items = items.clone();
12377 async move {
12378 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12379 items,
12380 item_defaults: Some(lsp::CompletionListItemDefaults {
12381 data: Some(default_data.clone()),
12382 commit_characters: Some(default_commit_characters.clone()),
12383 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12384 default_edit_range,
12385 )),
12386 insert_text_format: Some(default_insert_text_format),
12387 insert_text_mode: Some(default_insert_text_mode),
12388 }),
12389 ..lsp::CompletionList::default()
12390 })))
12391 }
12392 })
12393 .next()
12394 .await;
12395
12396 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12397 cx.lsp
12398 .server
12399 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12400 let closure_resolved_items = resolved_items.clone();
12401 move |item_to_resolve, _| {
12402 let closure_resolved_items = closure_resolved_items.clone();
12403 async move {
12404 closure_resolved_items.lock().push(item_to_resolve.clone());
12405 Ok(item_to_resolve)
12406 }
12407 }
12408 })
12409 .detach();
12410
12411 cx.condition(|editor, _| editor.context_menu_visible())
12412 .await;
12413 cx.run_until_parked();
12414 cx.update_editor(|editor, _, _| {
12415 let menu = editor.context_menu.borrow_mut();
12416 match menu.as_ref().expect("should have the completions menu") {
12417 CodeContextMenu::Completions(completions_menu) => {
12418 assert_eq!(
12419 completions_menu
12420 .entries
12421 .borrow()
12422 .iter()
12423 .map(|mat| mat.string.clone())
12424 .collect::<Vec<String>>(),
12425 items_out
12426 .iter()
12427 .map(|completion| completion.label.clone())
12428 .collect::<Vec<String>>()
12429 );
12430 }
12431 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12432 }
12433 });
12434 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12435 // with 4 from the end.
12436 assert_eq!(
12437 *resolved_items.lock(),
12438 [
12439 &items_out[0..16],
12440 &items_out[items_out.len() - 4..items_out.len()]
12441 ]
12442 .concat()
12443 .iter()
12444 .cloned()
12445 .collect::<Vec<lsp::CompletionItem>>()
12446 );
12447 resolved_items.lock().clear();
12448
12449 cx.update_editor(|editor, window, cx| {
12450 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12451 });
12452 cx.run_until_parked();
12453 // Completions that have already been resolved are skipped.
12454 assert_eq!(
12455 *resolved_items.lock(),
12456 items_out[items_out.len() - 16..items_out.len() - 4]
12457 .iter()
12458 .cloned()
12459 .collect::<Vec<lsp::CompletionItem>>()
12460 );
12461 resolved_items.lock().clear();
12462}
12463
12464#[gpui::test]
12465async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12466 init_test(cx, |_| {});
12467
12468 let mut cx = EditorLspTestContext::new(
12469 Language::new(
12470 LanguageConfig {
12471 matcher: LanguageMatcher {
12472 path_suffixes: vec!["jsx".into()],
12473 ..Default::default()
12474 },
12475 overrides: [(
12476 "element".into(),
12477 LanguageConfigOverride {
12478 word_characters: Override::Set(['-'].into_iter().collect()),
12479 ..Default::default()
12480 },
12481 )]
12482 .into_iter()
12483 .collect(),
12484 ..Default::default()
12485 },
12486 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12487 )
12488 .with_override_query("(jsx_self_closing_element) @element")
12489 .unwrap(),
12490 lsp::ServerCapabilities {
12491 completion_provider: Some(lsp::CompletionOptions {
12492 trigger_characters: Some(vec![":".to_string()]),
12493 ..Default::default()
12494 }),
12495 ..Default::default()
12496 },
12497 cx,
12498 )
12499 .await;
12500
12501 cx.lsp
12502 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12503 Ok(Some(lsp::CompletionResponse::Array(vec![
12504 lsp::CompletionItem {
12505 label: "bg-blue".into(),
12506 ..Default::default()
12507 },
12508 lsp::CompletionItem {
12509 label: "bg-red".into(),
12510 ..Default::default()
12511 },
12512 lsp::CompletionItem {
12513 label: "bg-yellow".into(),
12514 ..Default::default()
12515 },
12516 ])))
12517 });
12518
12519 cx.set_state(r#"<p class="bgˇ" />"#);
12520
12521 // Trigger completion when typing a dash, because the dash is an extra
12522 // word character in the 'element' scope, which contains the cursor.
12523 cx.simulate_keystroke("-");
12524 cx.executor().run_until_parked();
12525 cx.update_editor(|editor, _, _| {
12526 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12527 {
12528 assert_eq!(
12529 completion_menu_entries(&menu),
12530 &["bg-red", "bg-blue", "bg-yellow"]
12531 );
12532 } else {
12533 panic!("expected completion menu to be open");
12534 }
12535 });
12536
12537 cx.simulate_keystroke("l");
12538 cx.executor().run_until_parked();
12539 cx.update_editor(|editor, _, _| {
12540 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12541 {
12542 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12543 } else {
12544 panic!("expected completion menu to be open");
12545 }
12546 });
12547
12548 // When filtering completions, consider the character after the '-' to
12549 // be the start of a subword.
12550 cx.set_state(r#"<p class="yelˇ" />"#);
12551 cx.simulate_keystroke("l");
12552 cx.executor().run_until_parked();
12553 cx.update_editor(|editor, _, _| {
12554 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12555 {
12556 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12557 } else {
12558 panic!("expected completion menu to be open");
12559 }
12560 });
12561}
12562
12563fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12564 let entries = menu.entries.borrow();
12565 entries.iter().map(|mat| mat.string.clone()).collect()
12566}
12567
12568#[gpui::test]
12569async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12570 init_test(cx, |settings| {
12571 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12572 FormatterList(vec![Formatter::Prettier].into()),
12573 ))
12574 });
12575
12576 let fs = FakeFs::new(cx.executor());
12577 fs.insert_file(path!("/file.ts"), Default::default()).await;
12578
12579 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12580 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12581
12582 language_registry.add(Arc::new(Language::new(
12583 LanguageConfig {
12584 name: "TypeScript".into(),
12585 matcher: LanguageMatcher {
12586 path_suffixes: vec!["ts".to_string()],
12587 ..Default::default()
12588 },
12589 ..Default::default()
12590 },
12591 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12592 )));
12593 update_test_language_settings(cx, |settings| {
12594 settings.defaults.prettier = Some(PrettierSettings {
12595 allowed: true,
12596 ..PrettierSettings::default()
12597 });
12598 });
12599
12600 let test_plugin = "test_plugin";
12601 let _ = language_registry.register_fake_lsp(
12602 "TypeScript",
12603 FakeLspAdapter {
12604 prettier_plugins: vec![test_plugin],
12605 ..Default::default()
12606 },
12607 );
12608
12609 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12610 let buffer = project
12611 .update(cx, |project, cx| {
12612 project.open_local_buffer(path!("/file.ts"), cx)
12613 })
12614 .await
12615 .unwrap();
12616
12617 let buffer_text = "one\ntwo\nthree\n";
12618 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12619 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12620 editor.update_in(cx, |editor, window, cx| {
12621 editor.set_text(buffer_text, window, cx)
12622 });
12623
12624 editor
12625 .update_in(cx, |editor, window, cx| {
12626 editor.perform_format(
12627 project.clone(),
12628 FormatTrigger::Manual,
12629 FormatTarget::Buffers,
12630 window,
12631 cx,
12632 )
12633 })
12634 .unwrap()
12635 .await;
12636 assert_eq!(
12637 editor.update(cx, |editor, cx| editor.text(cx)),
12638 buffer_text.to_string() + prettier_format_suffix,
12639 "Test prettier formatting was not applied to the original buffer text",
12640 );
12641
12642 update_test_language_settings(cx, |settings| {
12643 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12644 });
12645 let format = editor.update_in(cx, |editor, window, cx| {
12646 editor.perform_format(
12647 project.clone(),
12648 FormatTrigger::Manual,
12649 FormatTarget::Buffers,
12650 window,
12651 cx,
12652 )
12653 });
12654 format.await.unwrap();
12655 assert_eq!(
12656 editor.update(cx, |editor, cx| editor.text(cx)),
12657 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12658 "Autoformatting (via test prettier) was not applied to the original buffer text",
12659 );
12660}
12661
12662#[gpui::test]
12663async fn test_addition_reverts(cx: &mut TestAppContext) {
12664 init_test(cx, |_| {});
12665 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12666 let base_text = indoc! {r#"
12667 struct Row;
12668 struct Row1;
12669 struct Row2;
12670
12671 struct Row4;
12672 struct Row5;
12673 struct Row6;
12674
12675 struct Row8;
12676 struct Row9;
12677 struct Row10;"#};
12678
12679 // When addition hunks are not adjacent to carets, no hunk revert is performed
12680 assert_hunk_revert(
12681 indoc! {r#"struct Row;
12682 struct Row1;
12683 struct Row1.1;
12684 struct Row1.2;
12685 struct Row2;ˇ
12686
12687 struct Row4;
12688 struct Row5;
12689 struct Row6;
12690
12691 struct Row8;
12692 ˇstruct Row9;
12693 struct Row9.1;
12694 struct Row9.2;
12695 struct Row9.3;
12696 struct Row10;"#},
12697 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12698 indoc! {r#"struct Row;
12699 struct Row1;
12700 struct Row1.1;
12701 struct Row1.2;
12702 struct Row2;ˇ
12703
12704 struct Row4;
12705 struct Row5;
12706 struct Row6;
12707
12708 struct Row8;
12709 ˇstruct Row9;
12710 struct Row9.1;
12711 struct Row9.2;
12712 struct Row9.3;
12713 struct Row10;"#},
12714 base_text,
12715 &mut cx,
12716 );
12717 // Same for selections
12718 assert_hunk_revert(
12719 indoc! {r#"struct Row;
12720 struct Row1;
12721 struct Row2;
12722 struct Row2.1;
12723 struct Row2.2;
12724 «ˇ
12725 struct Row4;
12726 struct» Row5;
12727 «struct Row6;
12728 ˇ»
12729 struct Row9.1;
12730 struct Row9.2;
12731 struct Row9.3;
12732 struct Row8;
12733 struct Row9;
12734 struct Row10;"#},
12735 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12736 indoc! {r#"struct Row;
12737 struct Row1;
12738 struct Row2;
12739 struct Row2.1;
12740 struct Row2.2;
12741 «ˇ
12742 struct Row4;
12743 struct» Row5;
12744 «struct Row6;
12745 ˇ»
12746 struct Row9.1;
12747 struct Row9.2;
12748 struct Row9.3;
12749 struct Row8;
12750 struct Row9;
12751 struct Row10;"#},
12752 base_text,
12753 &mut cx,
12754 );
12755
12756 // When carets and selections intersect the addition hunks, those are reverted.
12757 // Adjacent carets got merged.
12758 assert_hunk_revert(
12759 indoc! {r#"struct Row;
12760 ˇ// something on the top
12761 struct Row1;
12762 struct Row2;
12763 struct Roˇw3.1;
12764 struct Row2.2;
12765 struct Row2.3;ˇ
12766
12767 struct Row4;
12768 struct ˇRow5.1;
12769 struct Row5.2;
12770 struct «Rowˇ»5.3;
12771 struct Row5;
12772 struct Row6;
12773 ˇ
12774 struct Row9.1;
12775 struct «Rowˇ»9.2;
12776 struct «ˇRow»9.3;
12777 struct Row8;
12778 struct Row9;
12779 «ˇ// something on bottom»
12780 struct Row10;"#},
12781 vec![
12782 DiffHunkStatusKind::Added,
12783 DiffHunkStatusKind::Added,
12784 DiffHunkStatusKind::Added,
12785 DiffHunkStatusKind::Added,
12786 DiffHunkStatusKind::Added,
12787 ],
12788 indoc! {r#"struct Row;
12789 ˇstruct Row1;
12790 struct Row2;
12791 ˇ
12792 struct Row4;
12793 ˇstruct Row5;
12794 struct Row6;
12795 ˇ
12796 ˇstruct Row8;
12797 struct Row9;
12798 ˇstruct Row10;"#},
12799 base_text,
12800 &mut cx,
12801 );
12802}
12803
12804#[gpui::test]
12805async fn test_modification_reverts(cx: &mut TestAppContext) {
12806 init_test(cx, |_| {});
12807 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12808 let base_text = indoc! {r#"
12809 struct Row;
12810 struct Row1;
12811 struct Row2;
12812
12813 struct Row4;
12814 struct Row5;
12815 struct Row6;
12816
12817 struct Row8;
12818 struct Row9;
12819 struct Row10;"#};
12820
12821 // Modification hunks behave the same as the addition ones.
12822 assert_hunk_revert(
12823 indoc! {r#"struct Row;
12824 struct Row1;
12825 struct Row33;
12826 ˇ
12827 struct Row4;
12828 struct Row5;
12829 struct Row6;
12830 ˇ
12831 struct Row99;
12832 struct Row9;
12833 struct Row10;"#},
12834 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12835 indoc! {r#"struct Row;
12836 struct Row1;
12837 struct Row33;
12838 ˇ
12839 struct Row4;
12840 struct Row5;
12841 struct Row6;
12842 ˇ
12843 struct Row99;
12844 struct Row9;
12845 struct Row10;"#},
12846 base_text,
12847 &mut cx,
12848 );
12849 assert_hunk_revert(
12850 indoc! {r#"struct Row;
12851 struct Row1;
12852 struct Row33;
12853 «ˇ
12854 struct Row4;
12855 struct» Row5;
12856 «struct Row6;
12857 ˇ»
12858 struct Row99;
12859 struct Row9;
12860 struct Row10;"#},
12861 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12862 indoc! {r#"struct Row;
12863 struct Row1;
12864 struct Row33;
12865 «ˇ
12866 struct Row4;
12867 struct» Row5;
12868 «struct Row6;
12869 ˇ»
12870 struct Row99;
12871 struct Row9;
12872 struct Row10;"#},
12873 base_text,
12874 &mut cx,
12875 );
12876
12877 assert_hunk_revert(
12878 indoc! {r#"ˇstruct Row1.1;
12879 struct Row1;
12880 «ˇstr»uct Row22;
12881
12882 struct ˇRow44;
12883 struct Row5;
12884 struct «Rˇ»ow66;ˇ
12885
12886 «struˇ»ct Row88;
12887 struct Row9;
12888 struct Row1011;ˇ"#},
12889 vec![
12890 DiffHunkStatusKind::Modified,
12891 DiffHunkStatusKind::Modified,
12892 DiffHunkStatusKind::Modified,
12893 DiffHunkStatusKind::Modified,
12894 DiffHunkStatusKind::Modified,
12895 DiffHunkStatusKind::Modified,
12896 ],
12897 indoc! {r#"struct Row;
12898 ˇstruct Row1;
12899 struct Row2;
12900 ˇ
12901 struct Row4;
12902 ˇstruct Row5;
12903 struct Row6;
12904 ˇ
12905 struct Row8;
12906 ˇstruct Row9;
12907 struct Row10;ˇ"#},
12908 base_text,
12909 &mut cx,
12910 );
12911}
12912
12913#[gpui::test]
12914async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
12915 init_test(cx, |_| {});
12916 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12917 let base_text = indoc! {r#"
12918 one
12919
12920 two
12921 three
12922 "#};
12923
12924 cx.set_head_text(base_text);
12925 cx.set_state("\nˇ\n");
12926 cx.executor().run_until_parked();
12927 cx.update_editor(|editor, _window, cx| {
12928 editor.expand_selected_diff_hunks(cx);
12929 });
12930 cx.executor().run_until_parked();
12931 cx.update_editor(|editor, window, cx| {
12932 editor.backspace(&Default::default(), window, cx);
12933 });
12934 cx.run_until_parked();
12935 cx.assert_state_with_diff(
12936 indoc! {r#"
12937
12938 - two
12939 - threeˇ
12940 +
12941 "#}
12942 .to_string(),
12943 );
12944}
12945
12946#[gpui::test]
12947async fn test_deletion_reverts(cx: &mut TestAppContext) {
12948 init_test(cx, |_| {});
12949 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12950 let base_text = indoc! {r#"struct Row;
12951struct Row1;
12952struct Row2;
12953
12954struct Row4;
12955struct Row5;
12956struct Row6;
12957
12958struct Row8;
12959struct Row9;
12960struct Row10;"#};
12961
12962 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
12963 assert_hunk_revert(
12964 indoc! {r#"struct Row;
12965 struct Row2;
12966
12967 ˇstruct Row4;
12968 struct Row5;
12969 struct Row6;
12970 ˇ
12971 struct Row8;
12972 struct Row10;"#},
12973 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12974 indoc! {r#"struct Row;
12975 struct Row2;
12976
12977 ˇstruct Row4;
12978 struct Row5;
12979 struct Row6;
12980 ˇ
12981 struct Row8;
12982 struct Row10;"#},
12983 base_text,
12984 &mut cx,
12985 );
12986 assert_hunk_revert(
12987 indoc! {r#"struct Row;
12988 struct Row2;
12989
12990 «ˇstruct Row4;
12991 struct» Row5;
12992 «struct Row6;
12993 ˇ»
12994 struct Row8;
12995 struct Row10;"#},
12996 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
12997 indoc! {r#"struct Row;
12998 struct Row2;
12999
13000 «ˇstruct Row4;
13001 struct» Row5;
13002 «struct Row6;
13003 ˇ»
13004 struct Row8;
13005 struct Row10;"#},
13006 base_text,
13007 &mut cx,
13008 );
13009
13010 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13011 assert_hunk_revert(
13012 indoc! {r#"struct Row;
13013 ˇstruct Row2;
13014
13015 struct Row4;
13016 struct Row5;
13017 struct Row6;
13018
13019 struct Row8;ˇ
13020 struct Row10;"#},
13021 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13022 indoc! {r#"struct Row;
13023 struct Row1;
13024 ˇstruct Row2;
13025
13026 struct Row4;
13027 struct Row5;
13028 struct Row6;
13029
13030 struct Row8;ˇ
13031 struct Row9;
13032 struct Row10;"#},
13033 base_text,
13034 &mut cx,
13035 );
13036 assert_hunk_revert(
13037 indoc! {r#"struct Row;
13038 struct Row2«ˇ;
13039 struct Row4;
13040 struct» Row5;
13041 «struct Row6;
13042
13043 struct Row8;ˇ»
13044 struct Row10;"#},
13045 vec![
13046 DiffHunkStatusKind::Deleted,
13047 DiffHunkStatusKind::Deleted,
13048 DiffHunkStatusKind::Deleted,
13049 ],
13050 indoc! {r#"struct Row;
13051 struct Row1;
13052 struct Row2«ˇ;
13053
13054 struct Row4;
13055 struct» Row5;
13056 «struct Row6;
13057
13058 struct Row8;ˇ»
13059 struct Row9;
13060 struct Row10;"#},
13061 base_text,
13062 &mut cx,
13063 );
13064}
13065
13066#[gpui::test]
13067async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13068 init_test(cx, |_| {});
13069
13070 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13071 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13072 let base_text_3 =
13073 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13074
13075 let text_1 = edit_first_char_of_every_line(base_text_1);
13076 let text_2 = edit_first_char_of_every_line(base_text_2);
13077 let text_3 = edit_first_char_of_every_line(base_text_3);
13078
13079 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13080 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13081 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13082
13083 let multibuffer = cx.new(|cx| {
13084 let mut multibuffer = MultiBuffer::new(ReadWrite);
13085 multibuffer.push_excerpts(
13086 buffer_1.clone(),
13087 [
13088 ExcerptRange {
13089 context: Point::new(0, 0)..Point::new(3, 0),
13090 primary: None,
13091 },
13092 ExcerptRange {
13093 context: Point::new(5, 0)..Point::new(7, 0),
13094 primary: None,
13095 },
13096 ExcerptRange {
13097 context: Point::new(9, 0)..Point::new(10, 4),
13098 primary: None,
13099 },
13100 ],
13101 cx,
13102 );
13103 multibuffer.push_excerpts(
13104 buffer_2.clone(),
13105 [
13106 ExcerptRange {
13107 context: Point::new(0, 0)..Point::new(3, 0),
13108 primary: None,
13109 },
13110 ExcerptRange {
13111 context: Point::new(5, 0)..Point::new(7, 0),
13112 primary: None,
13113 },
13114 ExcerptRange {
13115 context: Point::new(9, 0)..Point::new(10, 4),
13116 primary: None,
13117 },
13118 ],
13119 cx,
13120 );
13121 multibuffer.push_excerpts(
13122 buffer_3.clone(),
13123 [
13124 ExcerptRange {
13125 context: Point::new(0, 0)..Point::new(3, 0),
13126 primary: None,
13127 },
13128 ExcerptRange {
13129 context: Point::new(5, 0)..Point::new(7, 0),
13130 primary: None,
13131 },
13132 ExcerptRange {
13133 context: Point::new(9, 0)..Point::new(10, 4),
13134 primary: None,
13135 },
13136 ],
13137 cx,
13138 );
13139 multibuffer
13140 });
13141
13142 let fs = FakeFs::new(cx.executor());
13143 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13144 let (editor, cx) = cx
13145 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13146 editor.update_in(cx, |editor, _window, cx| {
13147 for (buffer, diff_base) in [
13148 (buffer_1.clone(), base_text_1),
13149 (buffer_2.clone(), base_text_2),
13150 (buffer_3.clone(), base_text_3),
13151 ] {
13152 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13153 editor
13154 .buffer
13155 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13156 }
13157 });
13158 cx.executor().run_until_parked();
13159
13160 editor.update_in(cx, |editor, window, cx| {
13161 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}");
13162 editor.select_all(&SelectAll, window, cx);
13163 editor.git_restore(&Default::default(), window, cx);
13164 });
13165 cx.executor().run_until_parked();
13166
13167 // When all ranges are selected, all buffer hunks are reverted.
13168 editor.update(cx, |editor, cx| {
13169 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");
13170 });
13171 buffer_1.update(cx, |buffer, _| {
13172 assert_eq!(buffer.text(), base_text_1);
13173 });
13174 buffer_2.update(cx, |buffer, _| {
13175 assert_eq!(buffer.text(), base_text_2);
13176 });
13177 buffer_3.update(cx, |buffer, _| {
13178 assert_eq!(buffer.text(), base_text_3);
13179 });
13180
13181 editor.update_in(cx, |editor, window, cx| {
13182 editor.undo(&Default::default(), window, cx);
13183 });
13184
13185 editor.update_in(cx, |editor, window, cx| {
13186 editor.change_selections(None, window, cx, |s| {
13187 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13188 });
13189 editor.git_restore(&Default::default(), window, cx);
13190 });
13191
13192 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13193 // but not affect buffer_2 and its related excerpts.
13194 editor.update(cx, |editor, cx| {
13195 assert_eq!(
13196 editor.text(cx),
13197 "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}"
13198 );
13199 });
13200 buffer_1.update(cx, |buffer, _| {
13201 assert_eq!(buffer.text(), base_text_1);
13202 });
13203 buffer_2.update(cx, |buffer, _| {
13204 assert_eq!(
13205 buffer.text(),
13206 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13207 );
13208 });
13209 buffer_3.update(cx, |buffer, _| {
13210 assert_eq!(
13211 buffer.text(),
13212 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13213 );
13214 });
13215
13216 fn edit_first_char_of_every_line(text: &str) -> String {
13217 text.split('\n')
13218 .map(|line| format!("X{}", &line[1..]))
13219 .collect::<Vec<_>>()
13220 .join("\n")
13221 }
13222}
13223
13224#[gpui::test]
13225async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13226 init_test(cx, |_| {});
13227
13228 let cols = 4;
13229 let rows = 10;
13230 let sample_text_1 = sample_text(rows, cols, 'a');
13231 assert_eq!(
13232 sample_text_1,
13233 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13234 );
13235 let sample_text_2 = sample_text(rows, cols, 'l');
13236 assert_eq!(
13237 sample_text_2,
13238 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13239 );
13240 let sample_text_3 = sample_text(rows, cols, 'v');
13241 assert_eq!(
13242 sample_text_3,
13243 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13244 );
13245
13246 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13247 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13248 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13249
13250 let multi_buffer = cx.new(|cx| {
13251 let mut multibuffer = MultiBuffer::new(ReadWrite);
13252 multibuffer.push_excerpts(
13253 buffer_1.clone(),
13254 [
13255 ExcerptRange {
13256 context: Point::new(0, 0)..Point::new(3, 0),
13257 primary: None,
13258 },
13259 ExcerptRange {
13260 context: Point::new(5, 0)..Point::new(7, 0),
13261 primary: None,
13262 },
13263 ExcerptRange {
13264 context: Point::new(9, 0)..Point::new(10, 4),
13265 primary: None,
13266 },
13267 ],
13268 cx,
13269 );
13270 multibuffer.push_excerpts(
13271 buffer_2.clone(),
13272 [
13273 ExcerptRange {
13274 context: Point::new(0, 0)..Point::new(3, 0),
13275 primary: None,
13276 },
13277 ExcerptRange {
13278 context: Point::new(5, 0)..Point::new(7, 0),
13279 primary: None,
13280 },
13281 ExcerptRange {
13282 context: Point::new(9, 0)..Point::new(10, 4),
13283 primary: None,
13284 },
13285 ],
13286 cx,
13287 );
13288 multibuffer.push_excerpts(
13289 buffer_3.clone(),
13290 [
13291 ExcerptRange {
13292 context: Point::new(0, 0)..Point::new(3, 0),
13293 primary: None,
13294 },
13295 ExcerptRange {
13296 context: Point::new(5, 0)..Point::new(7, 0),
13297 primary: None,
13298 },
13299 ExcerptRange {
13300 context: Point::new(9, 0)..Point::new(10, 4),
13301 primary: None,
13302 },
13303 ],
13304 cx,
13305 );
13306 multibuffer
13307 });
13308
13309 let fs = FakeFs::new(cx.executor());
13310 fs.insert_tree(
13311 "/a",
13312 json!({
13313 "main.rs": sample_text_1,
13314 "other.rs": sample_text_2,
13315 "lib.rs": sample_text_3,
13316 }),
13317 )
13318 .await;
13319 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13320 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13321 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13322 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13323 Editor::new(
13324 EditorMode::Full,
13325 multi_buffer,
13326 Some(project.clone()),
13327 true,
13328 window,
13329 cx,
13330 )
13331 });
13332 let multibuffer_item_id = workspace
13333 .update(cx, |workspace, window, cx| {
13334 assert!(
13335 workspace.active_item(cx).is_none(),
13336 "active item should be None before the first item is added"
13337 );
13338 workspace.add_item_to_active_pane(
13339 Box::new(multi_buffer_editor.clone()),
13340 None,
13341 true,
13342 window,
13343 cx,
13344 );
13345 let active_item = workspace
13346 .active_item(cx)
13347 .expect("should have an active item after adding the multi buffer");
13348 assert!(
13349 !active_item.is_singleton(cx),
13350 "A multi buffer was expected to active after adding"
13351 );
13352 active_item.item_id()
13353 })
13354 .unwrap();
13355 cx.executor().run_until_parked();
13356
13357 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13358 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13359 s.select_ranges(Some(1..2))
13360 });
13361 editor.open_excerpts(&OpenExcerpts, window, cx);
13362 });
13363 cx.executor().run_until_parked();
13364 let first_item_id = workspace
13365 .update(cx, |workspace, window, cx| {
13366 let active_item = workspace
13367 .active_item(cx)
13368 .expect("should have an active item after navigating into the 1st buffer");
13369 let first_item_id = active_item.item_id();
13370 assert_ne!(
13371 first_item_id, multibuffer_item_id,
13372 "Should navigate into the 1st buffer and activate it"
13373 );
13374 assert!(
13375 active_item.is_singleton(cx),
13376 "New active item should be a singleton buffer"
13377 );
13378 assert_eq!(
13379 active_item
13380 .act_as::<Editor>(cx)
13381 .expect("should have navigated into an editor for the 1st buffer")
13382 .read(cx)
13383 .text(cx),
13384 sample_text_1
13385 );
13386
13387 workspace
13388 .go_back(workspace.active_pane().downgrade(), window, cx)
13389 .detach_and_log_err(cx);
13390
13391 first_item_id
13392 })
13393 .unwrap();
13394 cx.executor().run_until_parked();
13395 workspace
13396 .update(cx, |workspace, _, cx| {
13397 let active_item = workspace
13398 .active_item(cx)
13399 .expect("should have an active item after navigating back");
13400 assert_eq!(
13401 active_item.item_id(),
13402 multibuffer_item_id,
13403 "Should navigate back to the multi buffer"
13404 );
13405 assert!(!active_item.is_singleton(cx));
13406 })
13407 .unwrap();
13408
13409 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13410 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13411 s.select_ranges(Some(39..40))
13412 });
13413 editor.open_excerpts(&OpenExcerpts, window, cx);
13414 });
13415 cx.executor().run_until_parked();
13416 let second_item_id = workspace
13417 .update(cx, |workspace, window, cx| {
13418 let active_item = workspace
13419 .active_item(cx)
13420 .expect("should have an active item after navigating into the 2nd buffer");
13421 let second_item_id = active_item.item_id();
13422 assert_ne!(
13423 second_item_id, multibuffer_item_id,
13424 "Should navigate away from the multibuffer"
13425 );
13426 assert_ne!(
13427 second_item_id, first_item_id,
13428 "Should navigate into the 2nd buffer and activate it"
13429 );
13430 assert!(
13431 active_item.is_singleton(cx),
13432 "New active item should be a singleton buffer"
13433 );
13434 assert_eq!(
13435 active_item
13436 .act_as::<Editor>(cx)
13437 .expect("should have navigated into an editor")
13438 .read(cx)
13439 .text(cx),
13440 sample_text_2
13441 );
13442
13443 workspace
13444 .go_back(workspace.active_pane().downgrade(), window, cx)
13445 .detach_and_log_err(cx);
13446
13447 second_item_id
13448 })
13449 .unwrap();
13450 cx.executor().run_until_parked();
13451 workspace
13452 .update(cx, |workspace, _, cx| {
13453 let active_item = workspace
13454 .active_item(cx)
13455 .expect("should have an active item after navigating back from the 2nd buffer");
13456 assert_eq!(
13457 active_item.item_id(),
13458 multibuffer_item_id,
13459 "Should navigate back from the 2nd buffer to the multi buffer"
13460 );
13461 assert!(!active_item.is_singleton(cx));
13462 })
13463 .unwrap();
13464
13465 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13466 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13467 s.select_ranges(Some(70..70))
13468 });
13469 editor.open_excerpts(&OpenExcerpts, window, cx);
13470 });
13471 cx.executor().run_until_parked();
13472 workspace
13473 .update(cx, |workspace, window, cx| {
13474 let active_item = workspace
13475 .active_item(cx)
13476 .expect("should have an active item after navigating into the 3rd buffer");
13477 let third_item_id = active_item.item_id();
13478 assert_ne!(
13479 third_item_id, multibuffer_item_id,
13480 "Should navigate into the 3rd buffer and activate it"
13481 );
13482 assert_ne!(third_item_id, first_item_id);
13483 assert_ne!(third_item_id, second_item_id);
13484 assert!(
13485 active_item.is_singleton(cx),
13486 "New active item should be a singleton buffer"
13487 );
13488 assert_eq!(
13489 active_item
13490 .act_as::<Editor>(cx)
13491 .expect("should have navigated into an editor")
13492 .read(cx)
13493 .text(cx),
13494 sample_text_3
13495 );
13496
13497 workspace
13498 .go_back(workspace.active_pane().downgrade(), window, cx)
13499 .detach_and_log_err(cx);
13500 })
13501 .unwrap();
13502 cx.executor().run_until_parked();
13503 workspace
13504 .update(cx, |workspace, _, cx| {
13505 let active_item = workspace
13506 .active_item(cx)
13507 .expect("should have an active item after navigating back from the 3rd buffer");
13508 assert_eq!(
13509 active_item.item_id(),
13510 multibuffer_item_id,
13511 "Should navigate back from the 3rd buffer to the multi buffer"
13512 );
13513 assert!(!active_item.is_singleton(cx));
13514 })
13515 .unwrap();
13516}
13517
13518#[gpui::test]
13519async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13520 init_test(cx, |_| {});
13521
13522 let mut cx = EditorTestContext::new(cx).await;
13523
13524 let diff_base = r#"
13525 use some::mod;
13526
13527 const A: u32 = 42;
13528
13529 fn main() {
13530 println!("hello");
13531
13532 println!("world");
13533 }
13534 "#
13535 .unindent();
13536
13537 cx.set_state(
13538 &r#"
13539 use some::modified;
13540
13541 ˇ
13542 fn main() {
13543 println!("hello there");
13544
13545 println!("around the");
13546 println!("world");
13547 }
13548 "#
13549 .unindent(),
13550 );
13551
13552 cx.set_head_text(&diff_base);
13553 executor.run_until_parked();
13554
13555 cx.update_editor(|editor, window, cx| {
13556 editor.go_to_next_hunk(&GoToHunk, window, cx);
13557 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13558 });
13559 executor.run_until_parked();
13560 cx.assert_state_with_diff(
13561 r#"
13562 use some::modified;
13563
13564
13565 fn main() {
13566 - println!("hello");
13567 + ˇ println!("hello there");
13568
13569 println!("around the");
13570 println!("world");
13571 }
13572 "#
13573 .unindent(),
13574 );
13575
13576 cx.update_editor(|editor, window, cx| {
13577 for _ in 0..2 {
13578 editor.go_to_next_hunk(&GoToHunk, window, cx);
13579 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13580 }
13581 });
13582 executor.run_until_parked();
13583 cx.assert_state_with_diff(
13584 r#"
13585 - use some::mod;
13586 + ˇuse some::modified;
13587
13588
13589 fn main() {
13590 - println!("hello");
13591 + println!("hello there");
13592
13593 + println!("around the");
13594 println!("world");
13595 }
13596 "#
13597 .unindent(),
13598 );
13599
13600 cx.update_editor(|editor, window, cx| {
13601 editor.go_to_next_hunk(&GoToHunk, window, cx);
13602 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13603 });
13604 executor.run_until_parked();
13605 cx.assert_state_with_diff(
13606 r#"
13607 - use some::mod;
13608 + use some::modified;
13609
13610 - const A: u32 = 42;
13611 ˇ
13612 fn main() {
13613 - println!("hello");
13614 + println!("hello there");
13615
13616 + println!("around the");
13617 println!("world");
13618 }
13619 "#
13620 .unindent(),
13621 );
13622
13623 cx.update_editor(|editor, window, cx| {
13624 editor.cancel(&Cancel, window, cx);
13625 });
13626
13627 cx.assert_state_with_diff(
13628 r#"
13629 use some::modified;
13630
13631 ˇ
13632 fn main() {
13633 println!("hello there");
13634
13635 println!("around the");
13636 println!("world");
13637 }
13638 "#
13639 .unindent(),
13640 );
13641}
13642
13643#[gpui::test]
13644async fn test_diff_base_change_with_expanded_diff_hunks(
13645 executor: BackgroundExecutor,
13646 cx: &mut TestAppContext,
13647) {
13648 init_test(cx, |_| {});
13649
13650 let mut cx = EditorTestContext::new(cx).await;
13651
13652 let diff_base = r#"
13653 use some::mod1;
13654 use some::mod2;
13655
13656 const A: u32 = 42;
13657 const B: u32 = 42;
13658 const C: u32 = 42;
13659
13660 fn main() {
13661 println!("hello");
13662
13663 println!("world");
13664 }
13665 "#
13666 .unindent();
13667
13668 cx.set_state(
13669 &r#"
13670 use some::mod2;
13671
13672 const A: u32 = 42;
13673 const C: u32 = 42;
13674
13675 fn main(ˇ) {
13676 //println!("hello");
13677
13678 println!("world");
13679 //
13680 //
13681 }
13682 "#
13683 .unindent(),
13684 );
13685
13686 cx.set_head_text(&diff_base);
13687 executor.run_until_parked();
13688
13689 cx.update_editor(|editor, window, cx| {
13690 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13691 });
13692 executor.run_until_parked();
13693 cx.assert_state_with_diff(
13694 r#"
13695 - use some::mod1;
13696 use some::mod2;
13697
13698 const A: u32 = 42;
13699 - const B: u32 = 42;
13700 const C: u32 = 42;
13701
13702 fn main(ˇ) {
13703 - println!("hello");
13704 + //println!("hello");
13705
13706 println!("world");
13707 + //
13708 + //
13709 }
13710 "#
13711 .unindent(),
13712 );
13713
13714 cx.set_head_text("new diff base!");
13715 executor.run_until_parked();
13716 cx.assert_state_with_diff(
13717 r#"
13718 - new diff base!
13719 + use some::mod2;
13720 +
13721 + const A: u32 = 42;
13722 + const C: u32 = 42;
13723 +
13724 + fn main(ˇ) {
13725 + //println!("hello");
13726 +
13727 + println!("world");
13728 + //
13729 + //
13730 + }
13731 "#
13732 .unindent(),
13733 );
13734}
13735
13736#[gpui::test]
13737async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13738 init_test(cx, |_| {});
13739
13740 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13741 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13742 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13743 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13744 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13745 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13746
13747 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13748 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13749 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13750
13751 let multi_buffer = cx.new(|cx| {
13752 let mut multibuffer = MultiBuffer::new(ReadWrite);
13753 multibuffer.push_excerpts(
13754 buffer_1.clone(),
13755 [
13756 ExcerptRange {
13757 context: Point::new(0, 0)..Point::new(3, 0),
13758 primary: None,
13759 },
13760 ExcerptRange {
13761 context: Point::new(5, 0)..Point::new(7, 0),
13762 primary: None,
13763 },
13764 ExcerptRange {
13765 context: Point::new(9, 0)..Point::new(10, 3),
13766 primary: None,
13767 },
13768 ],
13769 cx,
13770 );
13771 multibuffer.push_excerpts(
13772 buffer_2.clone(),
13773 [
13774 ExcerptRange {
13775 context: Point::new(0, 0)..Point::new(3, 0),
13776 primary: None,
13777 },
13778 ExcerptRange {
13779 context: Point::new(5, 0)..Point::new(7, 0),
13780 primary: None,
13781 },
13782 ExcerptRange {
13783 context: Point::new(9, 0)..Point::new(10, 3),
13784 primary: None,
13785 },
13786 ],
13787 cx,
13788 );
13789 multibuffer.push_excerpts(
13790 buffer_3.clone(),
13791 [
13792 ExcerptRange {
13793 context: Point::new(0, 0)..Point::new(3, 0),
13794 primary: None,
13795 },
13796 ExcerptRange {
13797 context: Point::new(5, 0)..Point::new(7, 0),
13798 primary: None,
13799 },
13800 ExcerptRange {
13801 context: Point::new(9, 0)..Point::new(10, 3),
13802 primary: None,
13803 },
13804 ],
13805 cx,
13806 );
13807 multibuffer
13808 });
13809
13810 let editor = cx.add_window(|window, cx| {
13811 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13812 });
13813 editor
13814 .update(cx, |editor, _window, cx| {
13815 for (buffer, diff_base) in [
13816 (buffer_1.clone(), file_1_old),
13817 (buffer_2.clone(), file_2_old),
13818 (buffer_3.clone(), file_3_old),
13819 ] {
13820 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13821 editor
13822 .buffer
13823 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13824 }
13825 })
13826 .unwrap();
13827
13828 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13829 cx.run_until_parked();
13830
13831 cx.assert_editor_state(
13832 &"
13833 ˇaaa
13834 ccc
13835 ddd
13836
13837 ggg
13838 hhh
13839
13840
13841 lll
13842 mmm
13843 NNN
13844
13845 qqq
13846 rrr
13847
13848 uuu
13849 111
13850 222
13851 333
13852
13853 666
13854 777
13855
13856 000
13857 !!!"
13858 .unindent(),
13859 );
13860
13861 cx.update_editor(|editor, window, cx| {
13862 editor.select_all(&SelectAll, window, cx);
13863 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13864 });
13865 cx.executor().run_until_parked();
13866
13867 cx.assert_state_with_diff(
13868 "
13869 «aaa
13870 - bbb
13871 ccc
13872 ddd
13873
13874 ggg
13875 hhh
13876
13877
13878 lll
13879 mmm
13880 - nnn
13881 + NNN
13882
13883 qqq
13884 rrr
13885
13886 uuu
13887 111
13888 222
13889 333
13890
13891 + 666
13892 777
13893
13894 000
13895 !!!ˇ»"
13896 .unindent(),
13897 );
13898}
13899
13900#[gpui::test]
13901async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
13902 init_test(cx, |_| {});
13903
13904 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
13905 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
13906
13907 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
13908 let multi_buffer = cx.new(|cx| {
13909 let mut multibuffer = MultiBuffer::new(ReadWrite);
13910 multibuffer.push_excerpts(
13911 buffer.clone(),
13912 [
13913 ExcerptRange {
13914 context: Point::new(0, 0)..Point::new(2, 0),
13915 primary: None,
13916 },
13917 ExcerptRange {
13918 context: Point::new(4, 0)..Point::new(7, 0),
13919 primary: None,
13920 },
13921 ExcerptRange {
13922 context: Point::new(9, 0)..Point::new(10, 0),
13923 primary: None,
13924 },
13925 ],
13926 cx,
13927 );
13928 multibuffer
13929 });
13930
13931 let editor = cx.add_window(|window, cx| {
13932 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13933 });
13934 editor
13935 .update(cx, |editor, _window, cx| {
13936 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
13937 editor
13938 .buffer
13939 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
13940 })
13941 .unwrap();
13942
13943 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13944 cx.run_until_parked();
13945
13946 cx.update_editor(|editor, window, cx| {
13947 editor.expand_all_diff_hunks(&Default::default(), window, cx)
13948 });
13949 cx.executor().run_until_parked();
13950
13951 // When the start of a hunk coincides with the start of its excerpt,
13952 // the hunk is expanded. When the start of a a hunk is earlier than
13953 // the start of its excerpt, the hunk is not expanded.
13954 cx.assert_state_with_diff(
13955 "
13956 ˇaaa
13957 - bbb
13958 + BBB
13959
13960 - ddd
13961 - eee
13962 + DDD
13963 + EEE
13964 fff
13965
13966 iii
13967 "
13968 .unindent(),
13969 );
13970}
13971
13972#[gpui::test]
13973async fn test_edits_around_expanded_insertion_hunks(
13974 executor: BackgroundExecutor,
13975 cx: &mut TestAppContext,
13976) {
13977 init_test(cx, |_| {});
13978
13979 let mut cx = EditorTestContext::new(cx).await;
13980
13981 let diff_base = r#"
13982 use some::mod1;
13983 use some::mod2;
13984
13985 const A: u32 = 42;
13986
13987 fn main() {
13988 println!("hello");
13989
13990 println!("world");
13991 }
13992 "#
13993 .unindent();
13994 executor.run_until_parked();
13995 cx.set_state(
13996 &r#"
13997 use some::mod1;
13998 use some::mod2;
13999
14000 const A: u32 = 42;
14001 const B: u32 = 42;
14002 const C: u32 = 42;
14003 ˇ
14004
14005 fn main() {
14006 println!("hello");
14007
14008 println!("world");
14009 }
14010 "#
14011 .unindent(),
14012 );
14013
14014 cx.set_head_text(&diff_base);
14015 executor.run_until_parked();
14016
14017 cx.update_editor(|editor, window, cx| {
14018 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14019 });
14020 executor.run_until_parked();
14021
14022 cx.assert_state_with_diff(
14023 r#"
14024 use some::mod1;
14025 use some::mod2;
14026
14027 const A: u32 = 42;
14028 + const B: u32 = 42;
14029 + const C: u32 = 42;
14030 + ˇ
14031
14032 fn main() {
14033 println!("hello");
14034
14035 println!("world");
14036 }
14037 "#
14038 .unindent(),
14039 );
14040
14041 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14042 executor.run_until_parked();
14043
14044 cx.assert_state_with_diff(
14045 r#"
14046 use some::mod1;
14047 use some::mod2;
14048
14049 const A: u32 = 42;
14050 + const B: u32 = 42;
14051 + const C: u32 = 42;
14052 + const D: u32 = 42;
14053 + ˇ
14054
14055 fn main() {
14056 println!("hello");
14057
14058 println!("world");
14059 }
14060 "#
14061 .unindent(),
14062 );
14063
14064 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14065 executor.run_until_parked();
14066
14067 cx.assert_state_with_diff(
14068 r#"
14069 use some::mod1;
14070 use some::mod2;
14071
14072 const A: u32 = 42;
14073 + const B: u32 = 42;
14074 + const C: u32 = 42;
14075 + const D: u32 = 42;
14076 + const E: u32 = 42;
14077 + ˇ
14078
14079 fn main() {
14080 println!("hello");
14081
14082 println!("world");
14083 }
14084 "#
14085 .unindent(),
14086 );
14087
14088 cx.update_editor(|editor, window, cx| {
14089 editor.delete_line(&DeleteLine, window, cx);
14090 });
14091 executor.run_until_parked();
14092
14093 cx.assert_state_with_diff(
14094 r#"
14095 use some::mod1;
14096 use some::mod2;
14097
14098 const A: u32 = 42;
14099 + const B: u32 = 42;
14100 + const C: u32 = 42;
14101 + const D: u32 = 42;
14102 + const E: u32 = 42;
14103 ˇ
14104 fn main() {
14105 println!("hello");
14106
14107 println!("world");
14108 }
14109 "#
14110 .unindent(),
14111 );
14112
14113 cx.update_editor(|editor, window, cx| {
14114 editor.move_up(&MoveUp, window, cx);
14115 editor.delete_line(&DeleteLine, window, cx);
14116 editor.move_up(&MoveUp, window, cx);
14117 editor.delete_line(&DeleteLine, window, cx);
14118 editor.move_up(&MoveUp, window, cx);
14119 editor.delete_line(&DeleteLine, window, cx);
14120 });
14121 executor.run_until_parked();
14122 cx.assert_state_with_diff(
14123 r#"
14124 use some::mod1;
14125 use some::mod2;
14126
14127 const A: u32 = 42;
14128 + const B: u32 = 42;
14129 ˇ
14130 fn main() {
14131 println!("hello");
14132
14133 println!("world");
14134 }
14135 "#
14136 .unindent(),
14137 );
14138
14139 cx.update_editor(|editor, window, cx| {
14140 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14141 editor.delete_line(&DeleteLine, window, cx);
14142 });
14143 executor.run_until_parked();
14144 cx.assert_state_with_diff(
14145 r#"
14146 ˇ
14147 fn main() {
14148 println!("hello");
14149
14150 println!("world");
14151 }
14152 "#
14153 .unindent(),
14154 );
14155}
14156
14157#[gpui::test]
14158async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14159 init_test(cx, |_| {});
14160
14161 let mut cx = EditorTestContext::new(cx).await;
14162 cx.set_head_text(indoc! { "
14163 one
14164 two
14165 three
14166 four
14167 five
14168 "
14169 });
14170 cx.set_state(indoc! { "
14171 one
14172 ˇthree
14173 five
14174 "});
14175 cx.run_until_parked();
14176 cx.update_editor(|editor, window, cx| {
14177 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14178 });
14179 cx.assert_state_with_diff(
14180 indoc! { "
14181 one
14182 - two
14183 ˇthree
14184 - four
14185 five
14186 "}
14187 .to_string(),
14188 );
14189 cx.update_editor(|editor, window, cx| {
14190 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14191 });
14192
14193 cx.assert_state_with_diff(
14194 indoc! { "
14195 one
14196 ˇthree
14197 five
14198 "}
14199 .to_string(),
14200 );
14201
14202 cx.set_state(indoc! { "
14203 one
14204 ˇTWO
14205 three
14206 four
14207 five
14208 "});
14209 cx.run_until_parked();
14210 cx.update_editor(|editor, window, cx| {
14211 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14212 });
14213
14214 cx.assert_state_with_diff(
14215 indoc! { "
14216 one
14217 - two
14218 + ˇTWO
14219 three
14220 four
14221 five
14222 "}
14223 .to_string(),
14224 );
14225 cx.update_editor(|editor, window, cx| {
14226 editor.move_up(&Default::default(), window, cx);
14227 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14228 });
14229 cx.assert_state_with_diff(
14230 indoc! { "
14231 one
14232 ˇTWO
14233 three
14234 four
14235 five
14236 "}
14237 .to_string(),
14238 );
14239}
14240
14241#[gpui::test]
14242async fn test_edits_around_expanded_deletion_hunks(
14243 executor: BackgroundExecutor,
14244 cx: &mut TestAppContext,
14245) {
14246 init_test(cx, |_| {});
14247
14248 let mut cx = EditorTestContext::new(cx).await;
14249
14250 let diff_base = r#"
14251 use some::mod1;
14252 use some::mod2;
14253
14254 const A: u32 = 42;
14255 const B: u32 = 42;
14256 const C: u32 = 42;
14257
14258
14259 fn main() {
14260 println!("hello");
14261
14262 println!("world");
14263 }
14264 "#
14265 .unindent();
14266 executor.run_until_parked();
14267 cx.set_state(
14268 &r#"
14269 use some::mod1;
14270 use some::mod2;
14271
14272 ˇconst B: u32 = 42;
14273 const C: u32 = 42;
14274
14275
14276 fn main() {
14277 println!("hello");
14278
14279 println!("world");
14280 }
14281 "#
14282 .unindent(),
14283 );
14284
14285 cx.set_head_text(&diff_base);
14286 executor.run_until_parked();
14287
14288 cx.update_editor(|editor, window, cx| {
14289 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14290 });
14291 executor.run_until_parked();
14292
14293 cx.assert_state_with_diff(
14294 r#"
14295 use some::mod1;
14296 use some::mod2;
14297
14298 - const A: u32 = 42;
14299 ˇconst B: u32 = 42;
14300 const C: u32 = 42;
14301
14302
14303 fn main() {
14304 println!("hello");
14305
14306 println!("world");
14307 }
14308 "#
14309 .unindent(),
14310 );
14311
14312 cx.update_editor(|editor, window, cx| {
14313 editor.delete_line(&DeleteLine, window, cx);
14314 });
14315 executor.run_until_parked();
14316 cx.assert_state_with_diff(
14317 r#"
14318 use some::mod1;
14319 use some::mod2;
14320
14321 - const A: u32 = 42;
14322 - const B: u32 = 42;
14323 ˇconst C: u32 = 42;
14324
14325
14326 fn main() {
14327 println!("hello");
14328
14329 println!("world");
14330 }
14331 "#
14332 .unindent(),
14333 );
14334
14335 cx.update_editor(|editor, window, cx| {
14336 editor.delete_line(&DeleteLine, window, cx);
14337 });
14338 executor.run_until_parked();
14339 cx.assert_state_with_diff(
14340 r#"
14341 use some::mod1;
14342 use some::mod2;
14343
14344 - const A: u32 = 42;
14345 - const B: u32 = 42;
14346 - const C: u32 = 42;
14347 ˇ
14348
14349 fn main() {
14350 println!("hello");
14351
14352 println!("world");
14353 }
14354 "#
14355 .unindent(),
14356 );
14357
14358 cx.update_editor(|editor, window, cx| {
14359 editor.handle_input("replacement", window, cx);
14360 });
14361 executor.run_until_parked();
14362 cx.assert_state_with_diff(
14363 r#"
14364 use some::mod1;
14365 use some::mod2;
14366
14367 - const A: u32 = 42;
14368 - const B: u32 = 42;
14369 - const C: u32 = 42;
14370 -
14371 + replacementˇ
14372
14373 fn main() {
14374 println!("hello");
14375
14376 println!("world");
14377 }
14378 "#
14379 .unindent(),
14380 );
14381}
14382
14383#[gpui::test]
14384async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14385 init_test(cx, |_| {});
14386
14387 let mut cx = EditorTestContext::new(cx).await;
14388
14389 let base_text = r#"
14390 one
14391 two
14392 three
14393 four
14394 five
14395 "#
14396 .unindent();
14397 executor.run_until_parked();
14398 cx.set_state(
14399 &r#"
14400 one
14401 two
14402 fˇour
14403 five
14404 "#
14405 .unindent(),
14406 );
14407
14408 cx.set_head_text(&base_text);
14409 executor.run_until_parked();
14410
14411 cx.update_editor(|editor, window, cx| {
14412 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14413 });
14414 executor.run_until_parked();
14415
14416 cx.assert_state_with_diff(
14417 r#"
14418 one
14419 two
14420 - three
14421 fˇour
14422 five
14423 "#
14424 .unindent(),
14425 );
14426
14427 cx.update_editor(|editor, window, cx| {
14428 editor.backspace(&Backspace, window, cx);
14429 editor.backspace(&Backspace, window, cx);
14430 });
14431 executor.run_until_parked();
14432 cx.assert_state_with_diff(
14433 r#"
14434 one
14435 two
14436 - threeˇ
14437 - four
14438 + our
14439 five
14440 "#
14441 .unindent(),
14442 );
14443}
14444
14445#[gpui::test]
14446async fn test_edit_after_expanded_modification_hunk(
14447 executor: BackgroundExecutor,
14448 cx: &mut TestAppContext,
14449) {
14450 init_test(cx, |_| {});
14451
14452 let mut cx = EditorTestContext::new(cx).await;
14453
14454 let diff_base = r#"
14455 use some::mod1;
14456 use some::mod2;
14457
14458 const A: u32 = 42;
14459 const B: u32 = 42;
14460 const C: u32 = 42;
14461 const D: u32 = 42;
14462
14463
14464 fn main() {
14465 println!("hello");
14466
14467 println!("world");
14468 }"#
14469 .unindent();
14470
14471 cx.set_state(
14472 &r#"
14473 use some::mod1;
14474 use some::mod2;
14475
14476 const A: u32 = 42;
14477 const B: u32 = 42;
14478 const C: u32 = 43ˇ
14479 const D: u32 = 42;
14480
14481
14482 fn main() {
14483 println!("hello");
14484
14485 println!("world");
14486 }"#
14487 .unindent(),
14488 );
14489
14490 cx.set_head_text(&diff_base);
14491 executor.run_until_parked();
14492 cx.update_editor(|editor, window, cx| {
14493 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14494 });
14495 executor.run_until_parked();
14496
14497 cx.assert_state_with_diff(
14498 r#"
14499 use some::mod1;
14500 use some::mod2;
14501
14502 const A: u32 = 42;
14503 const B: u32 = 42;
14504 - const C: u32 = 42;
14505 + const C: u32 = 43ˇ
14506 const D: u32 = 42;
14507
14508
14509 fn main() {
14510 println!("hello");
14511
14512 println!("world");
14513 }"#
14514 .unindent(),
14515 );
14516
14517 cx.update_editor(|editor, window, cx| {
14518 editor.handle_input("\nnew_line\n", window, cx);
14519 });
14520 executor.run_until_parked();
14521
14522 cx.assert_state_with_diff(
14523 r#"
14524 use some::mod1;
14525 use some::mod2;
14526
14527 const A: u32 = 42;
14528 const B: u32 = 42;
14529 - const C: u32 = 42;
14530 + const C: u32 = 43
14531 + new_line
14532 + ˇ
14533 const D: u32 = 42;
14534
14535
14536 fn main() {
14537 println!("hello");
14538
14539 println!("world");
14540 }"#
14541 .unindent(),
14542 );
14543}
14544
14545#[gpui::test]
14546async fn test_stage_and_unstage_added_file_hunk(
14547 executor: BackgroundExecutor,
14548 cx: &mut TestAppContext,
14549) {
14550 init_test(cx, |_| {});
14551
14552 let mut cx = EditorTestContext::new(cx).await;
14553 cx.update_editor(|editor, _, cx| {
14554 editor.set_expand_all_diff_hunks(cx);
14555 });
14556
14557 let working_copy = r#"
14558 ˇfn main() {
14559 println!("hello, world!");
14560 }
14561 "#
14562 .unindent();
14563
14564 cx.set_state(&working_copy);
14565 executor.run_until_parked();
14566
14567 cx.assert_state_with_diff(
14568 r#"
14569 + ˇfn main() {
14570 + println!("hello, world!");
14571 + }
14572 "#
14573 .unindent(),
14574 );
14575 cx.assert_index_text(None);
14576
14577 cx.update_editor(|editor, window, cx| {
14578 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14579 });
14580 executor.run_until_parked();
14581 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14582 cx.assert_state_with_diff(
14583 r#"
14584 + ˇfn main() {
14585 + println!("hello, world!");
14586 + }
14587 "#
14588 .unindent(),
14589 );
14590
14591 cx.update_editor(|editor, window, cx| {
14592 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14593 });
14594 executor.run_until_parked();
14595 cx.assert_index_text(None);
14596}
14597
14598async fn setup_indent_guides_editor(
14599 text: &str,
14600 cx: &mut TestAppContext,
14601) -> (BufferId, EditorTestContext) {
14602 init_test(cx, |_| {});
14603
14604 let mut cx = EditorTestContext::new(cx).await;
14605
14606 let buffer_id = cx.update_editor(|editor, window, cx| {
14607 editor.set_text(text, window, cx);
14608 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14609
14610 buffer_ids[0]
14611 });
14612
14613 (buffer_id, cx)
14614}
14615
14616fn assert_indent_guides(
14617 range: Range<u32>,
14618 expected: Vec<IndentGuide>,
14619 active_indices: Option<Vec<usize>>,
14620 cx: &mut EditorTestContext,
14621) {
14622 let indent_guides = cx.update_editor(|editor, window, cx| {
14623 let snapshot = editor.snapshot(window, cx).display_snapshot;
14624 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14625 editor,
14626 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14627 true,
14628 &snapshot,
14629 cx,
14630 );
14631
14632 indent_guides.sort_by(|a, b| {
14633 a.depth.cmp(&b.depth).then(
14634 a.start_row
14635 .cmp(&b.start_row)
14636 .then(a.end_row.cmp(&b.end_row)),
14637 )
14638 });
14639 indent_guides
14640 });
14641
14642 if let Some(expected) = active_indices {
14643 let active_indices = cx.update_editor(|editor, window, cx| {
14644 let snapshot = editor.snapshot(window, cx).display_snapshot;
14645 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14646 });
14647
14648 assert_eq!(
14649 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14650 expected,
14651 "Active indent guide indices do not match"
14652 );
14653 }
14654
14655 assert_eq!(indent_guides, expected, "Indent guides do not match");
14656}
14657
14658fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14659 IndentGuide {
14660 buffer_id,
14661 start_row: MultiBufferRow(start_row),
14662 end_row: MultiBufferRow(end_row),
14663 depth,
14664 tab_size: 4,
14665 settings: IndentGuideSettings {
14666 enabled: true,
14667 line_width: 1,
14668 active_line_width: 1,
14669 ..Default::default()
14670 },
14671 }
14672}
14673
14674#[gpui::test]
14675async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14676 let (buffer_id, mut cx) = setup_indent_guides_editor(
14677 &"
14678 fn main() {
14679 let a = 1;
14680 }"
14681 .unindent(),
14682 cx,
14683 )
14684 .await;
14685
14686 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14687}
14688
14689#[gpui::test]
14690async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14691 let (buffer_id, mut cx) = setup_indent_guides_editor(
14692 &"
14693 fn main() {
14694 let a = 1;
14695 let b = 2;
14696 }"
14697 .unindent(),
14698 cx,
14699 )
14700 .await;
14701
14702 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14703}
14704
14705#[gpui::test]
14706async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14707 let (buffer_id, mut cx) = setup_indent_guides_editor(
14708 &"
14709 fn main() {
14710 let a = 1;
14711 if a == 3 {
14712 let b = 2;
14713 } else {
14714 let c = 3;
14715 }
14716 }"
14717 .unindent(),
14718 cx,
14719 )
14720 .await;
14721
14722 assert_indent_guides(
14723 0..8,
14724 vec![
14725 indent_guide(buffer_id, 1, 6, 0),
14726 indent_guide(buffer_id, 3, 3, 1),
14727 indent_guide(buffer_id, 5, 5, 1),
14728 ],
14729 None,
14730 &mut cx,
14731 );
14732}
14733
14734#[gpui::test]
14735async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14736 let (buffer_id, mut cx) = setup_indent_guides_editor(
14737 &"
14738 fn main() {
14739 let a = 1;
14740 let b = 2;
14741 let c = 3;
14742 }"
14743 .unindent(),
14744 cx,
14745 )
14746 .await;
14747
14748 assert_indent_guides(
14749 0..5,
14750 vec![
14751 indent_guide(buffer_id, 1, 3, 0),
14752 indent_guide(buffer_id, 2, 2, 1),
14753 ],
14754 None,
14755 &mut cx,
14756 );
14757}
14758
14759#[gpui::test]
14760async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14761 let (buffer_id, mut cx) = setup_indent_guides_editor(
14762 &"
14763 fn main() {
14764 let a = 1;
14765
14766 let c = 3;
14767 }"
14768 .unindent(),
14769 cx,
14770 )
14771 .await;
14772
14773 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14774}
14775
14776#[gpui::test]
14777async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14778 let (buffer_id, mut cx) = setup_indent_guides_editor(
14779 &"
14780 fn main() {
14781 let a = 1;
14782
14783 let c = 3;
14784
14785 if a == 3 {
14786 let b = 2;
14787 } else {
14788 let c = 3;
14789 }
14790 }"
14791 .unindent(),
14792 cx,
14793 )
14794 .await;
14795
14796 assert_indent_guides(
14797 0..11,
14798 vec![
14799 indent_guide(buffer_id, 1, 9, 0),
14800 indent_guide(buffer_id, 6, 6, 1),
14801 indent_guide(buffer_id, 8, 8, 1),
14802 ],
14803 None,
14804 &mut cx,
14805 );
14806}
14807
14808#[gpui::test]
14809async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14810 let (buffer_id, mut cx) = setup_indent_guides_editor(
14811 &"
14812 fn main() {
14813 let a = 1;
14814
14815 let c = 3;
14816
14817 if a == 3 {
14818 let b = 2;
14819 } else {
14820 let c = 3;
14821 }
14822 }"
14823 .unindent(),
14824 cx,
14825 )
14826 .await;
14827
14828 assert_indent_guides(
14829 1..11,
14830 vec![
14831 indent_guide(buffer_id, 1, 9, 0),
14832 indent_guide(buffer_id, 6, 6, 1),
14833 indent_guide(buffer_id, 8, 8, 1),
14834 ],
14835 None,
14836 &mut cx,
14837 );
14838}
14839
14840#[gpui::test]
14841async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14842 let (buffer_id, mut cx) = setup_indent_guides_editor(
14843 &"
14844 fn main() {
14845 let a = 1;
14846
14847 let c = 3;
14848
14849 if a == 3 {
14850 let b = 2;
14851 } else {
14852 let c = 3;
14853 }
14854 }"
14855 .unindent(),
14856 cx,
14857 )
14858 .await;
14859
14860 assert_indent_guides(
14861 1..10,
14862 vec![
14863 indent_guide(buffer_id, 1, 9, 0),
14864 indent_guide(buffer_id, 6, 6, 1),
14865 indent_guide(buffer_id, 8, 8, 1),
14866 ],
14867 None,
14868 &mut cx,
14869 );
14870}
14871
14872#[gpui::test]
14873async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14874 let (buffer_id, mut cx) = setup_indent_guides_editor(
14875 &"
14876 block1
14877 block2
14878 block3
14879 block4
14880 block2
14881 block1
14882 block1"
14883 .unindent(),
14884 cx,
14885 )
14886 .await;
14887
14888 assert_indent_guides(
14889 1..10,
14890 vec![
14891 indent_guide(buffer_id, 1, 4, 0),
14892 indent_guide(buffer_id, 2, 3, 1),
14893 indent_guide(buffer_id, 3, 3, 2),
14894 ],
14895 None,
14896 &mut cx,
14897 );
14898}
14899
14900#[gpui::test]
14901async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
14902 let (buffer_id, mut cx) = setup_indent_guides_editor(
14903 &"
14904 block1
14905 block2
14906 block3
14907
14908 block1
14909 block1"
14910 .unindent(),
14911 cx,
14912 )
14913 .await;
14914
14915 assert_indent_guides(
14916 0..6,
14917 vec![
14918 indent_guide(buffer_id, 1, 2, 0),
14919 indent_guide(buffer_id, 2, 2, 1),
14920 ],
14921 None,
14922 &mut cx,
14923 );
14924}
14925
14926#[gpui::test]
14927async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
14928 let (buffer_id, mut cx) = setup_indent_guides_editor(
14929 &"
14930 block1
14931
14932
14933
14934 block2
14935 "
14936 .unindent(),
14937 cx,
14938 )
14939 .await;
14940
14941 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14942}
14943
14944#[gpui::test]
14945async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
14946 let (buffer_id, mut cx) = setup_indent_guides_editor(
14947 &"
14948 def a:
14949 \tb = 3
14950 \tif True:
14951 \t\tc = 4
14952 \t\td = 5
14953 \tprint(b)
14954 "
14955 .unindent(),
14956 cx,
14957 )
14958 .await;
14959
14960 assert_indent_guides(
14961 0..6,
14962 vec![
14963 indent_guide(buffer_id, 1, 6, 0),
14964 indent_guide(buffer_id, 3, 4, 1),
14965 ],
14966 None,
14967 &mut cx,
14968 );
14969}
14970
14971#[gpui::test]
14972async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
14973 let (buffer_id, mut cx) = setup_indent_guides_editor(
14974 &"
14975 fn main() {
14976 let a = 1;
14977 }"
14978 .unindent(),
14979 cx,
14980 )
14981 .await;
14982
14983 cx.update_editor(|editor, window, cx| {
14984 editor.change_selections(None, window, cx, |s| {
14985 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14986 });
14987 });
14988
14989 assert_indent_guides(
14990 0..3,
14991 vec![indent_guide(buffer_id, 1, 1, 0)],
14992 Some(vec![0]),
14993 &mut cx,
14994 );
14995}
14996
14997#[gpui::test]
14998async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
14999 let (buffer_id, mut cx) = setup_indent_guides_editor(
15000 &"
15001 fn main() {
15002 if 1 == 2 {
15003 let a = 1;
15004 }
15005 }"
15006 .unindent(),
15007 cx,
15008 )
15009 .await;
15010
15011 cx.update_editor(|editor, window, cx| {
15012 editor.change_selections(None, window, cx, |s| {
15013 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15014 });
15015 });
15016
15017 assert_indent_guides(
15018 0..4,
15019 vec![
15020 indent_guide(buffer_id, 1, 3, 0),
15021 indent_guide(buffer_id, 2, 2, 1),
15022 ],
15023 Some(vec![1]),
15024 &mut cx,
15025 );
15026
15027 cx.update_editor(|editor, window, cx| {
15028 editor.change_selections(None, window, cx, |s| {
15029 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15030 });
15031 });
15032
15033 assert_indent_guides(
15034 0..4,
15035 vec![
15036 indent_guide(buffer_id, 1, 3, 0),
15037 indent_guide(buffer_id, 2, 2, 1),
15038 ],
15039 Some(vec![1]),
15040 &mut cx,
15041 );
15042
15043 cx.update_editor(|editor, window, cx| {
15044 editor.change_selections(None, window, cx, |s| {
15045 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15046 });
15047 });
15048
15049 assert_indent_guides(
15050 0..4,
15051 vec![
15052 indent_guide(buffer_id, 1, 3, 0),
15053 indent_guide(buffer_id, 2, 2, 1),
15054 ],
15055 Some(vec![0]),
15056 &mut cx,
15057 );
15058}
15059
15060#[gpui::test]
15061async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15062 let (buffer_id, mut cx) = setup_indent_guides_editor(
15063 &"
15064 fn main() {
15065 let a = 1;
15066
15067 let b = 2;
15068 }"
15069 .unindent(),
15070 cx,
15071 )
15072 .await;
15073
15074 cx.update_editor(|editor, window, cx| {
15075 editor.change_selections(None, window, cx, |s| {
15076 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15077 });
15078 });
15079
15080 assert_indent_guides(
15081 0..5,
15082 vec![indent_guide(buffer_id, 1, 3, 0)],
15083 Some(vec![0]),
15084 &mut cx,
15085 );
15086}
15087
15088#[gpui::test]
15089async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15090 let (buffer_id, mut cx) = setup_indent_guides_editor(
15091 &"
15092 def m:
15093 a = 1
15094 pass"
15095 .unindent(),
15096 cx,
15097 )
15098 .await;
15099
15100 cx.update_editor(|editor, window, cx| {
15101 editor.change_selections(None, window, cx, |s| {
15102 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15103 });
15104 });
15105
15106 assert_indent_guides(
15107 0..3,
15108 vec![indent_guide(buffer_id, 1, 2, 0)],
15109 Some(vec![0]),
15110 &mut cx,
15111 );
15112}
15113
15114#[gpui::test]
15115async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15116 init_test(cx, |_| {});
15117 let mut cx = EditorTestContext::new(cx).await;
15118 let text = indoc! {
15119 "
15120 impl A {
15121 fn b() {
15122 0;
15123 3;
15124 5;
15125 6;
15126 7;
15127 }
15128 }
15129 "
15130 };
15131 let base_text = indoc! {
15132 "
15133 impl A {
15134 fn b() {
15135 0;
15136 1;
15137 2;
15138 3;
15139 4;
15140 }
15141 fn c() {
15142 5;
15143 6;
15144 7;
15145 }
15146 }
15147 "
15148 };
15149
15150 cx.update_editor(|editor, window, cx| {
15151 editor.set_text(text, window, cx);
15152
15153 editor.buffer().update(cx, |multibuffer, cx| {
15154 let buffer = multibuffer.as_singleton().unwrap();
15155 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15156
15157 multibuffer.set_all_diff_hunks_expanded(cx);
15158 multibuffer.add_diff(diff, cx);
15159
15160 buffer.read(cx).remote_id()
15161 })
15162 });
15163 cx.run_until_parked();
15164
15165 cx.assert_state_with_diff(
15166 indoc! { "
15167 impl A {
15168 fn b() {
15169 0;
15170 - 1;
15171 - 2;
15172 3;
15173 - 4;
15174 - }
15175 - fn c() {
15176 5;
15177 6;
15178 7;
15179 }
15180 }
15181 ˇ"
15182 }
15183 .to_string(),
15184 );
15185
15186 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15187 editor
15188 .snapshot(window, cx)
15189 .buffer_snapshot
15190 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15191 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15192 .collect::<Vec<_>>()
15193 });
15194 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15195 assert_eq!(
15196 actual_guides,
15197 vec![
15198 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15199 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15200 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15201 ]
15202 );
15203}
15204
15205#[gpui::test]
15206async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15207 init_test(cx, |_| {});
15208 let mut cx = EditorTestContext::new(cx).await;
15209
15210 let diff_base = r#"
15211 a
15212 b
15213 c
15214 "#
15215 .unindent();
15216
15217 cx.set_state(
15218 &r#"
15219 ˇA
15220 b
15221 C
15222 "#
15223 .unindent(),
15224 );
15225 cx.set_head_text(&diff_base);
15226 cx.update_editor(|editor, window, cx| {
15227 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15228 });
15229 executor.run_until_parked();
15230
15231 let both_hunks_expanded = r#"
15232 - a
15233 + ˇA
15234 b
15235 - c
15236 + C
15237 "#
15238 .unindent();
15239
15240 cx.assert_state_with_diff(both_hunks_expanded.clone());
15241
15242 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15243 let snapshot = editor.snapshot(window, cx);
15244 let hunks = editor
15245 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15246 .collect::<Vec<_>>();
15247 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15248 let buffer_id = hunks[0].buffer_id;
15249 hunks
15250 .into_iter()
15251 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15252 .collect::<Vec<_>>()
15253 });
15254 assert_eq!(hunk_ranges.len(), 2);
15255
15256 cx.update_editor(|editor, _, cx| {
15257 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15258 });
15259 executor.run_until_parked();
15260
15261 let second_hunk_expanded = r#"
15262 ˇA
15263 b
15264 - c
15265 + C
15266 "#
15267 .unindent();
15268
15269 cx.assert_state_with_diff(second_hunk_expanded);
15270
15271 cx.update_editor(|editor, _, cx| {
15272 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15273 });
15274 executor.run_until_parked();
15275
15276 cx.assert_state_with_diff(both_hunks_expanded.clone());
15277
15278 cx.update_editor(|editor, _, cx| {
15279 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15280 });
15281 executor.run_until_parked();
15282
15283 let first_hunk_expanded = r#"
15284 - a
15285 + ˇA
15286 b
15287 C
15288 "#
15289 .unindent();
15290
15291 cx.assert_state_with_diff(first_hunk_expanded);
15292
15293 cx.update_editor(|editor, _, cx| {
15294 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15295 });
15296 executor.run_until_parked();
15297
15298 cx.assert_state_with_diff(both_hunks_expanded);
15299
15300 cx.set_state(
15301 &r#"
15302 ˇA
15303 b
15304 "#
15305 .unindent(),
15306 );
15307 cx.run_until_parked();
15308
15309 // TODO this cursor position seems bad
15310 cx.assert_state_with_diff(
15311 r#"
15312 - ˇa
15313 + A
15314 b
15315 "#
15316 .unindent(),
15317 );
15318
15319 cx.update_editor(|editor, window, cx| {
15320 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15321 });
15322
15323 cx.assert_state_with_diff(
15324 r#"
15325 - ˇa
15326 + A
15327 b
15328 - c
15329 "#
15330 .unindent(),
15331 );
15332
15333 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15334 let snapshot = editor.snapshot(window, cx);
15335 let hunks = editor
15336 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15337 .collect::<Vec<_>>();
15338 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15339 let buffer_id = hunks[0].buffer_id;
15340 hunks
15341 .into_iter()
15342 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15343 .collect::<Vec<_>>()
15344 });
15345 assert_eq!(hunk_ranges.len(), 2);
15346
15347 cx.update_editor(|editor, _, cx| {
15348 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15349 });
15350 executor.run_until_parked();
15351
15352 cx.assert_state_with_diff(
15353 r#"
15354 - ˇa
15355 + A
15356 b
15357 "#
15358 .unindent(),
15359 );
15360}
15361
15362#[gpui::test]
15363async fn test_toggle_deletion_hunk_at_start_of_file(
15364 executor: BackgroundExecutor,
15365 cx: &mut TestAppContext,
15366) {
15367 init_test(cx, |_| {});
15368 let mut cx = EditorTestContext::new(cx).await;
15369
15370 let diff_base = r#"
15371 a
15372 b
15373 c
15374 "#
15375 .unindent();
15376
15377 cx.set_state(
15378 &r#"
15379 ˇb
15380 c
15381 "#
15382 .unindent(),
15383 );
15384 cx.set_head_text(&diff_base);
15385 cx.update_editor(|editor, window, cx| {
15386 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15387 });
15388 executor.run_until_parked();
15389
15390 let hunk_expanded = r#"
15391 - a
15392 ˇb
15393 c
15394 "#
15395 .unindent();
15396
15397 cx.assert_state_with_diff(hunk_expanded.clone());
15398
15399 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15400 let snapshot = editor.snapshot(window, cx);
15401 let hunks = editor
15402 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15403 .collect::<Vec<_>>();
15404 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15405 let buffer_id = hunks[0].buffer_id;
15406 hunks
15407 .into_iter()
15408 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15409 .collect::<Vec<_>>()
15410 });
15411 assert_eq!(hunk_ranges.len(), 1);
15412
15413 cx.update_editor(|editor, _, cx| {
15414 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15415 });
15416 executor.run_until_parked();
15417
15418 let hunk_collapsed = r#"
15419 ˇb
15420 c
15421 "#
15422 .unindent();
15423
15424 cx.assert_state_with_diff(hunk_collapsed);
15425
15426 cx.update_editor(|editor, _, cx| {
15427 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15428 });
15429 executor.run_until_parked();
15430
15431 cx.assert_state_with_diff(hunk_expanded.clone());
15432}
15433
15434#[gpui::test]
15435async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15436 init_test(cx, |_| {});
15437
15438 let fs = FakeFs::new(cx.executor());
15439 fs.insert_tree(
15440 path!("/test"),
15441 json!({
15442 ".git": {},
15443 "file-1": "ONE\n",
15444 "file-2": "TWO\n",
15445 "file-3": "THREE\n",
15446 }),
15447 )
15448 .await;
15449
15450 fs.set_head_for_repo(
15451 path!("/test/.git").as_ref(),
15452 &[
15453 ("file-1".into(), "one\n".into()),
15454 ("file-2".into(), "two\n".into()),
15455 ("file-3".into(), "three\n".into()),
15456 ],
15457 );
15458
15459 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15460 let mut buffers = vec![];
15461 for i in 1..=3 {
15462 let buffer = project
15463 .update(cx, |project, cx| {
15464 let path = format!(path!("/test/file-{}"), i);
15465 project.open_local_buffer(path, cx)
15466 })
15467 .await
15468 .unwrap();
15469 buffers.push(buffer);
15470 }
15471
15472 let multibuffer = cx.new(|cx| {
15473 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15474 multibuffer.set_all_diff_hunks_expanded(cx);
15475 for buffer in &buffers {
15476 let snapshot = buffer.read(cx).snapshot();
15477 multibuffer.set_excerpts_for_path(
15478 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15479 buffer.clone(),
15480 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15481 DEFAULT_MULTIBUFFER_CONTEXT,
15482 cx,
15483 );
15484 }
15485 multibuffer
15486 });
15487
15488 let editor = cx.add_window(|window, cx| {
15489 Editor::new(
15490 EditorMode::Full,
15491 multibuffer,
15492 Some(project),
15493 true,
15494 window,
15495 cx,
15496 )
15497 });
15498 cx.run_until_parked();
15499
15500 let snapshot = editor
15501 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15502 .unwrap();
15503 let hunks = snapshot
15504 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15505 .map(|hunk| match hunk {
15506 DisplayDiffHunk::Unfolded {
15507 display_row_range, ..
15508 } => display_row_range,
15509 DisplayDiffHunk::Folded { .. } => unreachable!(),
15510 })
15511 .collect::<Vec<_>>();
15512 assert_eq!(
15513 hunks,
15514 [
15515 DisplayRow(3)..DisplayRow(5),
15516 DisplayRow(10)..DisplayRow(12),
15517 DisplayRow(17)..DisplayRow(19),
15518 ]
15519 );
15520}
15521
15522#[gpui::test]
15523async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15524 init_test(cx, |_| {});
15525
15526 let mut cx = EditorTestContext::new(cx).await;
15527 cx.set_head_text(indoc! { "
15528 one
15529 two
15530 three
15531 four
15532 five
15533 "
15534 });
15535 cx.set_index_text(indoc! { "
15536 one
15537 two
15538 three
15539 four
15540 five
15541 "
15542 });
15543 cx.set_state(indoc! {"
15544 one
15545 TWO
15546 ˇTHREE
15547 FOUR
15548 five
15549 "});
15550 cx.run_until_parked();
15551 cx.update_editor(|editor, window, cx| {
15552 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15553 });
15554 cx.run_until_parked();
15555 cx.assert_index_text(Some(indoc! {"
15556 one
15557 TWO
15558 THREE
15559 FOUR
15560 five
15561 "}));
15562 cx.set_state(indoc! { "
15563 one
15564 TWO
15565 ˇTHREE-HUNDRED
15566 FOUR
15567 five
15568 "});
15569 cx.run_until_parked();
15570 cx.update_editor(|editor, window, cx| {
15571 let snapshot = editor.snapshot(window, cx);
15572 let hunks = editor
15573 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15574 .collect::<Vec<_>>();
15575 assert_eq!(hunks.len(), 1);
15576 assert_eq!(
15577 hunks[0].status(),
15578 DiffHunkStatus {
15579 kind: DiffHunkStatusKind::Modified,
15580 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15581 }
15582 );
15583
15584 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15585 });
15586 cx.run_until_parked();
15587 cx.assert_index_text(Some(indoc! {"
15588 one
15589 TWO
15590 THREE-HUNDRED
15591 FOUR
15592 five
15593 "}));
15594}
15595
15596#[gpui::test]
15597fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15598 init_test(cx, |_| {});
15599
15600 let editor = cx.add_window(|window, cx| {
15601 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15602 build_editor(buffer, window, cx)
15603 });
15604
15605 let render_args = Arc::new(Mutex::new(None));
15606 let snapshot = editor
15607 .update(cx, |editor, window, cx| {
15608 let snapshot = editor.buffer().read(cx).snapshot(cx);
15609 let range =
15610 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15611
15612 struct RenderArgs {
15613 row: MultiBufferRow,
15614 folded: bool,
15615 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15616 }
15617
15618 let crease = Crease::inline(
15619 range,
15620 FoldPlaceholder::test(),
15621 {
15622 let toggle_callback = render_args.clone();
15623 move |row, folded, callback, _window, _cx| {
15624 *toggle_callback.lock() = Some(RenderArgs {
15625 row,
15626 folded,
15627 callback,
15628 });
15629 div()
15630 }
15631 },
15632 |_row, _folded, _window, _cx| div(),
15633 );
15634
15635 editor.insert_creases(Some(crease), cx);
15636 let snapshot = editor.snapshot(window, cx);
15637 let _div = snapshot.render_crease_toggle(
15638 MultiBufferRow(1),
15639 false,
15640 cx.entity().clone(),
15641 window,
15642 cx,
15643 );
15644 snapshot
15645 })
15646 .unwrap();
15647
15648 let render_args = render_args.lock().take().unwrap();
15649 assert_eq!(render_args.row, MultiBufferRow(1));
15650 assert!(!render_args.folded);
15651 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15652
15653 cx.update_window(*editor, |_, window, cx| {
15654 (render_args.callback)(true, window, cx)
15655 })
15656 .unwrap();
15657 let snapshot = editor
15658 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15659 .unwrap();
15660 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15661
15662 cx.update_window(*editor, |_, window, cx| {
15663 (render_args.callback)(false, window, cx)
15664 })
15665 .unwrap();
15666 let snapshot = editor
15667 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15668 .unwrap();
15669 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15670}
15671
15672#[gpui::test]
15673async fn test_input_text(cx: &mut TestAppContext) {
15674 init_test(cx, |_| {});
15675 let mut cx = EditorTestContext::new(cx).await;
15676
15677 cx.set_state(
15678 &r#"ˇone
15679 two
15680
15681 three
15682 fourˇ
15683 five
15684
15685 siˇx"#
15686 .unindent(),
15687 );
15688
15689 cx.dispatch_action(HandleInput(String::new()));
15690 cx.assert_editor_state(
15691 &r#"ˇone
15692 two
15693
15694 three
15695 fourˇ
15696 five
15697
15698 siˇx"#
15699 .unindent(),
15700 );
15701
15702 cx.dispatch_action(HandleInput("AAAA".to_string()));
15703 cx.assert_editor_state(
15704 &r#"AAAAˇone
15705 two
15706
15707 three
15708 fourAAAAˇ
15709 five
15710
15711 siAAAAˇx"#
15712 .unindent(),
15713 );
15714}
15715
15716#[gpui::test]
15717async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15718 init_test(cx, |_| {});
15719
15720 let mut cx = EditorTestContext::new(cx).await;
15721 cx.set_state(
15722 r#"let foo = 1;
15723let foo = 2;
15724let foo = 3;
15725let fooˇ = 4;
15726let foo = 5;
15727let foo = 6;
15728let foo = 7;
15729let foo = 8;
15730let foo = 9;
15731let foo = 10;
15732let foo = 11;
15733let foo = 12;
15734let foo = 13;
15735let foo = 14;
15736let foo = 15;"#,
15737 );
15738
15739 cx.update_editor(|e, window, cx| {
15740 assert_eq!(
15741 e.next_scroll_position,
15742 NextScrollCursorCenterTopBottom::Center,
15743 "Default next scroll direction is center",
15744 );
15745
15746 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15747 assert_eq!(
15748 e.next_scroll_position,
15749 NextScrollCursorCenterTopBottom::Top,
15750 "After center, next scroll direction should be top",
15751 );
15752
15753 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15754 assert_eq!(
15755 e.next_scroll_position,
15756 NextScrollCursorCenterTopBottom::Bottom,
15757 "After top, next scroll direction should be bottom",
15758 );
15759
15760 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15761 assert_eq!(
15762 e.next_scroll_position,
15763 NextScrollCursorCenterTopBottom::Center,
15764 "After bottom, scrolling should start over",
15765 );
15766
15767 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15768 assert_eq!(
15769 e.next_scroll_position,
15770 NextScrollCursorCenterTopBottom::Top,
15771 "Scrolling continues if retriggered fast enough"
15772 );
15773 });
15774
15775 cx.executor()
15776 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15777 cx.executor().run_until_parked();
15778 cx.update_editor(|e, _, _| {
15779 assert_eq!(
15780 e.next_scroll_position,
15781 NextScrollCursorCenterTopBottom::Center,
15782 "If scrolling is not triggered fast enough, it should reset"
15783 );
15784 });
15785}
15786
15787#[gpui::test]
15788async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15789 init_test(cx, |_| {});
15790 let mut cx = EditorLspTestContext::new_rust(
15791 lsp::ServerCapabilities {
15792 definition_provider: Some(lsp::OneOf::Left(true)),
15793 references_provider: Some(lsp::OneOf::Left(true)),
15794 ..lsp::ServerCapabilities::default()
15795 },
15796 cx,
15797 )
15798 .await;
15799
15800 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15801 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15802 move |params, _| async move {
15803 if empty_go_to_definition {
15804 Ok(None)
15805 } else {
15806 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15807 uri: params.text_document_position_params.text_document.uri,
15808 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15809 })))
15810 }
15811 },
15812 );
15813 let references =
15814 cx.lsp
15815 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15816 Ok(Some(vec![lsp::Location {
15817 uri: params.text_document_position.text_document.uri,
15818 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15819 }]))
15820 });
15821 (go_to_definition, references)
15822 };
15823
15824 cx.set_state(
15825 &r#"fn one() {
15826 let mut a = ˇtwo();
15827 }
15828
15829 fn two() {}"#
15830 .unindent(),
15831 );
15832 set_up_lsp_handlers(false, &mut cx);
15833 let navigated = cx
15834 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15835 .await
15836 .expect("Failed to navigate to definition");
15837 assert_eq!(
15838 navigated,
15839 Navigated::Yes,
15840 "Should have navigated to definition from the GetDefinition response"
15841 );
15842 cx.assert_editor_state(
15843 &r#"fn one() {
15844 let mut a = two();
15845 }
15846
15847 fn «twoˇ»() {}"#
15848 .unindent(),
15849 );
15850
15851 let editors = cx.update_workspace(|workspace, _, cx| {
15852 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15853 });
15854 cx.update_editor(|_, _, test_editor_cx| {
15855 assert_eq!(
15856 editors.len(),
15857 1,
15858 "Initially, only one, test, editor should be open in the workspace"
15859 );
15860 assert_eq!(
15861 test_editor_cx.entity(),
15862 editors.last().expect("Asserted len is 1").clone()
15863 );
15864 });
15865
15866 set_up_lsp_handlers(true, &mut cx);
15867 let navigated = cx
15868 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15869 .await
15870 .expect("Failed to navigate to lookup references");
15871 assert_eq!(
15872 navigated,
15873 Navigated::Yes,
15874 "Should have navigated to references as a fallback after empty GoToDefinition response"
15875 );
15876 // We should not change the selections in the existing file,
15877 // if opening another milti buffer with the references
15878 cx.assert_editor_state(
15879 &r#"fn one() {
15880 let mut a = two();
15881 }
15882
15883 fn «twoˇ»() {}"#
15884 .unindent(),
15885 );
15886 let editors = cx.update_workspace(|workspace, _, cx| {
15887 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15888 });
15889 cx.update_editor(|_, _, test_editor_cx| {
15890 assert_eq!(
15891 editors.len(),
15892 2,
15893 "After falling back to references search, we open a new editor with the results"
15894 );
15895 let references_fallback_text = editors
15896 .into_iter()
15897 .find(|new_editor| *new_editor != test_editor_cx.entity())
15898 .expect("Should have one non-test editor now")
15899 .read(test_editor_cx)
15900 .text(test_editor_cx);
15901 assert_eq!(
15902 references_fallback_text, "fn one() {\n let mut a = two();\n}",
15903 "Should use the range from the references response and not the GoToDefinition one"
15904 );
15905 });
15906}
15907
15908#[gpui::test]
15909async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
15910 init_test(cx, |_| {});
15911
15912 let language = Arc::new(Language::new(
15913 LanguageConfig::default(),
15914 Some(tree_sitter_rust::LANGUAGE.into()),
15915 ));
15916
15917 let text = r#"
15918 #[cfg(test)]
15919 mod tests() {
15920 #[test]
15921 fn runnable_1() {
15922 let a = 1;
15923 }
15924
15925 #[test]
15926 fn runnable_2() {
15927 let a = 1;
15928 let b = 2;
15929 }
15930 }
15931 "#
15932 .unindent();
15933
15934 let fs = FakeFs::new(cx.executor());
15935 fs.insert_file("/file.rs", Default::default()).await;
15936
15937 let project = Project::test(fs, ["/a".as_ref()], cx).await;
15938 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15939 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15940 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15941 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
15942
15943 let editor = cx.new_window_entity(|window, cx| {
15944 Editor::new(
15945 EditorMode::Full,
15946 multi_buffer,
15947 Some(project.clone()),
15948 true,
15949 window,
15950 cx,
15951 )
15952 });
15953
15954 editor.update_in(cx, |editor, window, cx| {
15955 let snapshot = editor.buffer().read(cx).snapshot(cx);
15956 editor.tasks.insert(
15957 (buffer.read(cx).remote_id(), 3),
15958 RunnableTasks {
15959 templates: vec![],
15960 offset: snapshot.anchor_before(43),
15961 column: 0,
15962 extra_variables: HashMap::default(),
15963 context_range: BufferOffset(43)..BufferOffset(85),
15964 },
15965 );
15966 editor.tasks.insert(
15967 (buffer.read(cx).remote_id(), 8),
15968 RunnableTasks {
15969 templates: vec![],
15970 offset: snapshot.anchor_before(86),
15971 column: 0,
15972 extra_variables: HashMap::default(),
15973 context_range: BufferOffset(86)..BufferOffset(191),
15974 },
15975 );
15976
15977 // Test finding task when cursor is inside function body
15978 editor.change_selections(None, window, cx, |s| {
15979 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
15980 });
15981 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15982 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
15983
15984 // Test finding task when cursor is on function name
15985 editor.change_selections(None, window, cx, |s| {
15986 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
15987 });
15988 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
15989 assert_eq!(row, 8, "Should find task when cursor is on function name");
15990 });
15991}
15992
15993#[gpui::test]
15994async fn test_folding_buffers(cx: &mut TestAppContext) {
15995 init_test(cx, |_| {});
15996
15997 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
15998 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
15999 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16000
16001 let fs = FakeFs::new(cx.executor());
16002 fs.insert_tree(
16003 path!("/a"),
16004 json!({
16005 "first.rs": sample_text_1,
16006 "second.rs": sample_text_2,
16007 "third.rs": sample_text_3,
16008 }),
16009 )
16010 .await;
16011 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16012 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16013 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16014 let worktree = project.update(cx, |project, cx| {
16015 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16016 assert_eq!(worktrees.len(), 1);
16017 worktrees.pop().unwrap()
16018 });
16019 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16020
16021 let buffer_1 = project
16022 .update(cx, |project, cx| {
16023 project.open_buffer((worktree_id, "first.rs"), cx)
16024 })
16025 .await
16026 .unwrap();
16027 let buffer_2 = project
16028 .update(cx, |project, cx| {
16029 project.open_buffer((worktree_id, "second.rs"), cx)
16030 })
16031 .await
16032 .unwrap();
16033 let buffer_3 = project
16034 .update(cx, |project, cx| {
16035 project.open_buffer((worktree_id, "third.rs"), cx)
16036 })
16037 .await
16038 .unwrap();
16039
16040 let multi_buffer = cx.new(|cx| {
16041 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16042 multi_buffer.push_excerpts(
16043 buffer_1.clone(),
16044 [
16045 ExcerptRange {
16046 context: Point::new(0, 0)..Point::new(3, 0),
16047 primary: None,
16048 },
16049 ExcerptRange {
16050 context: Point::new(5, 0)..Point::new(7, 0),
16051 primary: None,
16052 },
16053 ExcerptRange {
16054 context: Point::new(9, 0)..Point::new(10, 4),
16055 primary: None,
16056 },
16057 ],
16058 cx,
16059 );
16060 multi_buffer.push_excerpts(
16061 buffer_2.clone(),
16062 [
16063 ExcerptRange {
16064 context: Point::new(0, 0)..Point::new(3, 0),
16065 primary: None,
16066 },
16067 ExcerptRange {
16068 context: Point::new(5, 0)..Point::new(7, 0),
16069 primary: None,
16070 },
16071 ExcerptRange {
16072 context: Point::new(9, 0)..Point::new(10, 4),
16073 primary: None,
16074 },
16075 ],
16076 cx,
16077 );
16078 multi_buffer.push_excerpts(
16079 buffer_3.clone(),
16080 [
16081 ExcerptRange {
16082 context: Point::new(0, 0)..Point::new(3, 0),
16083 primary: None,
16084 },
16085 ExcerptRange {
16086 context: Point::new(5, 0)..Point::new(7, 0),
16087 primary: None,
16088 },
16089 ExcerptRange {
16090 context: Point::new(9, 0)..Point::new(10, 4),
16091 primary: None,
16092 },
16093 ],
16094 cx,
16095 );
16096 multi_buffer
16097 });
16098 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16099 Editor::new(
16100 EditorMode::Full,
16101 multi_buffer.clone(),
16102 Some(project.clone()),
16103 true,
16104 window,
16105 cx,
16106 )
16107 });
16108
16109 assert_eq!(
16110 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16111 "\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",
16112 );
16113
16114 multi_buffer_editor.update(cx, |editor, cx| {
16115 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16116 });
16117 assert_eq!(
16118 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16119 "\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",
16120 "After folding the first buffer, its text should not be displayed"
16121 );
16122
16123 multi_buffer_editor.update(cx, |editor, cx| {
16124 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16125 });
16126 assert_eq!(
16127 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16128 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16129 "After folding the second buffer, its text should not be displayed"
16130 );
16131
16132 multi_buffer_editor.update(cx, |editor, cx| {
16133 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16134 });
16135 assert_eq!(
16136 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16137 "\n\n\n\n\n",
16138 "After folding the third buffer, its text should not be displayed"
16139 );
16140
16141 // Emulate selection inside the fold logic, that should work
16142 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16143 editor
16144 .snapshot(window, cx)
16145 .next_line_boundary(Point::new(0, 4));
16146 });
16147
16148 multi_buffer_editor.update(cx, |editor, cx| {
16149 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16150 });
16151 assert_eq!(
16152 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16153 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16154 "After unfolding the second buffer, its text should be displayed"
16155 );
16156
16157 // Typing inside of buffer 1 causes that buffer to be unfolded.
16158 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16159 assert_eq!(
16160 multi_buffer
16161 .read(cx)
16162 .snapshot(cx)
16163 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16164 .collect::<String>(),
16165 "bbbb"
16166 );
16167 editor.change_selections(None, window, cx, |selections| {
16168 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16169 });
16170 editor.handle_input("B", window, cx);
16171 });
16172
16173 assert_eq!(
16174 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16175 "\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",
16176 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16177 );
16178
16179 multi_buffer_editor.update(cx, |editor, cx| {
16180 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16181 });
16182 assert_eq!(
16183 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16184 "\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",
16185 "After unfolding the all buffers, all original text should be displayed"
16186 );
16187}
16188
16189#[gpui::test]
16190async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16191 init_test(cx, |_| {});
16192
16193 let sample_text_1 = "1111\n2222\n3333".to_string();
16194 let sample_text_2 = "4444\n5555\n6666".to_string();
16195 let sample_text_3 = "7777\n8888\n9999".to_string();
16196
16197 let fs = FakeFs::new(cx.executor());
16198 fs.insert_tree(
16199 path!("/a"),
16200 json!({
16201 "first.rs": sample_text_1,
16202 "second.rs": sample_text_2,
16203 "third.rs": sample_text_3,
16204 }),
16205 )
16206 .await;
16207 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16208 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16209 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16210 let worktree = project.update(cx, |project, cx| {
16211 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16212 assert_eq!(worktrees.len(), 1);
16213 worktrees.pop().unwrap()
16214 });
16215 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16216
16217 let buffer_1 = project
16218 .update(cx, |project, cx| {
16219 project.open_buffer((worktree_id, "first.rs"), cx)
16220 })
16221 .await
16222 .unwrap();
16223 let buffer_2 = project
16224 .update(cx, |project, cx| {
16225 project.open_buffer((worktree_id, "second.rs"), cx)
16226 })
16227 .await
16228 .unwrap();
16229 let buffer_3 = project
16230 .update(cx, |project, cx| {
16231 project.open_buffer((worktree_id, "third.rs"), cx)
16232 })
16233 .await
16234 .unwrap();
16235
16236 let multi_buffer = cx.new(|cx| {
16237 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16238 multi_buffer.push_excerpts(
16239 buffer_1.clone(),
16240 [ExcerptRange {
16241 context: Point::new(0, 0)..Point::new(3, 0),
16242 primary: None,
16243 }],
16244 cx,
16245 );
16246 multi_buffer.push_excerpts(
16247 buffer_2.clone(),
16248 [ExcerptRange {
16249 context: Point::new(0, 0)..Point::new(3, 0),
16250 primary: None,
16251 }],
16252 cx,
16253 );
16254 multi_buffer.push_excerpts(
16255 buffer_3.clone(),
16256 [ExcerptRange {
16257 context: Point::new(0, 0)..Point::new(3, 0),
16258 primary: None,
16259 }],
16260 cx,
16261 );
16262 multi_buffer
16263 });
16264
16265 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16266 Editor::new(
16267 EditorMode::Full,
16268 multi_buffer,
16269 Some(project.clone()),
16270 true,
16271 window,
16272 cx,
16273 )
16274 });
16275
16276 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16277 assert_eq!(
16278 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16279 full_text,
16280 );
16281
16282 multi_buffer_editor.update(cx, |editor, cx| {
16283 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16284 });
16285 assert_eq!(
16286 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16287 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16288 "After folding the first buffer, its text should not be displayed"
16289 );
16290
16291 multi_buffer_editor.update(cx, |editor, cx| {
16292 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16293 });
16294
16295 assert_eq!(
16296 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16297 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16298 "After folding the second buffer, its text should not be displayed"
16299 );
16300
16301 multi_buffer_editor.update(cx, |editor, cx| {
16302 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16303 });
16304 assert_eq!(
16305 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16306 "\n\n\n\n\n",
16307 "After folding the third buffer, its text should not be displayed"
16308 );
16309
16310 multi_buffer_editor.update(cx, |editor, cx| {
16311 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16312 });
16313 assert_eq!(
16314 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16315 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16316 "After unfolding the second buffer, its text should be displayed"
16317 );
16318
16319 multi_buffer_editor.update(cx, |editor, cx| {
16320 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16321 });
16322 assert_eq!(
16323 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16324 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16325 "After unfolding the first buffer, its text should be displayed"
16326 );
16327
16328 multi_buffer_editor.update(cx, |editor, cx| {
16329 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16330 });
16331 assert_eq!(
16332 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16333 full_text,
16334 "After unfolding all buffers, all original text should be displayed"
16335 );
16336}
16337
16338#[gpui::test]
16339async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16340 init_test(cx, |_| {});
16341
16342 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16343
16344 let fs = FakeFs::new(cx.executor());
16345 fs.insert_tree(
16346 path!("/a"),
16347 json!({
16348 "main.rs": sample_text,
16349 }),
16350 )
16351 .await;
16352 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16353 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16354 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16355 let worktree = project.update(cx, |project, cx| {
16356 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16357 assert_eq!(worktrees.len(), 1);
16358 worktrees.pop().unwrap()
16359 });
16360 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16361
16362 let buffer_1 = project
16363 .update(cx, |project, cx| {
16364 project.open_buffer((worktree_id, "main.rs"), cx)
16365 })
16366 .await
16367 .unwrap();
16368
16369 let multi_buffer = cx.new(|cx| {
16370 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16371 multi_buffer.push_excerpts(
16372 buffer_1.clone(),
16373 [ExcerptRange {
16374 context: Point::new(0, 0)
16375 ..Point::new(
16376 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16377 0,
16378 ),
16379 primary: None,
16380 }],
16381 cx,
16382 );
16383 multi_buffer
16384 });
16385 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16386 Editor::new(
16387 EditorMode::Full,
16388 multi_buffer,
16389 Some(project.clone()),
16390 true,
16391 window,
16392 cx,
16393 )
16394 });
16395
16396 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16397 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16398 enum TestHighlight {}
16399 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16400 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16401 editor.highlight_text::<TestHighlight>(
16402 vec![highlight_range.clone()],
16403 HighlightStyle::color(Hsla::green()),
16404 cx,
16405 );
16406 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16407 });
16408
16409 let full_text = format!("\n\n\n{sample_text}\n");
16410 assert_eq!(
16411 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16412 full_text,
16413 );
16414}
16415
16416#[gpui::test]
16417async fn test_inline_completion_text(cx: &mut TestAppContext) {
16418 init_test(cx, |_| {});
16419
16420 // Simple insertion
16421 assert_highlighted_edits(
16422 "Hello, world!",
16423 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16424 true,
16425 cx,
16426 |highlighted_edits, cx| {
16427 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16428 assert_eq!(highlighted_edits.highlights.len(), 1);
16429 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16430 assert_eq!(
16431 highlighted_edits.highlights[0].1.background_color,
16432 Some(cx.theme().status().created_background)
16433 );
16434 },
16435 )
16436 .await;
16437
16438 // Replacement
16439 assert_highlighted_edits(
16440 "This is a test.",
16441 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16442 false,
16443 cx,
16444 |highlighted_edits, cx| {
16445 assert_eq!(highlighted_edits.text, "That is a test.");
16446 assert_eq!(highlighted_edits.highlights.len(), 1);
16447 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16448 assert_eq!(
16449 highlighted_edits.highlights[0].1.background_color,
16450 Some(cx.theme().status().created_background)
16451 );
16452 },
16453 )
16454 .await;
16455
16456 // Multiple edits
16457 assert_highlighted_edits(
16458 "Hello, world!",
16459 vec![
16460 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16461 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16462 ],
16463 false,
16464 cx,
16465 |highlighted_edits, cx| {
16466 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16467 assert_eq!(highlighted_edits.highlights.len(), 2);
16468 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16469 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16470 assert_eq!(
16471 highlighted_edits.highlights[0].1.background_color,
16472 Some(cx.theme().status().created_background)
16473 );
16474 assert_eq!(
16475 highlighted_edits.highlights[1].1.background_color,
16476 Some(cx.theme().status().created_background)
16477 );
16478 },
16479 )
16480 .await;
16481
16482 // Multiple lines with edits
16483 assert_highlighted_edits(
16484 "First line\nSecond line\nThird line\nFourth line",
16485 vec![
16486 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16487 (
16488 Point::new(2, 0)..Point::new(2, 10),
16489 "New third line".to_string(),
16490 ),
16491 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16492 ],
16493 false,
16494 cx,
16495 |highlighted_edits, cx| {
16496 assert_eq!(
16497 highlighted_edits.text,
16498 "Second modified\nNew third line\nFourth updated line"
16499 );
16500 assert_eq!(highlighted_edits.highlights.len(), 3);
16501 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16502 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16503 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16504 for highlight in &highlighted_edits.highlights {
16505 assert_eq!(
16506 highlight.1.background_color,
16507 Some(cx.theme().status().created_background)
16508 );
16509 }
16510 },
16511 )
16512 .await;
16513}
16514
16515#[gpui::test]
16516async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16517 init_test(cx, |_| {});
16518
16519 // Deletion
16520 assert_highlighted_edits(
16521 "Hello, world!",
16522 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16523 true,
16524 cx,
16525 |highlighted_edits, cx| {
16526 assert_eq!(highlighted_edits.text, "Hello, world!");
16527 assert_eq!(highlighted_edits.highlights.len(), 1);
16528 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16529 assert_eq!(
16530 highlighted_edits.highlights[0].1.background_color,
16531 Some(cx.theme().status().deleted_background)
16532 );
16533 },
16534 )
16535 .await;
16536
16537 // Insertion
16538 assert_highlighted_edits(
16539 "Hello, world!",
16540 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16541 true,
16542 cx,
16543 |highlighted_edits, cx| {
16544 assert_eq!(highlighted_edits.highlights.len(), 1);
16545 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16546 assert_eq!(
16547 highlighted_edits.highlights[0].1.background_color,
16548 Some(cx.theme().status().created_background)
16549 );
16550 },
16551 )
16552 .await;
16553}
16554
16555async fn assert_highlighted_edits(
16556 text: &str,
16557 edits: Vec<(Range<Point>, String)>,
16558 include_deletions: bool,
16559 cx: &mut TestAppContext,
16560 assertion_fn: impl Fn(HighlightedText, &App),
16561) {
16562 let window = cx.add_window(|window, cx| {
16563 let buffer = MultiBuffer::build_simple(text, cx);
16564 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16565 });
16566 let cx = &mut VisualTestContext::from_window(*window, cx);
16567
16568 let (buffer, snapshot) = window
16569 .update(cx, |editor, _window, cx| {
16570 (
16571 editor.buffer().clone(),
16572 editor.buffer().read(cx).snapshot(cx),
16573 )
16574 })
16575 .unwrap();
16576
16577 let edits = edits
16578 .into_iter()
16579 .map(|(range, edit)| {
16580 (
16581 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16582 edit,
16583 )
16584 })
16585 .collect::<Vec<_>>();
16586
16587 let text_anchor_edits = edits
16588 .clone()
16589 .into_iter()
16590 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16591 .collect::<Vec<_>>();
16592
16593 let edit_preview = window
16594 .update(cx, |_, _window, cx| {
16595 buffer
16596 .read(cx)
16597 .as_singleton()
16598 .unwrap()
16599 .read(cx)
16600 .preview_edits(text_anchor_edits.into(), cx)
16601 })
16602 .unwrap()
16603 .await;
16604
16605 cx.update(|_window, cx| {
16606 let highlighted_edits = inline_completion_edit_text(
16607 &snapshot.as_singleton().unwrap().2,
16608 &edits,
16609 &edit_preview,
16610 include_deletions,
16611 cx,
16612 );
16613 assertion_fn(highlighted_edits, cx)
16614 });
16615}
16616
16617#[gpui::test]
16618async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16619 init_test(cx, |_| {});
16620 let capabilities = lsp::ServerCapabilities {
16621 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16622 prepare_provider: Some(true),
16623 work_done_progress_options: Default::default(),
16624 })),
16625 ..Default::default()
16626 };
16627 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16628
16629 cx.set_state(indoc! {"
16630 struct Fˇoo {}
16631 "});
16632
16633 cx.update_editor(|editor, _, cx| {
16634 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16635 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16636 editor.highlight_background::<DocumentHighlightRead>(
16637 &[highlight_range],
16638 |c| c.editor_document_highlight_read_background,
16639 cx,
16640 );
16641 });
16642
16643 let mut prepare_rename_handler =
16644 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16645 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16646 start: lsp::Position {
16647 line: 0,
16648 character: 7,
16649 },
16650 end: lsp::Position {
16651 line: 0,
16652 character: 10,
16653 },
16654 })))
16655 });
16656 let prepare_rename_task = cx
16657 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16658 .expect("Prepare rename was not started");
16659 prepare_rename_handler.next().await.unwrap();
16660 prepare_rename_task.await.expect("Prepare rename failed");
16661
16662 let mut rename_handler =
16663 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16664 let edit = lsp::TextEdit {
16665 range: lsp::Range {
16666 start: lsp::Position {
16667 line: 0,
16668 character: 7,
16669 },
16670 end: lsp::Position {
16671 line: 0,
16672 character: 10,
16673 },
16674 },
16675 new_text: "FooRenamed".to_string(),
16676 };
16677 Ok(Some(lsp::WorkspaceEdit::new(
16678 // Specify the same edit twice
16679 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16680 )))
16681 });
16682 let rename_task = cx
16683 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16684 .expect("Confirm rename was not started");
16685 rename_handler.next().await.unwrap();
16686 rename_task.await.expect("Confirm rename failed");
16687 cx.run_until_parked();
16688
16689 // Despite two edits, only one is actually applied as those are identical
16690 cx.assert_editor_state(indoc! {"
16691 struct FooRenamedˇ {}
16692 "});
16693}
16694
16695#[gpui::test]
16696async fn test_rename_without_prepare(cx: &mut TestAppContext) {
16697 init_test(cx, |_| {});
16698 // These capabilities indicate that the server does not support prepare rename.
16699 let capabilities = lsp::ServerCapabilities {
16700 rename_provider: Some(lsp::OneOf::Left(true)),
16701 ..Default::default()
16702 };
16703 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16704
16705 cx.set_state(indoc! {"
16706 struct Fˇoo {}
16707 "});
16708
16709 cx.update_editor(|editor, _window, cx| {
16710 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16711 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16712 editor.highlight_background::<DocumentHighlightRead>(
16713 &[highlight_range],
16714 |c| c.editor_document_highlight_read_background,
16715 cx,
16716 );
16717 });
16718
16719 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16720 .expect("Prepare rename was not started")
16721 .await
16722 .expect("Prepare rename failed");
16723
16724 let mut rename_handler =
16725 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16726 let edit = lsp::TextEdit {
16727 range: lsp::Range {
16728 start: lsp::Position {
16729 line: 0,
16730 character: 7,
16731 },
16732 end: lsp::Position {
16733 line: 0,
16734 character: 10,
16735 },
16736 },
16737 new_text: "FooRenamed".to_string(),
16738 };
16739 Ok(Some(lsp::WorkspaceEdit::new(
16740 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
16741 )))
16742 });
16743 let rename_task = cx
16744 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16745 .expect("Confirm rename was not started");
16746 rename_handler.next().await.unwrap();
16747 rename_task.await.expect("Confirm rename failed");
16748 cx.run_until_parked();
16749
16750 // Correct range is renamed, as `surrounding_word` is used to find it.
16751 cx.assert_editor_state(indoc! {"
16752 struct FooRenamedˇ {}
16753 "});
16754}
16755
16756#[gpui::test]
16757async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
16758 init_test(cx, |_| {});
16759 let mut cx = EditorTestContext::new(cx).await;
16760
16761 let language = Arc::new(
16762 Language::new(
16763 LanguageConfig::default(),
16764 Some(tree_sitter_html::LANGUAGE.into()),
16765 )
16766 .with_brackets_query(
16767 r#"
16768 ("<" @open "/>" @close)
16769 ("</" @open ">" @close)
16770 ("<" @open ">" @close)
16771 ("\"" @open "\"" @close)
16772 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
16773 "#,
16774 )
16775 .unwrap(),
16776 );
16777 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16778
16779 cx.set_state(indoc! {"
16780 <span>ˇ</span>
16781 "});
16782 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16783 cx.assert_editor_state(indoc! {"
16784 <span>
16785 ˇ
16786 </span>
16787 "});
16788
16789 cx.set_state(indoc! {"
16790 <span><span></span>ˇ</span>
16791 "});
16792 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16793 cx.assert_editor_state(indoc! {"
16794 <span><span></span>
16795 ˇ</span>
16796 "});
16797
16798 cx.set_state(indoc! {"
16799 <span>ˇ
16800 </span>
16801 "});
16802 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
16803 cx.assert_editor_state(indoc! {"
16804 <span>
16805 ˇ
16806 </span>
16807 "});
16808}
16809
16810mod autoclose_tags {
16811 use super::*;
16812 use language::language_settings::JsxTagAutoCloseSettings;
16813 use languages::language;
16814
16815 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
16816 init_test(cx, |settings| {
16817 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
16818 });
16819
16820 let mut cx = EditorTestContext::new(cx).await;
16821 cx.update_buffer(|buffer, cx| {
16822 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
16823
16824 buffer.set_language(Some(language), cx)
16825 });
16826
16827 cx
16828 }
16829
16830 macro_rules! check {
16831 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
16832 #[gpui::test]
16833 async fn $name(cx: &mut TestAppContext) {
16834 let mut cx = test_setup(cx).await;
16835 cx.set_state($initial);
16836 cx.run_until_parked();
16837
16838 cx.update_editor(|editor, window, cx| {
16839 editor.handle_input($input, window, cx);
16840 });
16841 cx.run_until_parked();
16842 cx.assert_editor_state($expected);
16843 }
16844 };
16845 }
16846
16847 check!(
16848 test_basic,
16849 "<divˇ" + ">" => "<div>ˇ</div>"
16850 );
16851
16852 check!(
16853 test_basic_nested,
16854 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
16855 );
16856
16857 check!(
16858 test_basic_ignore_already_closed,
16859 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
16860 );
16861
16862 check!(
16863 test_doesnt_autoclose_closing_tag,
16864 "</divˇ" + ">" => "</div>ˇ"
16865 );
16866
16867 check!(
16868 test_jsx_attr,
16869 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
16870 );
16871
16872 check!(
16873 test_ignores_closing_tags_in_expr_block,
16874 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
16875 );
16876
16877 check!(
16878 test_doesnt_autoclose_on_gt_in_expr,
16879 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
16880 );
16881
16882 check!(
16883 test_ignores_closing_tags_with_different_tag_names,
16884 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
16885 );
16886
16887 check!(
16888 test_autocloses_in_jsx_expression,
16889 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
16890 );
16891
16892 check!(
16893 test_doesnt_autoclose_already_closed_in_jsx_expression,
16894 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
16895 );
16896
16897 check!(
16898 test_autocloses_fragment,
16899 "<ˇ" + ">" => "<>ˇ</>"
16900 );
16901
16902 check!(
16903 test_does_not_include_type_argument_in_autoclose_tag_name,
16904 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
16905 );
16906
16907 check!(
16908 test_does_not_autoclose_doctype,
16909 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
16910 );
16911
16912 check!(
16913 test_does_not_autoclose_comment,
16914 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
16915 );
16916
16917 check!(
16918 test_multi_cursor_autoclose_same_tag,
16919 r#"
16920 <divˇ
16921 <divˇ
16922 "#
16923 + ">" =>
16924 r#"
16925 <div>ˇ</div>
16926 <div>ˇ</div>
16927 "#
16928 );
16929
16930 check!(
16931 test_multi_cursor_autoclose_different_tags,
16932 r#"
16933 <divˇ
16934 <spanˇ
16935 "#
16936 + ">" =>
16937 r#"
16938 <div>ˇ</div>
16939 <span>ˇ</span>
16940 "#
16941 );
16942
16943 check!(
16944 test_multi_cursor_autoclose_some_dont_autoclose_others,
16945 r#"
16946 <divˇ
16947 <div /ˇ
16948 <spanˇ</span>
16949 <!DOCTYPE htmlˇ
16950 </headˇ
16951 <Component<T>ˇ
16952 ˇ
16953 "#
16954 + ">" =>
16955 r#"
16956 <div>ˇ</div>
16957 <div />ˇ
16958 <span>ˇ</span>
16959 <!DOCTYPE html>ˇ
16960 </head>ˇ
16961 <Component<T>>ˇ</Component>
16962 >ˇ
16963 "#
16964 );
16965
16966 check!(
16967 test_doesnt_mess_up_trailing_text,
16968 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
16969 );
16970
16971 #[gpui::test]
16972 async fn test_multibuffer(cx: &mut TestAppContext) {
16973 init_test(cx, |settings| {
16974 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
16975 });
16976
16977 let buffer_a = cx.new(|cx| {
16978 let mut buf = language::Buffer::local("<div", cx);
16979 buf.set_language(
16980 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
16981 cx,
16982 );
16983 buf
16984 });
16985 let buffer_b = cx.new(|cx| {
16986 let mut buf = language::Buffer::local("<pre", cx);
16987 buf.set_language(
16988 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
16989 cx,
16990 );
16991 buf
16992 });
16993 let buffer_c = cx.new(|cx| {
16994 let buf = language::Buffer::local("<span", cx);
16995 buf
16996 });
16997 let buffer = cx.new(|cx| {
16998 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
16999 buf.push_excerpts(
17000 buffer_a,
17001 [ExcerptRange {
17002 context: text::Anchor::MIN..text::Anchor::MAX,
17003 primary: None,
17004 }],
17005 cx,
17006 );
17007 buf.push_excerpts(
17008 buffer_b,
17009 [ExcerptRange {
17010 context: text::Anchor::MIN..text::Anchor::MAX,
17011 primary: None,
17012 }],
17013 cx,
17014 );
17015 buf.push_excerpts(
17016 buffer_c,
17017 [ExcerptRange {
17018 context: text::Anchor::MIN..text::Anchor::MAX,
17019 primary: None,
17020 }],
17021 cx,
17022 );
17023 buf
17024 });
17025 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17026
17027 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17028
17029 cx.update_editor(|editor, window, cx| {
17030 editor.change_selections(None, window, cx, |selections| {
17031 selections.select(vec![
17032 Selection::from_offset(4),
17033 Selection::from_offset(9),
17034 Selection::from_offset(15),
17035 ])
17036 })
17037 });
17038 cx.run_until_parked();
17039
17040 cx.update_editor(|editor, window, cx| {
17041 editor.handle_input(">", window, cx);
17042 });
17043 cx.run_until_parked();
17044
17045 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17046 }
17047}
17048
17049fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17050 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17051 point..point
17052}
17053
17054fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17055 let (text, ranges) = marked_text_ranges(marked_text, true);
17056 assert_eq!(editor.text(cx), text);
17057 assert_eq!(
17058 editor.selections.ranges(cx),
17059 ranges,
17060 "Assert selections are {}",
17061 marked_text
17062 );
17063}
17064
17065pub fn handle_signature_help_request(
17066 cx: &mut EditorLspTestContext,
17067 mocked_response: lsp::SignatureHelp,
17068) -> impl Future<Output = ()> {
17069 let mut request =
17070 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17071 let mocked_response = mocked_response.clone();
17072 async move { Ok(Some(mocked_response)) }
17073 });
17074
17075 async move {
17076 request.next().await;
17077 }
17078}
17079
17080/// Handle completion request passing a marked string specifying where the completion
17081/// should be triggered from using '|' character, what range should be replaced, and what completions
17082/// should be returned using '<' and '>' to delimit the range
17083pub fn handle_completion_request(
17084 cx: &mut EditorLspTestContext,
17085 marked_string: &str,
17086 completions: Vec<&'static str>,
17087 counter: Arc<AtomicUsize>,
17088) -> impl Future<Output = ()> {
17089 let complete_from_marker: TextRangeMarker = '|'.into();
17090 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17091 let (_, mut marked_ranges) = marked_text_ranges_by(
17092 marked_string,
17093 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17094 );
17095
17096 let complete_from_position =
17097 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17098 let replace_range =
17099 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17100
17101 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17102 let completions = completions.clone();
17103 counter.fetch_add(1, atomic::Ordering::Release);
17104 async move {
17105 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17106 assert_eq!(
17107 params.text_document_position.position,
17108 complete_from_position
17109 );
17110 Ok(Some(lsp::CompletionResponse::Array(
17111 completions
17112 .iter()
17113 .map(|completion_text| lsp::CompletionItem {
17114 label: completion_text.to_string(),
17115 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17116 range: replace_range,
17117 new_text: completion_text.to_string(),
17118 })),
17119 ..Default::default()
17120 })
17121 .collect(),
17122 )))
17123 }
17124 });
17125
17126 async move {
17127 request.next().await;
17128 }
17129}
17130
17131fn handle_resolve_completion_request(
17132 cx: &mut EditorLspTestContext,
17133 edits: Option<Vec<(&'static str, &'static str)>>,
17134) -> impl Future<Output = ()> {
17135 let edits = edits.map(|edits| {
17136 edits
17137 .iter()
17138 .map(|(marked_string, new_text)| {
17139 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17140 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17141 lsp::TextEdit::new(replace_range, new_text.to_string())
17142 })
17143 .collect::<Vec<_>>()
17144 });
17145
17146 let mut request =
17147 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17148 let edits = edits.clone();
17149 async move {
17150 Ok(lsp::CompletionItem {
17151 additional_text_edits: edits,
17152 ..Default::default()
17153 })
17154 }
17155 });
17156
17157 async move {
17158 request.next().await;
17159 }
17160}
17161
17162pub(crate) fn update_test_language_settings(
17163 cx: &mut TestAppContext,
17164 f: impl Fn(&mut AllLanguageSettingsContent),
17165) {
17166 cx.update(|cx| {
17167 SettingsStore::update_global(cx, |store, cx| {
17168 store.update_user_settings::<AllLanguageSettings>(cx, f);
17169 });
17170 });
17171}
17172
17173pub(crate) fn update_test_project_settings(
17174 cx: &mut TestAppContext,
17175 f: impl Fn(&mut ProjectSettings),
17176) {
17177 cx.update(|cx| {
17178 SettingsStore::update_global(cx, |store, cx| {
17179 store.update_user_settings::<ProjectSettings>(cx, f);
17180 });
17181 });
17182}
17183
17184pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17185 cx.update(|cx| {
17186 assets::Assets.load_test_fonts(cx);
17187 let store = SettingsStore::test(cx);
17188 cx.set_global(store);
17189 theme::init(theme::LoadThemes::JustBase, cx);
17190 release_channel::init(SemanticVersion::default(), cx);
17191 client::init_settings(cx);
17192 language::init(cx);
17193 Project::init_settings(cx);
17194 workspace::init_settings(cx);
17195 crate::init(cx);
17196 });
17197
17198 update_test_language_settings(cx, f);
17199}
17200
17201#[track_caller]
17202fn assert_hunk_revert(
17203 not_reverted_text_with_selections: &str,
17204 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17205 expected_reverted_text_with_selections: &str,
17206 base_text: &str,
17207 cx: &mut EditorLspTestContext,
17208) {
17209 cx.set_state(not_reverted_text_with_selections);
17210 cx.set_head_text(base_text);
17211 cx.executor().run_until_parked();
17212
17213 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17214 let snapshot = editor.snapshot(window, cx);
17215 let reverted_hunk_statuses = snapshot
17216 .buffer_snapshot
17217 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17218 .map(|hunk| hunk.status().kind)
17219 .collect::<Vec<_>>();
17220
17221 editor.git_restore(&Default::default(), window, cx);
17222 reverted_hunk_statuses
17223 });
17224 cx.executor().run_until_parked();
17225 cx.assert_editor_state(expected_reverted_text_with_selections);
17226 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17227}